@vibecheckai/cli 3.1.8 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/bin/registry.js +106 -116
  2. package/bin/runners/context/generators/mcp.js +18 -0
  3. package/bin/runners/context/index.js +72 -4
  4. package/bin/runners/context/proof-context.js +293 -1
  5. package/bin/runners/context/security-scanner.js +311 -73
  6. package/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
  7. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  8. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  9. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  10. package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
  11. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  12. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  13. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  14. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
  15. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  16. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
  17. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  18. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  19. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  20. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  21. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  22. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  23. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  24. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  25. package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
  26. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  27. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  28. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  29. package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
  30. package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
  31. package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
  32. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  33. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  34. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
  35. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
  36. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
  37. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  38. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  39. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  40. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  41. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  42. package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
  43. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  44. package/bin/runners/lib/analysis-core.js +198 -180
  45. package/bin/runners/lib/analyzers.js +1394 -224
  46. package/bin/runners/lib/detectors-v2.js +560 -641
  47. package/bin/runners/lib/entitlements-v2.js +48 -1
  48. package/bin/runners/lib/evidence-pack.js +678 -0
  49. package/bin/runners/lib/fingerprint.js +377 -0
  50. package/bin/runners/lib/html-proof-report.js +913 -0
  51. package/bin/runners/lib/missions/plan.js +231 -41
  52. package/bin/runners/lib/missions/templates.js +125 -0
  53. package/bin/runners/lib/route-truth.js +1167 -322
  54. package/bin/runners/lib/scan-output.js +558 -235
  55. package/bin/runners/lib/ship-output.js +901 -641
  56. package/bin/runners/lib/truth.js +1004 -321
  57. package/bin/runners/runAgent.js +161 -0
  58. package/bin/runners/runCheckpoint.js +44 -3
  59. package/bin/runners/runContext.d.ts +4 -0
  60. package/bin/runners/runDoctor.js +10 -2
  61. package/bin/runners/runFirewall.js +134 -0
  62. package/bin/runners/runFirewallHook.js +56 -0
  63. package/bin/runners/runFix.js +51 -341
  64. package/bin/runners/runInit.js +11 -0
  65. package/bin/runners/runPolish.d.ts +4 -0
  66. package/bin/runners/runPolish.js +608 -29
  67. package/bin/runners/runProve.js +210 -25
  68. package/bin/runners/runReality.js +846 -101
  69. package/bin/runners/runScan.js +351 -14
  70. package/bin/runners/runShip.js +19 -3
  71. package/bin/runners/runTruth.js +89 -0
  72. package/bin/runners/runWatch.js +14 -1
  73. package/bin/vibecheck.js +32 -2
  74. package/mcp-server/agent-firewall-interceptor.js +164 -0
  75. package/mcp-server/consolidated-tools.js +408 -42
  76. package/mcp-server/index.js +498 -327
  77. package/mcp-server/proof-tools.js +571 -0
  78. package/mcp-server/tier-auth.js +22 -19
  79. package/mcp-server/tools-v3.js +744 -0
  80. package/mcp-server/truth-context.js +131 -90
  81. package/mcp-server/truth-firewall-tools.js +1494 -941
  82. package/package.json +3 -1
  83. package/bin/runners/runInstall.js +0 -281
  84. package/bin/runners/runLabs.js +0 -341
@@ -0,0 +1,228 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "required": ["id", "timestamp", "agentId", "intent", "files", "claims", "verdict"],
5
+ "properties": {
6
+ "id": {
7
+ "type": "string",
8
+ "description": "Unique packet ID (hash-based)"
9
+ },
10
+ "timestamp": {
11
+ "type": "string",
12
+ "format": "date-time",
13
+ "description": "ISO timestamp of the change"
14
+ },
15
+ "agentId": {
16
+ "type": "string",
17
+ "description": "Identifier for the AI agent (e.g., 'cursor', 'windsurf', 'copilot')"
18
+ },
19
+ "intent": {
20
+ "type": "string",
21
+ "description": "Agent's stated intent for the change"
22
+ },
23
+ "diff": {
24
+ "type": "object",
25
+ "properties": {
26
+ "before": {
27
+ "type": "string",
28
+ "description": "File content before change"
29
+ },
30
+ "after": {
31
+ "type": "string",
32
+ "description": "File content after change"
33
+ },
34
+ "unified": {
35
+ "type": "string",
36
+ "description": "Unified diff format"
37
+ }
38
+ }
39
+ },
40
+ "files": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "required": ["path", "linesChanged"],
45
+ "properties": {
46
+ "path": {
47
+ "type": "string",
48
+ "description": "Relative file path"
49
+ },
50
+ "linesChanged": {
51
+ "type": "number",
52
+ "description": "Number of lines changed"
53
+ },
54
+ "domain": {
55
+ "type": "string",
56
+ "enum": ["auth", "payments", "routes", "contracts", "ui", "general"],
57
+ "description": "File domain classification"
58
+ }
59
+ }
60
+ },
61
+ "description": "List of changed files"
62
+ },
63
+ "claims": {
64
+ "type": "array",
65
+ "items": {
66
+ "type": "object",
67
+ "required": ["type", "value", "criticality"],
68
+ "properties": {
69
+ "type": {
70
+ "type": "string",
71
+ "enum": ["route", "env_used", "auth_boundary", "data_contract", "http_call", "ui_success_claim", "side_effect"],
72
+ "description": "Claim type"
73
+ },
74
+ "value": {
75
+ "type": "string",
76
+ "description": "Claim value (e.g., route path, env var name)"
77
+ },
78
+ "criticality": {
79
+ "type": "string",
80
+ "enum": ["hard", "soft"],
81
+ "description": "Claim criticality"
82
+ },
83
+ "pointer": {
84
+ "type": "string",
85
+ "description": "File:line pointer (e.g., 'file.ts:42-45')"
86
+ },
87
+ "reason": {
88
+ "type": "string",
89
+ "description": "Reason for claim extraction"
90
+ },
91
+ "file": {
92
+ "type": "string",
93
+ "description": "File where claim was found"
94
+ },
95
+ "domain": {
96
+ "type": "string",
97
+ "enum": ["auth", "payments", "routes", "contracts", "ui", "general"]
98
+ }
99
+ }
100
+ },
101
+ "description": "Extracted claims from the change"
102
+ },
103
+ "evidence": {
104
+ "type": "array",
105
+ "items": {
106
+ "type": "object",
107
+ "required": ["claimId", "result"],
108
+ "properties": {
109
+ "claimId": {
110
+ "type": "string",
111
+ "description": "Reference to claim in claims array"
112
+ },
113
+ "result": {
114
+ "type": "string",
115
+ "enum": ["PROVEN", "UNPROVEN", "CONTRADICTS"],
116
+ "description": "Evidence resolution result"
117
+ },
118
+ "sources": {
119
+ "type": "array",
120
+ "items": {
121
+ "type": "object",
122
+ "properties": {
123
+ "type": {
124
+ "type": "string",
125
+ "enum": ["truthpack.routes", "truthpack.env", "truthpack.auth", "truthpack.contracts", "repo.search"]
126
+ },
127
+ "pointer": {
128
+ "type": "string"
129
+ },
130
+ "confidence": {
131
+ "type": "number",
132
+ "minimum": 0,
133
+ "maximum": 1
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ },
140
+ "description": "Evidence resolution results"
141
+ },
142
+ "verdict": {
143
+ "type": "object",
144
+ "required": ["decision", "violations"],
145
+ "properties": {
146
+ "decision": {
147
+ "type": "string",
148
+ "enum": ["ALLOW", "WARN", "BLOCK"],
149
+ "description": "Final verdict"
150
+ },
151
+ "violations": {
152
+ "type": "array",
153
+ "items": {
154
+ "type": "object",
155
+ "required": ["rule", "severity", "message"],
156
+ "properties": {
157
+ "rule": {
158
+ "type": "string",
159
+ "description": "Rule ID that was violated"
160
+ },
161
+ "severity": {
162
+ "type": "string",
163
+ "enum": ["allow", "warn", "block"]
164
+ },
165
+ "message": {
166
+ "type": "string",
167
+ "description": "Violation message"
168
+ },
169
+ "claimId": {
170
+ "type": "string",
171
+ "description": "Related claim ID"
172
+ }
173
+ }
174
+ }
175
+ },
176
+ "message": {
177
+ "type": "string",
178
+ "description": "Human-readable verdict message"
179
+ }
180
+ }
181
+ },
182
+ "unblockPlan": {
183
+ "type": "object",
184
+ "properties": {
185
+ "steps": {
186
+ "type": "array",
187
+ "items": {
188
+ "type": "object",
189
+ "required": ["action", "file", "description"],
190
+ "properties": {
191
+ "action": {
192
+ "type": "string",
193
+ "enum": ["add", "modify", "create"]
194
+ },
195
+ "file": {
196
+ "type": "string"
197
+ },
198
+ "line": {
199
+ "type": "number"
200
+ },
201
+ "description": {
202
+ "type": "string"
203
+ }
204
+ }
205
+ }
206
+ }
207
+ },
208
+ "description": "Plan to unblock if verdict is BLOCK"
209
+ },
210
+ "metadata": {
211
+ "type": "object",
212
+ "properties": {
213
+ "totalFiles": {
214
+ "type": "number"
215
+ },
216
+ "totalLines": {
217
+ "type": "number"
218
+ },
219
+ "policyVersion": {
220
+ "type": "string"
221
+ },
222
+ "policyProfile": {
223
+ "type": "string"
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Change Packet Store
3
+ *
4
+ * Stores change packets in .vibecheck/packets/YYYY-MM-DD/<hash>.json
5
+ * Provides querying capabilities by agent, date, verdict.
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+
13
+ /**
14
+ * Store a change packet to disk
15
+ * @param {string} projectRoot - Project root directory
16
+ * @param {object} packet - Change packet to store
17
+ * @returns {string} Path where packet was stored
18
+ */
19
+ function storePacket(projectRoot, packet) {
20
+ const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
21
+
22
+ // Create date-based directory
23
+ const date = new Date(packet.timestamp);
24
+ const dateDir = date.toISOString().split('T')[0]; // YYYY-MM-DD
25
+ const dayDir = path.join(packetsDir, dateDir);
26
+
27
+ // Ensure directory exists
28
+ if (!fs.existsSync(dayDir)) {
29
+ fs.mkdirSync(dayDir, { recursive: true });
30
+ }
31
+
32
+ // Write packet file
33
+ const packetPath = path.join(dayDir, `${packet.id}.json`);
34
+ fs.writeFileSync(packetPath, JSON.stringify(packet, null, 2));
35
+
36
+ return packetPath;
37
+ }
38
+
39
+ /**
40
+ * Load a change packet by ID
41
+ * @param {string} projectRoot - Project root directory
42
+ * @param {string} packetId - Packet ID
43
+ * @returns {object|null} Change packet or null if not found
44
+ */
45
+ function loadPacket(projectRoot, packetId) {
46
+ const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
47
+
48
+ // Search all date directories
49
+ if (!fs.existsSync(packetsDir)) {
50
+ return null;
51
+ }
52
+
53
+ const dateDirs = fs.readdirSync(packetsDir, { withFileTypes: true })
54
+ .filter(dirent => dirent.isDirectory())
55
+ .map(dirent => dirent.name);
56
+
57
+ for (const dateDir of dateDirs) {
58
+ const dayDir = path.join(packetsDir, dateDir);
59
+ const packetPath = path.join(dayDir, `${packetId}.json`);
60
+
61
+ if (fs.existsSync(packetPath)) {
62
+ return JSON.parse(fs.readFileSync(packetPath, "utf8"));
63
+ }
64
+ }
65
+
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * Query change packets
71
+ * @param {string} projectRoot - Project root directory
72
+ * @param {object} filters - Query filters
73
+ * @param {string} filters.agentId - Filter by agent ID
74
+ * @param {string} filters.date - Filter by date (YYYY-MM-DD)
75
+ * @param {string} filters.verdict - Filter by verdict (ALLOW, WARN, BLOCK)
76
+ * @param {number} filters.limit - Maximum number of results
77
+ * @returns {array} Array of change packets
78
+ */
79
+ function queryPackets(projectRoot, filters = {}) {
80
+ const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
81
+
82
+ if (!fs.existsSync(packetsDir)) {
83
+ return [];
84
+ }
85
+
86
+ const results = [];
87
+ const { agentId, date, verdict, limit } = filters;
88
+
89
+ // Determine which date directories to search
90
+ const dateDirs = date
91
+ ? [date]
92
+ : fs.readdirSync(packetsDir, { withFileTypes: true })
93
+ .filter(dirent => dirent.isDirectory())
94
+ .map(dirent => dirent.name)
95
+ .sort()
96
+ .reverse(); // Most recent first
97
+
98
+ for (const dateDir of dateDirs) {
99
+ const dayDir = path.join(packetsDir, dateDir);
100
+
101
+ if (!fs.existsSync(dayDir)) continue;
102
+
103
+ const packetFiles = fs.readdirSync(dayDir)
104
+ .filter(file => file.endsWith('.json'))
105
+ .map(file => path.join(dayDir, file));
106
+
107
+ for (const packetPath of packetFiles) {
108
+ try {
109
+ const packet = JSON.parse(fs.readFileSync(packetPath, "utf8"));
110
+
111
+ // Apply filters
112
+ if (agentId && packet.agentId !== agentId) continue;
113
+ if (verdict && packet.verdict?.decision !== verdict) continue;
114
+
115
+ results.push(packet);
116
+
117
+ // Apply limit
118
+ if (limit && results.length >= limit) {
119
+ return results;
120
+ }
121
+ } catch (error) {
122
+ // Skip invalid packets
123
+ continue;
124
+ }
125
+ }
126
+
127
+ // If we have a date filter, we only need to check that one directory
128
+ if (date) break;
129
+ }
130
+
131
+ return results;
132
+ }
133
+
134
+ /**
135
+ * Get statistics about stored packets
136
+ * @param {string} projectRoot - Project root directory
137
+ * @returns {object} Statistics
138
+ */
139
+ function getPacketStats(projectRoot) {
140
+ const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
141
+
142
+ if (!fs.existsSync(packetsDir)) {
143
+ return {
144
+ total: 0,
145
+ byAgent: {},
146
+ byVerdict: { ALLOW: 0, WARN: 0, BLOCK: 0 },
147
+ byDate: {}
148
+ };
149
+ }
150
+
151
+ const stats = {
152
+ total: 0,
153
+ byAgent: {},
154
+ byVerdict: { ALLOW: 0, WARN: 0, BLOCK: 0 },
155
+ byDate: {}
156
+ };
157
+
158
+ const dateDirs = fs.readdirSync(packetsDir, { withFileTypes: true })
159
+ .filter(dirent => dirent.isDirectory())
160
+ .map(dirent => dirent.name);
161
+
162
+ for (const dateDir of dateDirs) {
163
+ const dayDir = path.join(packetsDir, dateDir);
164
+ if (!fs.existsSync(dayDir)) continue;
165
+
166
+ const packetFiles = fs.readdirSync(dayDir)
167
+ .filter(file => file.endsWith('.json'));
168
+
169
+ stats.byDate[dateDir] = packetFiles.length;
170
+
171
+ for (const file of packetFiles) {
172
+ try {
173
+ const packet = JSON.parse(
174
+ fs.readFileSync(path.join(dayDir, file), "utf8")
175
+ );
176
+
177
+ stats.total++;
178
+
179
+ // Count by agent
180
+ stats.byAgent[packet.agentId] = (stats.byAgent[packet.agentId] || 0) + 1;
181
+
182
+ // Count by verdict
183
+ const verdict = packet.verdict?.decision || "ALLOW";
184
+ stats.byVerdict[verdict] = (stats.byVerdict[verdict] || 0) + 1;
185
+ } catch (error) {
186
+ // Skip invalid packets
187
+ continue;
188
+ }
189
+ }
190
+ }
191
+
192
+ return stats;
193
+ }
194
+
195
+ module.exports = {
196
+ storePacket,
197
+ loadPacket,
198
+ queryPackets,
199
+ getPacketStats
200
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Claim Types
3
+ *
4
+ * Enumeration of claim types and criticality levels.
5
+ */
6
+
7
+ module.exports = {
8
+ CLAIM_TYPES: {
9
+ ROUTE: "route",
10
+ ENV: "env_used",
11
+ AUTH: "auth_boundary",
12
+ CONTRACT: "data_contract",
13
+ HTTP_CALL: "http_call",
14
+ UI_SUCCESS: "ui_success_claim",
15
+ SIDE_EFFECT: "side_effect"
16
+ },
17
+ CRITICALITY: {
18
+ HARD: "hard",
19
+ SOFT: "soft"
20
+ }
21
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Claim Extractor
3
+ *
4
+ * Extracts "drift-causing claims" from changed files.
5
+ * Uses AST-based extraction with Babel parser.
6
+ */
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+ const parser = require("@babel/parser");
11
+ const traverse = require("@babel/traverse").default;
12
+ const t = require("@babel/types");
13
+ const { CLAIM_TYPES, CRITICALITY } = require("./claim-types");
14
+ const { ROUTE_LIKE, AUTH_HINTS, SUCCESS_UI } = require("./patterns");
15
+
16
+ function parse(code) {
17
+ return parser.parse(code, {
18
+ sourceType: "unambiguous",
19
+ plugins: ["typescript", "jsx"]
20
+ });
21
+ }
22
+
23
+ function locPtr(fileRel, node) {
24
+ const loc = node && node.loc;
25
+ if (!loc) return null;
26
+ return `${fileRel}:${loc.start.line}-${loc.end.line}`;
27
+ }
28
+
29
+ function pushClaim(out, claim) {
30
+ const key = `${claim.type}|${claim.value}|${claim.pointer || ""}`;
31
+ if (!out._dedupe.has(key)) {
32
+ out._dedupe.add(key);
33
+ out.claims.push(claim);
34
+ }
35
+ }
36
+
37
+ function extractStringLiterals(code) {
38
+ // Cheap scan for route-ish strings even if AST misses template parts.
39
+ const hits = [];
40
+ for (const rx of ROUTE_LIKE) {
41
+ const m = code.match(rx);
42
+ if (m) hits.push(...m);
43
+ }
44
+ return [...new Set(hits)];
45
+ }
46
+
47
+ function classifyFileDomain(fileRel) {
48
+ const s = fileRel.toLowerCase();
49
+ if (s.includes("auth")) return "auth";
50
+ if (s.includes("stripe") || s.includes("payment")) return "payments";
51
+ if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
52
+ if (s.includes("schema") || s.includes("contract") || s.includes("openapi")) return "contracts";
53
+ if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
54
+ return "general";
55
+ }
56
+
57
+ function extractClaimsFromFile({ repoRoot, fileAbs }) {
58
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
59
+ const code = fs.readFileSync(fileAbs, "utf8");
60
+ const ast = parse(code);
61
+
62
+ const out = { fileRel, domain: classifyFileDomain(fileRel), claims: [], _dedupe: new Set() };
63
+
64
+ // 1) quick string route-ish scan
65
+ for (const r of extractStringLiterals(code)) {
66
+ pushClaim(out, {
67
+ type: CLAIM_TYPES.ROUTE,
68
+ value: r,
69
+ criticality: CRITICALITY.HARD,
70
+ pointer: `${fileRel}:1-1`,
71
+ reason: "route-like string literal"
72
+ });
73
+ }
74
+
75
+ // 2) AST-based env extraction
76
+ traverse(ast, {
77
+ MemberExpression(p) {
78
+ // process.env.X
79
+ const n = p.node;
80
+ if (
81
+ t.isMemberExpression(n.object) &&
82
+ t.isIdentifier(n.object.object, { name: "process" }) &&
83
+ t.isIdentifier(n.object.property, { name: "env" }) &&
84
+ (t.isIdentifier(n.property) || t.isStringLiteral(n.property))
85
+ ) {
86
+ const name = t.isIdentifier(n.property) ? n.property.name : n.property.value;
87
+ pushClaim(out, {
88
+ type: CLAIM_TYPES.ENV,
89
+ value: name,
90
+ criticality: CRITICALITY.HARD,
91
+ pointer: locPtr(fileRel, n),
92
+ reason: "process.env usage"
93
+ });
94
+ }
95
+
96
+ // import.meta.env.X (Vite)
97
+ if (
98
+ t.isMemberExpression(n.object) &&
99
+ t.isMemberExpression(n.object.object) &&
100
+ t.isIdentifier(n.object.object.object, { name: "import" }) &&
101
+ t.isIdentifier(n.object.object.property, { name: "meta" }) &&
102
+ t.isIdentifier(n.object.property, { name: "env" }) &&
103
+ (t.isIdentifier(n.property) || t.isStringLiteral(n.property))
104
+ ) {
105
+ const name = t.isIdentifier(n.property) ? n.property.name : n.property.value;
106
+ pushClaim(out, {
107
+ type: CLAIM_TYPES.ENV,
108
+ value: name,
109
+ criticality: CRITICALITY.HARD,
110
+ pointer: locPtr(fileRel, n),
111
+ reason: "import.meta.env usage"
112
+ });
113
+ }
114
+ },
115
+
116
+ CallExpression(p) {
117
+ const n = p.node;
118
+
119
+ // fetch("/api/...")
120
+ if (t.isIdentifier(n.callee, { name: "fetch" }) && n.arguments[0]) {
121
+ const a0 = n.arguments[0];
122
+ if (t.isStringLiteral(a0)) {
123
+ pushClaim(out, {
124
+ type: CLAIM_TYPES.HTTP_CALL,
125
+ value: a0.value,
126
+ criticality: CRITICALITY.HARD,
127
+ pointer: locPtr(fileRel, n),
128
+ reason: "fetch call"
129
+ });
130
+ }
131
+ }
132
+
133
+ // axios.get("/api/...")
134
+ if (t.isMemberExpression(n.callee) && t.isIdentifier(n.callee.object, { name: "axios" })) {
135
+ const method = t.isIdentifier(n.callee.property) ? n.callee.property.name : "call";
136
+ const a0 = n.arguments[0];
137
+ if (a0 && t.isStringLiteral(a0)) {
138
+ pushClaim(out, {
139
+ type: CLAIM_TYPES.HTTP_CALL,
140
+ value: `${method.toUpperCase()} ${a0.value}`,
141
+ criticality: CRITICALITY.HARD,
142
+ pointer: locPtr(fileRel, n),
143
+ reason: "axios call"
144
+ });
145
+ }
146
+ }
147
+
148
+ // toast.success("Saved")
149
+ if (
150
+ t.isMemberExpression(n.callee) &&
151
+ t.isIdentifier(n.callee.object, { name: "toast" }) &&
152
+ t.isIdentifier(n.callee.property, { name: "success" })
153
+ ) {
154
+ const msg = n.arguments[0] && t.isStringLiteral(n.arguments[0]) ? n.arguments[0].value : "toast.success";
155
+ pushClaim(out, {
156
+ type: CLAIM_TYPES.UI_SUCCESS,
157
+ value: msg,
158
+ criticality: CRITICALITY.SOFT,
159
+ pointer: locPtr(fileRel, n),
160
+ reason: "success UI signal"
161
+ });
162
+ }
163
+ }
164
+ });
165
+
166
+ // 3) auth-ish keyword scan (cheap but effective)
167
+ const lines = code.split(/\r?\n/);
168
+ for (let i = 0; i < lines.length; i++) {
169
+ const line = lines[i];
170
+ if (AUTH_HINTS.some((rx) => rx.test(line))) {
171
+ pushClaim(out, {
172
+ type: CLAIM_TYPES.AUTH,
173
+ value: line.trim().slice(0, 140),
174
+ criticality: CRITICALITY.HARD,
175
+ pointer: `${fileRel}:${i + 1}-${i + 1}`,
176
+ reason: "auth/role/scope hint"
177
+ });
178
+ }
179
+ if (SUCCESS_UI.some((rx) => rx.test(line))) {
180
+ pushClaim(out, {
181
+ type: CLAIM_TYPES.UI_SUCCESS,
182
+ value: line.trim().slice(0, 140),
183
+ criticality: CRITICALITY.SOFT,
184
+ pointer: `${fileRel}:${i + 1}-${i + 1}`,
185
+ reason: "success UI hint"
186
+ });
187
+ }
188
+ }
189
+
190
+ delete out._dedupe;
191
+ return out;
192
+ }
193
+
194
+ function extractClaims({ repoRoot, changedFilesAbs }) {
195
+ const files = changedFilesAbs.filter((f) => fs.existsSync(f));
196
+ const perFile = files.map((fileAbs) => extractClaimsFromFile({ repoRoot, fileAbs }));
197
+
198
+ // Flatten + dedupe across files
199
+ const dedupe = new Set();
200
+ const claims = [];
201
+ for (const f of perFile) {
202
+ for (const c of f.claims) {
203
+ const k = `${c.type}|${c.value}`;
204
+ if (!dedupe.has(k)) {
205
+ dedupe.add(k);
206
+ claims.push({ ...c, file: f.fileRel, domain: f.domain });
207
+ }
208
+ }
209
+ }
210
+
211
+ return { claims, perFile };
212
+ }
213
+
214
+ module.exports = { extractClaims, extractClaimsFromFile };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Claim Patterns
3
+ *
4
+ * Regex patterns for detecting claims in code.
5
+ */
6
+
7
+ const ROUTE_LIKE = [
8
+ /\/api\/[a-zA-Z0-9_\/\-]+/g,
9
+ /\/health\/[a-zA-Z0-9_\/\-]+/g
10
+ ];
11
+
12
+ const AUTH_HINTS = [
13
+ /\b(admin|owner|staff|superadmin|root)\b/i,
14
+ /\b(role|roles|scope|scopes|permission|permissions)\b/i,
15
+ /\b(auth|authorize|authorization|requireAuth|requireRole|rbac)\b/i
16
+ ];
17
+
18
+ const SUCCESS_UI = [
19
+ /\b(success|saved|updated|complete|done)\b/i,
20
+ /\btoast\.(success|info)\b/i,
21
+ /\benqueueSnackbar\b/i
22
+ ];
23
+
24
+ module.exports = { ROUTE_LIKE, AUTH_HINTS, SUCCESS_UI };