@vibecheckai/cli 3.2.0 → 3.2.2
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/runners/lib/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1119 -536
- package/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +144 -738
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runScan.js +113 -10
- package/bin/runners/runShip.js +7 -8
- package/bin/runners/runTruth.js +89 -0
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/index.js +347 -313
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1412 -1045
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change Packet Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds change packets from diffs + agent intent.
|
|
5
|
+
* Each packet is a complete audit artifact of an AI code change attempt.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const crypto = require("crypto");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a change packet from diff and agent intent
|
|
15
|
+
* @param {object} params
|
|
16
|
+
* @param {string} params.agentId - Agent identifier
|
|
17
|
+
* @param {string} params.intent - Agent's stated intent
|
|
18
|
+
* @param {object} params.diff - Diff object { before, after, unified }
|
|
19
|
+
* @param {string} params.filePath - File path (relative to repo root)
|
|
20
|
+
* @param {object} params.claims - Extracted claims
|
|
21
|
+
* @param {object} params.evidence - Evidence resolution results
|
|
22
|
+
* @param {object} params.verdict - Policy verdict
|
|
23
|
+
* @param {object} params.unblockPlan - Unblock plan (if blocked)
|
|
24
|
+
* @param {object} params.policy - Policy used for evaluation
|
|
25
|
+
* @returns {object} Change packet
|
|
26
|
+
*/
|
|
27
|
+
function buildChangePacket({
|
|
28
|
+
agentId,
|
|
29
|
+
intent,
|
|
30
|
+
diff,
|
|
31
|
+
filePath,
|
|
32
|
+
claims = [],
|
|
33
|
+
evidence = [],
|
|
34
|
+
verdict,
|
|
35
|
+
unblockPlan = null,
|
|
36
|
+
policy = null
|
|
37
|
+
}) {
|
|
38
|
+
const timestamp = new Date().toISOString();
|
|
39
|
+
|
|
40
|
+
// Generate unique packet ID from content hash
|
|
41
|
+
const packetContent = JSON.stringify({
|
|
42
|
+
agentId,
|
|
43
|
+
intent,
|
|
44
|
+
filePath,
|
|
45
|
+
timestamp,
|
|
46
|
+
diff: diff?.unified || ""
|
|
47
|
+
});
|
|
48
|
+
const id = crypto.createHash("sha256")
|
|
49
|
+
.update(packetContent)
|
|
50
|
+
.digest("hex")
|
|
51
|
+
.slice(0, 16);
|
|
52
|
+
|
|
53
|
+
// Calculate file statistics
|
|
54
|
+
const linesChanged = diff?.unified
|
|
55
|
+
? diff.unified.split('\n').filter(line => line.startsWith('+') || line.startsWith('-')).length
|
|
56
|
+
: 0;
|
|
57
|
+
|
|
58
|
+
const files = [{
|
|
59
|
+
path: filePath,
|
|
60
|
+
linesChanged,
|
|
61
|
+
domain: classifyFileDomain(filePath)
|
|
62
|
+
}];
|
|
63
|
+
|
|
64
|
+
// Build packet
|
|
65
|
+
const packet = {
|
|
66
|
+
id,
|
|
67
|
+
timestamp,
|
|
68
|
+
agentId,
|
|
69
|
+
intent: intent || "No intent provided",
|
|
70
|
+
diff: diff || null,
|
|
71
|
+
files,
|
|
72
|
+
claims,
|
|
73
|
+
evidence,
|
|
74
|
+
verdict: verdict || {
|
|
75
|
+
decision: "ALLOW",
|
|
76
|
+
violations: [],
|
|
77
|
+
message: "No verdict provided"
|
|
78
|
+
},
|
|
79
|
+
unblockPlan: unblockPlan || null,
|
|
80
|
+
metadata: {
|
|
81
|
+
totalFiles: files.length,
|
|
82
|
+
totalLines: linesChanged,
|
|
83
|
+
policyVersion: policy?.version || "unknown",
|
|
84
|
+
policyProfile: policy?.profile || "unknown"
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return packet;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build change packet from multiple files
|
|
93
|
+
* @param {object} params
|
|
94
|
+
* @param {string} params.agentId - Agent identifier
|
|
95
|
+
* @param {string} params.intent - Agent's stated intent
|
|
96
|
+
* @param {array} params.changes - Array of { filePath, diff, claims }
|
|
97
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
98
|
+
* @param {object} params.verdict - Policy verdict
|
|
99
|
+
* @param {object} params.unblockPlan - Unblock plan (if blocked)
|
|
100
|
+
* @param {object} params.policy - Policy used for evaluation
|
|
101
|
+
* @returns {object} Change packet
|
|
102
|
+
*/
|
|
103
|
+
function buildMultiFileChangePacket({
|
|
104
|
+
agentId,
|
|
105
|
+
intent,
|
|
106
|
+
changes = [],
|
|
107
|
+
evidence = [],
|
|
108
|
+
verdict,
|
|
109
|
+
unblockPlan = null,
|
|
110
|
+
policy = null
|
|
111
|
+
}) {
|
|
112
|
+
const timestamp = new Date().toISOString();
|
|
113
|
+
|
|
114
|
+
// Aggregate all file changes
|
|
115
|
+
const files = changes.map(change => ({
|
|
116
|
+
path: change.filePath,
|
|
117
|
+
linesChanged: calculateLinesChanged(change.diff),
|
|
118
|
+
domain: classifyFileDomain(change.filePath)
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// Aggregate all claims
|
|
122
|
+
const claims = changes.flatMap(change =>
|
|
123
|
+
(change.claims || []).map(claim => ({
|
|
124
|
+
...claim,
|
|
125
|
+
file: change.filePath
|
|
126
|
+
}))
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Generate unique packet ID from all changes
|
|
130
|
+
const packetContent = JSON.stringify({
|
|
131
|
+
agentId,
|
|
132
|
+
intent,
|
|
133
|
+
timestamp,
|
|
134
|
+
files: files.map(f => f.path),
|
|
135
|
+
claims: claims.map(c => `${c.type}:${c.value}`)
|
|
136
|
+
});
|
|
137
|
+
const id = crypto.createHash("sha256")
|
|
138
|
+
.update(packetContent)
|
|
139
|
+
.digest("hex")
|
|
140
|
+
.slice(0, 16);
|
|
141
|
+
|
|
142
|
+
// Build unified diff (concatenate all diffs)
|
|
143
|
+
const unifiedDiff = changes
|
|
144
|
+
.map(change => {
|
|
145
|
+
const diff = change.diff?.unified || "";
|
|
146
|
+
return diff ? `--- ${change.filePath}\n+++ ${change.filePath}\n${diff}` : "";
|
|
147
|
+
})
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join("\n\n");
|
|
150
|
+
|
|
151
|
+
const packet = {
|
|
152
|
+
id,
|
|
153
|
+
timestamp,
|
|
154
|
+
agentId,
|
|
155
|
+
intent: intent || "No intent provided",
|
|
156
|
+
diff: unifiedDiff ? {
|
|
157
|
+
unified: unifiedDiff,
|
|
158
|
+
before: null, // Multi-file diffs don't store full before/after
|
|
159
|
+
after: null
|
|
160
|
+
} : null,
|
|
161
|
+
files,
|
|
162
|
+
claims,
|
|
163
|
+
evidence,
|
|
164
|
+
verdict: verdict || {
|
|
165
|
+
decision: "ALLOW",
|
|
166
|
+
violations: [],
|
|
167
|
+
message: "No verdict provided"
|
|
168
|
+
},
|
|
169
|
+
unblockPlan: unblockPlan || null,
|
|
170
|
+
metadata: {
|
|
171
|
+
totalFiles: files.length,
|
|
172
|
+
totalLines: files.reduce((sum, f) => sum + f.linesChanged, 0),
|
|
173
|
+
policyVersion: policy?.version || "unknown",
|
|
174
|
+
policyProfile: policy?.profile || "unknown"
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return packet;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calculate number of lines changed from diff
|
|
183
|
+
* @param {object} diff - Diff object
|
|
184
|
+
* @returns {number} Number of lines changed
|
|
185
|
+
*/
|
|
186
|
+
function calculateLinesChanged(diff) {
|
|
187
|
+
if (!diff || !diff.unified) return 0;
|
|
188
|
+
return diff.unified
|
|
189
|
+
.split('\n')
|
|
190
|
+
.filter(line => line.startsWith('+') || line.startsWith('-'))
|
|
191
|
+
.length;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Classify file domain from path
|
|
196
|
+
* @param {string} filePath - File path
|
|
197
|
+
* @returns {string} Domain classification
|
|
198
|
+
*/
|
|
199
|
+
function classifyFileDomain(filePath) {
|
|
200
|
+
const s = filePath.toLowerCase();
|
|
201
|
+
if (s.includes("auth")) return "auth";
|
|
202
|
+
if (s.includes("stripe") || s.includes("payment")) return "payments";
|
|
203
|
+
if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
|
|
204
|
+
if (s.includes("schema") || s.includes("contract") || s.includes("openapi")) return "contracts";
|
|
205
|
+
if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
|
|
206
|
+
return "general";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
buildChangePacket,
|
|
211
|
+
buildMultiFileChangePacket,
|
|
212
|
+
calculateLinesChanged,
|
|
213
|
+
classifyFileDomain
|
|
214
|
+
};
|
|
@@ -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
|
+
};
|