@vibecheckai/cli 3.8.0 → 3.9.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.
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -98
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -318
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -484
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -418
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -333
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -622
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -102
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -352
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -283
- package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
- package/bin/runners/lib/engine/ast-cache.js +210 -210
- package/bin/runners/lib/engine/auth-extractor.js +211 -211
- package/bin/runners/lib/engine/billing-extractor.js +112 -112
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
- package/bin/runners/lib/engine/env-extractor.js +207 -207
- package/bin/runners/lib/engine/express-extractor.js +208 -208
- package/bin/runners/lib/engine/extractors.js +849 -849
- package/bin/runners/lib/engine/index.js +207 -207
- package/bin/runners/lib/engine/repo-index.js +514 -514
- package/bin/runners/lib/engine/types.js +124 -124
- package/bin/runners/runIntent.js +906 -906
- package/bin/runners/runPacks.js +2089 -2089
- package/bin/runners/runReality.js +178 -1
- package/bin/runners/runShield.js +1282 -1282
- package/mcp-server/handlers/index.ts +2 -2
- package/mcp-server/handlers/tool-handler.ts +47 -8
- package/mcp-server/lib/executor.ts +5 -5
- package/mcp-server/lib/index.ts +14 -4
- package/mcp-server/lib/sandbox.test.ts +4 -4
- package/mcp-server/lib/sandbox.ts +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry.test.ts +18 -12
- package/mcp-server/tsconfig.json +1 -0
- package/package.json +2 -1
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Intent Store - Persistent Intent Storage
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* AGENT FIREWALL™ - INTENT PERSISTENCE
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* Stores intent declarations for session and audit purposes.
|
|
9
|
-
* Intent is immutable once stored - updates create new versions.
|
|
10
|
-
*
|
|
11
|
-
* @module intent/store
|
|
12
|
-
* @version 2.0.0
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
"use strict";
|
|
16
|
-
|
|
17
|
-
const fs = require("fs");
|
|
18
|
-
const path = require("path");
|
|
19
|
-
const { verifyIntentIntegrity, createBlockingIntent } = require("./schema");
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Intent Store - manages intent persistence
|
|
23
|
-
*/
|
|
24
|
-
class IntentStore {
|
|
25
|
-
constructor(projectRoot) {
|
|
26
|
-
this.projectRoot = projectRoot;
|
|
27
|
-
this.intentDir = path.join(projectRoot, ".vibecheck", "intents");
|
|
28
|
-
this.currentIntentPath = path.join(this.intentDir, "current.json");
|
|
29
|
-
this.historyDir = path.join(this.intentDir, "history");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Ensure storage directories exist
|
|
34
|
-
*/
|
|
35
|
-
ensureDirectories() {
|
|
36
|
-
if (!fs.existsSync(this.intentDir)) {
|
|
37
|
-
fs.mkdirSync(this.intentDir, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
if (!fs.existsSync(this.historyDir)) {
|
|
40
|
-
fs.mkdirSync(this.historyDir, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Store a new intent (or update)
|
|
46
|
-
* @param {Object} intent - Intent to store
|
|
47
|
-
* @returns {Object} Storage result
|
|
48
|
-
*/
|
|
49
|
-
store(intent) {
|
|
50
|
-
this.ensureDirectories();
|
|
51
|
-
|
|
52
|
-
// Verify intent integrity
|
|
53
|
-
const verification = verifyIntentIntegrity(intent);
|
|
54
|
-
if (!verification.valid) {
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: `INTENT_INTEGRITY_FAILED: ${verification.reason}`,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Archive current intent if exists
|
|
62
|
-
const current = this.getCurrent();
|
|
63
|
-
if (current && current.hash !== intent.hash) {
|
|
64
|
-
this.archive(current);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Write new current intent
|
|
68
|
-
const data = {
|
|
69
|
-
intent,
|
|
70
|
-
stored_at: new Date().toISOString(),
|
|
71
|
-
verified: true,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
fs.writeFileSync(this.currentIntentPath, JSON.stringify(data, null, 2));
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
success: true,
|
|
78
|
-
hash: intent.hash,
|
|
79
|
-
path: this.currentIntentPath,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get current intent (returns blocking intent if none exists)
|
|
85
|
-
* @param {Object} options - Options
|
|
86
|
-
* @param {boolean} options.allowMissing - Don't return blocking intent if missing
|
|
87
|
-
* @returns {Object|null} Current intent or blocking intent
|
|
88
|
-
*/
|
|
89
|
-
getCurrent(options = {}) {
|
|
90
|
-
if (!fs.existsSync(this.currentIntentPath)) {
|
|
91
|
-
if (options.allowMissing) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
// Return blocking intent when no intent declared
|
|
95
|
-
return createBlockingIntent();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const data = JSON.parse(fs.readFileSync(this.currentIntentPath, "utf-8"));
|
|
100
|
-
const intent = data.intent;
|
|
101
|
-
|
|
102
|
-
// Verify integrity on load
|
|
103
|
-
const verification = verifyIntentIntegrity(intent);
|
|
104
|
-
if (!verification.valid) {
|
|
105
|
-
console.error(`[IntentStore] INTEGRITY VIOLATION: ${verification.reason}`);
|
|
106
|
-
// Return blocking intent on integrity failure
|
|
107
|
-
return createBlockingIntent();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return intent;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error(`[IntentStore] Error loading intent: ${error.message}`);
|
|
113
|
-
if (options.allowMissing) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
return createBlockingIntent();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if an intent is currently declared
|
|
122
|
-
* @returns {boolean} True if intent exists
|
|
123
|
-
*/
|
|
124
|
-
hasIntent() {
|
|
125
|
-
return fs.existsSync(this.currentIntentPath);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Archive an intent to history
|
|
130
|
-
* @param {Object} intent - Intent to archive
|
|
131
|
-
*/
|
|
132
|
-
archive(intent) {
|
|
133
|
-
this.ensureDirectories();
|
|
134
|
-
|
|
135
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
136
|
-
const archivePath = path.join(this.historyDir, `${timestamp}_${intent.hash.slice(0, 8)}.json`);
|
|
137
|
-
|
|
138
|
-
const data = {
|
|
139
|
-
intent,
|
|
140
|
-
archived_at: new Date().toISOString(),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
fs.writeFileSync(archivePath, JSON.stringify(data, null, 2));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get intent history
|
|
148
|
-
* @param {number} limit - Maximum number of intents to return
|
|
149
|
-
* @returns {Object[]} Array of historical intents
|
|
150
|
-
*/
|
|
151
|
-
getHistory(limit = 10) {
|
|
152
|
-
if (!fs.existsSync(this.historyDir)) {
|
|
153
|
-
return [];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const files = fs.readdirSync(this.historyDir)
|
|
157
|
-
.filter(f => f.endsWith(".json"))
|
|
158
|
-
.sort()
|
|
159
|
-
.reverse()
|
|
160
|
-
.slice(0, limit);
|
|
161
|
-
|
|
162
|
-
return files.map(file => {
|
|
163
|
-
try {
|
|
164
|
-
const data = JSON.parse(fs.readFileSync(path.join(this.historyDir, file), "utf-8"));
|
|
165
|
-
return {
|
|
166
|
-
...data.intent,
|
|
167
|
-
archived_at: data.archived_at,
|
|
168
|
-
};
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}).filter(Boolean);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Clear current intent
|
|
177
|
-
* @returns {boolean} Success
|
|
178
|
-
*/
|
|
179
|
-
clear() {
|
|
180
|
-
const current = this.getCurrent({ allowMissing: true });
|
|
181
|
-
if (current) {
|
|
182
|
-
this.archive(current);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (fs.existsSync(this.currentIntentPath)) {
|
|
186
|
-
fs.unlinkSync(this.currentIntentPath);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get intent by hash (from history)
|
|
194
|
-
* @param {string} hash - Intent hash
|
|
195
|
-
* @returns {Object|null} Intent or null
|
|
196
|
-
*/
|
|
197
|
-
getByHash(hash) {
|
|
198
|
-
// Check current
|
|
199
|
-
const current = this.getCurrent({ allowMissing: true });
|
|
200
|
-
if (current && current.hash === hash) {
|
|
201
|
-
return current;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Check history
|
|
205
|
-
if (!fs.existsSync(this.historyDir)) {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const files = fs.readdirSync(this.historyDir);
|
|
210
|
-
for (const file of files) {
|
|
211
|
-
if (file.includes(hash.slice(0, 8))) {
|
|
212
|
-
try {
|
|
213
|
-
const data = JSON.parse(fs.readFileSync(path.join(this.historyDir, file), "utf-8"));
|
|
214
|
-
if (data.intent.hash === hash) {
|
|
215
|
-
return data.intent;
|
|
216
|
-
}
|
|
217
|
-
} catch {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Session-scoped intent tracking
|
|
229
|
-
*/
|
|
230
|
-
class SessionIntentTracker {
|
|
231
|
-
constructor() {
|
|
232
|
-
this.sessions = new Map();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Set intent for a session
|
|
237
|
-
* @param {string} sessionId - Session identifier
|
|
238
|
-
* @param {Object} intent - Intent object
|
|
239
|
-
*/
|
|
240
|
-
setIntent(sessionId, intent) {
|
|
241
|
-
this.sessions.set(sessionId, {
|
|
242
|
-
intent,
|
|
243
|
-
set_at: new Date().toISOString(),
|
|
244
|
-
locked: true, // Immutable by default
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Get intent for a session
|
|
250
|
-
* @param {string} sessionId - Session identifier
|
|
251
|
-
* @returns {Object|null} Intent or null
|
|
252
|
-
*/
|
|
253
|
-
getIntent(sessionId) {
|
|
254
|
-
const session = this.sessions.get(sessionId);
|
|
255
|
-
return session?.intent || null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Check if session has intent
|
|
260
|
-
* @param {string} sessionId - Session identifier
|
|
261
|
-
* @returns {boolean} True if intent exists
|
|
262
|
-
*/
|
|
263
|
-
hasIntent(sessionId) {
|
|
264
|
-
return this.sessions.has(sessionId);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Clear session intent
|
|
269
|
-
* @param {string} sessionId - Session identifier
|
|
270
|
-
*/
|
|
271
|
-
clearIntent(sessionId) {
|
|
272
|
-
this.sessions.delete(sessionId);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Global session tracker
|
|
277
|
-
const globalSessionTracker = new SessionIntentTracker();
|
|
278
|
-
|
|
279
|
-
module.exports = {
|
|
280
|
-
IntentStore,
|
|
281
|
-
SessionIntentTracker,
|
|
282
|
-
globalSessionTracker,
|
|
283
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Intent Store - Persistent Intent Storage
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* AGENT FIREWALL™ - INTENT PERSISTENCE
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Stores intent declarations for session and audit purposes.
|
|
9
|
+
* Intent is immutable once stored - updates create new versions.
|
|
10
|
+
*
|
|
11
|
+
* @module intent/store
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const { verifyIntentIntegrity, createBlockingIntent } = require("./schema");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Intent Store - manages intent persistence
|
|
23
|
+
*/
|
|
24
|
+
class IntentStore {
|
|
25
|
+
constructor(projectRoot) {
|
|
26
|
+
this.projectRoot = projectRoot;
|
|
27
|
+
this.intentDir = path.join(projectRoot, ".vibecheck", "intents");
|
|
28
|
+
this.currentIntentPath = path.join(this.intentDir, "current.json");
|
|
29
|
+
this.historyDir = path.join(this.intentDir, "history");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ensure storage directories exist
|
|
34
|
+
*/
|
|
35
|
+
ensureDirectories() {
|
|
36
|
+
if (!fs.existsSync(this.intentDir)) {
|
|
37
|
+
fs.mkdirSync(this.intentDir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
if (!fs.existsSync(this.historyDir)) {
|
|
40
|
+
fs.mkdirSync(this.historyDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Store a new intent (or update)
|
|
46
|
+
* @param {Object} intent - Intent to store
|
|
47
|
+
* @returns {Object} Storage result
|
|
48
|
+
*/
|
|
49
|
+
store(intent) {
|
|
50
|
+
this.ensureDirectories();
|
|
51
|
+
|
|
52
|
+
// Verify intent integrity
|
|
53
|
+
const verification = verifyIntentIntegrity(intent);
|
|
54
|
+
if (!verification.valid) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: `INTENT_INTEGRITY_FAILED: ${verification.reason}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Archive current intent if exists (use allowMissing to avoid archiving blocking intent)
|
|
62
|
+
const current = this.getCurrent({ allowMissing: true });
|
|
63
|
+
if (current && current.hash !== intent.hash) {
|
|
64
|
+
this.archive(current);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Write new current intent
|
|
68
|
+
const data = {
|
|
69
|
+
intent,
|
|
70
|
+
stored_at: new Date().toISOString(),
|
|
71
|
+
verified: true,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(this.currentIntentPath, JSON.stringify(data, null, 2));
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
hash: intent.hash,
|
|
79
|
+
path: this.currentIntentPath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get current intent (returns blocking intent if none exists)
|
|
85
|
+
* @param {Object} options - Options
|
|
86
|
+
* @param {boolean} options.allowMissing - Don't return blocking intent if missing
|
|
87
|
+
* @returns {Object|null} Current intent or blocking intent
|
|
88
|
+
*/
|
|
89
|
+
getCurrent(options = {}) {
|
|
90
|
+
if (!fs.existsSync(this.currentIntentPath)) {
|
|
91
|
+
if (options.allowMissing) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
// Return blocking intent when no intent declared
|
|
95
|
+
return createBlockingIntent();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const data = JSON.parse(fs.readFileSync(this.currentIntentPath, "utf-8"));
|
|
100
|
+
const intent = data.intent;
|
|
101
|
+
|
|
102
|
+
// Verify integrity on load
|
|
103
|
+
const verification = verifyIntentIntegrity(intent);
|
|
104
|
+
if (!verification.valid) {
|
|
105
|
+
console.error(`[IntentStore] INTEGRITY VIOLATION: ${verification.reason}`);
|
|
106
|
+
// Return blocking intent on integrity failure
|
|
107
|
+
return createBlockingIntent();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return intent;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`[IntentStore] Error loading intent: ${error.message}`);
|
|
113
|
+
if (options.allowMissing) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return createBlockingIntent();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if an intent is currently declared
|
|
122
|
+
* @returns {boolean} True if intent exists
|
|
123
|
+
*/
|
|
124
|
+
hasIntent() {
|
|
125
|
+
return fs.existsSync(this.currentIntentPath);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Archive an intent to history
|
|
130
|
+
* @param {Object} intent - Intent to archive
|
|
131
|
+
*/
|
|
132
|
+
archive(intent) {
|
|
133
|
+
this.ensureDirectories();
|
|
134
|
+
|
|
135
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
136
|
+
const archivePath = path.join(this.historyDir, `${timestamp}_${intent.hash.slice(0, 8)}.json`);
|
|
137
|
+
|
|
138
|
+
const data = {
|
|
139
|
+
intent,
|
|
140
|
+
archived_at: new Date().toISOString(),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
fs.writeFileSync(archivePath, JSON.stringify(data, null, 2));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get intent history
|
|
148
|
+
* @param {number} limit - Maximum number of intents to return
|
|
149
|
+
* @returns {Object[]} Array of historical intents
|
|
150
|
+
*/
|
|
151
|
+
getHistory(limit = 10) {
|
|
152
|
+
if (!fs.existsSync(this.historyDir)) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const files = fs.readdirSync(this.historyDir)
|
|
157
|
+
.filter(f => f.endsWith(".json"))
|
|
158
|
+
.sort()
|
|
159
|
+
.reverse()
|
|
160
|
+
.slice(0, limit);
|
|
161
|
+
|
|
162
|
+
return files.map(file => {
|
|
163
|
+
try {
|
|
164
|
+
const data = JSON.parse(fs.readFileSync(path.join(this.historyDir, file), "utf-8"));
|
|
165
|
+
return {
|
|
166
|
+
...data.intent,
|
|
167
|
+
archived_at: data.archived_at,
|
|
168
|
+
};
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}).filter(Boolean);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Clear current intent
|
|
177
|
+
* @returns {boolean} Success
|
|
178
|
+
*/
|
|
179
|
+
clear() {
|
|
180
|
+
const current = this.getCurrent({ allowMissing: true });
|
|
181
|
+
if (current) {
|
|
182
|
+
this.archive(current);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (fs.existsSync(this.currentIntentPath)) {
|
|
186
|
+
fs.unlinkSync(this.currentIntentPath);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get intent by hash (from history)
|
|
194
|
+
* @param {string} hash - Intent hash
|
|
195
|
+
* @returns {Object|null} Intent or null
|
|
196
|
+
*/
|
|
197
|
+
getByHash(hash) {
|
|
198
|
+
// Check current
|
|
199
|
+
const current = this.getCurrent({ allowMissing: true });
|
|
200
|
+
if (current && current.hash === hash) {
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check history
|
|
205
|
+
if (!fs.existsSync(this.historyDir)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const files = fs.readdirSync(this.historyDir);
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
if (file.includes(hash.slice(0, 8))) {
|
|
212
|
+
try {
|
|
213
|
+
const data = JSON.parse(fs.readFileSync(path.join(this.historyDir, file), "utf-8"));
|
|
214
|
+
if (data.intent.hash === hash) {
|
|
215
|
+
return data.intent;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Session-scoped intent tracking
|
|
229
|
+
*/
|
|
230
|
+
class SessionIntentTracker {
|
|
231
|
+
constructor() {
|
|
232
|
+
this.sessions = new Map();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set intent for a session
|
|
237
|
+
* @param {string} sessionId - Session identifier
|
|
238
|
+
* @param {Object} intent - Intent object
|
|
239
|
+
*/
|
|
240
|
+
setIntent(sessionId, intent) {
|
|
241
|
+
this.sessions.set(sessionId, {
|
|
242
|
+
intent,
|
|
243
|
+
set_at: new Date().toISOString(),
|
|
244
|
+
locked: true, // Immutable by default
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get intent for a session
|
|
250
|
+
* @param {string} sessionId - Session identifier
|
|
251
|
+
* @returns {Object|null} Intent or null
|
|
252
|
+
*/
|
|
253
|
+
getIntent(sessionId) {
|
|
254
|
+
const session = this.sessions.get(sessionId);
|
|
255
|
+
return session?.intent || null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if session has intent
|
|
260
|
+
* @param {string} sessionId - Session identifier
|
|
261
|
+
* @returns {boolean} True if intent exists
|
|
262
|
+
*/
|
|
263
|
+
hasIntent(sessionId) {
|
|
264
|
+
return this.sessions.has(sessionId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Clear session intent
|
|
269
|
+
* @param {string} sessionId - Session identifier
|
|
270
|
+
*/
|
|
271
|
+
clearIntent(sessionId) {
|
|
272
|
+
this.sessions.delete(sessionId);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Global session tracker
|
|
277
|
+
const globalSessionTracker = new SessionIntentTracker();
|
|
278
|
+
|
|
279
|
+
module.exports = {
|
|
280
|
+
IntentStore,
|
|
281
|
+
SessionIntentTracker,
|
|
282
|
+
globalSessionTracker,
|
|
283
|
+
};
|
|
@@ -114,13 +114,17 @@ async function interceptFileWrite({
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Return interception result
|
|
117
|
+
// In observe mode, always allow but still report violations
|
|
118
|
+
const isObserveMode = policy.mode === "observe";
|
|
117
119
|
return {
|
|
118
|
-
allowed: verdict.decision === "ALLOW",
|
|
119
|
-
verdict: verdict.decision,
|
|
120
|
+
allowed: isObserveMode || verdict.decision === "ALLOW",
|
|
121
|
+
verdict: isObserveMode && verdict.decision !== "ALLOW" ? "ALLOW" : verdict.decision,
|
|
120
122
|
violations: verdict.violations,
|
|
121
123
|
unblockPlan,
|
|
122
124
|
packetId: packet.id,
|
|
123
|
-
message: verdict.
|
|
125
|
+
message: isObserveMode && verdict.violations?.length > 0
|
|
126
|
+
? `[OBSERVE MODE] Would have blocked: ${verdict.message}`
|
|
127
|
+
: verdict.message
|
|
124
128
|
};
|
|
125
129
|
}
|
|
126
130
|
|