@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,352 +1,352 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Intent Schema v2 - Enforcement-Grade Intent Declaration
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* AGENT FIREWALL™ - INTENT DECLARATION SYSTEM
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* Intent is the foundation of the Agent Firewall enforcement model.
|
|
9
|
-
* All AI actions MUST be checked against declared intent.
|
|
10
|
-
*
|
|
11
|
-
* Properties:
|
|
12
|
-
* - Intent is explicit, human-written, short, and structured
|
|
13
|
-
* - Intent is captured BEFORE any AI code generation
|
|
14
|
-
* - Intent is IMMUTABLE during a session unless explicitly updated
|
|
15
|
-
* - If intent is missing → Agent Firewall defaults to BLOCK
|
|
16
|
-
*
|
|
17
|
-
* @module intent/schema
|
|
18
|
-
* @version 2.0.0
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
"use strict";
|
|
22
|
-
|
|
23
|
-
const crypto = require("crypto");
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Intent Declaration JSON Schema
|
|
27
|
-
*/
|
|
28
|
-
const INTENT_SCHEMA = {
|
|
29
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
30
|
-
$id: "https://vibecheckai.dev/schemas/intent/v2",
|
|
31
|
-
type: "object",
|
|
32
|
-
required: ["summary", "constraints", "created_at", "hash"],
|
|
33
|
-
properties: {
|
|
34
|
-
// Core required fields
|
|
35
|
-
summary: {
|
|
36
|
-
type: "string",
|
|
37
|
-
description: "Human-written summary of what the change intends to accomplish",
|
|
38
|
-
minLength: 10,
|
|
39
|
-
maxLength: 500,
|
|
40
|
-
},
|
|
41
|
-
constraints: {
|
|
42
|
-
type: "array",
|
|
43
|
-
description: "Explicit constraints that MUST be respected. Violations = BLOCK.",
|
|
44
|
-
items: {
|
|
45
|
-
type: "string",
|
|
46
|
-
minLength: 5,
|
|
47
|
-
},
|
|
48
|
-
minItems: 0,
|
|
49
|
-
},
|
|
50
|
-
allowed_changes: {
|
|
51
|
-
type: "array",
|
|
52
|
-
description: "Explicit list of allowed modifications (files, routes, env vars)",
|
|
53
|
-
items: {
|
|
54
|
-
type: "object",
|
|
55
|
-
required: ["type", "target"],
|
|
56
|
-
properties: {
|
|
57
|
-
type: {
|
|
58
|
-
type: "string",
|
|
59
|
-
enum: ["file_create", "file_modify", "file_delete", "route_add", "route_modify", "env_add", "permission_modify", "config_change"],
|
|
60
|
-
description: "Type of allowed change",
|
|
61
|
-
},
|
|
62
|
-
target: {
|
|
63
|
-
type: "string",
|
|
64
|
-
description: "Target of the change (file path, route pattern, env var name)",
|
|
65
|
-
},
|
|
66
|
-
pattern: {
|
|
67
|
-
type: "string",
|
|
68
|
-
description: "Glob pattern for matching multiple targets",
|
|
69
|
-
},
|
|
70
|
-
reason: {
|
|
71
|
-
type: "string",
|
|
72
|
-
description: "Why this change is allowed",
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
created_at: {
|
|
78
|
-
type: "string",
|
|
79
|
-
format: "date-time",
|
|
80
|
-
description: "ISO timestamp when intent was declared",
|
|
81
|
-
},
|
|
82
|
-
hash: {
|
|
83
|
-
type: "string",
|
|
84
|
-
pattern: "^[a-f0-9]{64}$",
|
|
85
|
-
description: "SHA-256 hash of intent content for immutability verification",
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Optional metadata
|
|
89
|
-
session_id: {
|
|
90
|
-
type: "string",
|
|
91
|
-
description: "Session identifier for tracking",
|
|
92
|
-
},
|
|
93
|
-
author: {
|
|
94
|
-
type: "string",
|
|
95
|
-
description: "Who declared the intent (user identifier)",
|
|
96
|
-
},
|
|
97
|
-
version: {
|
|
98
|
-
type: "number",
|
|
99
|
-
description: "Intent version (increments on explicit update)",
|
|
100
|
-
minimum: 1,
|
|
101
|
-
},
|
|
102
|
-
parent_hash: {
|
|
103
|
-
type: "string",
|
|
104
|
-
description: "Hash of parent intent if this is an update",
|
|
105
|
-
},
|
|
106
|
-
expires_at: {
|
|
107
|
-
type: "string",
|
|
108
|
-
format: "date-time",
|
|
109
|
-
description: "Optional expiration time for the intent",
|
|
110
|
-
},
|
|
111
|
-
scope: {
|
|
112
|
-
type: "object",
|
|
113
|
-
description: "Scope restrictions for the intent",
|
|
114
|
-
properties: {
|
|
115
|
-
directories: {
|
|
116
|
-
type: "array",
|
|
117
|
-
items: { type: "string" },
|
|
118
|
-
description: "Allowed directories for changes",
|
|
119
|
-
},
|
|
120
|
-
file_patterns: {
|
|
121
|
-
type: "array",
|
|
122
|
-
items: { type: "string" },
|
|
123
|
-
description: "Allowed file glob patterns",
|
|
124
|
-
},
|
|
125
|
-
domains: {
|
|
126
|
-
type: "array",
|
|
127
|
-
items: {
|
|
128
|
-
type: "string",
|
|
129
|
-
enum: ["auth", "payments", "routes", "contracts", "ui", "database", "config", "general"],
|
|
130
|
-
},
|
|
131
|
-
description: "Allowed domains for changes",
|
|
132
|
-
},
|
|
133
|
-
excluded_paths: {
|
|
134
|
-
type: "array",
|
|
135
|
-
items: { type: "string" },
|
|
136
|
-
description: "Explicitly excluded paths",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
additionalProperties: false,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Compute content hash for intent immutability verification
|
|
146
|
-
* @param {Object} intent - Intent object (without hash)
|
|
147
|
-
* @returns {string} SHA-256 hash
|
|
148
|
-
*/
|
|
149
|
-
function computeIntentHash(intent) {
|
|
150
|
-
// Normalize intent for consistent hashing
|
|
151
|
-
const normalized = {
|
|
152
|
-
summary: intent.summary?.trim() || "",
|
|
153
|
-
constraints: (intent.constraints || []).map(c => c.trim()).sort(),
|
|
154
|
-
allowed_changes: (intent.allowed_changes || [])
|
|
155
|
-
.map(c => ({ type: c.type, target: c.target, pattern: c.pattern }))
|
|
156
|
-
.sort((a, b) => `${a.type}:${a.target}`.localeCompare(`${b.type}:${b.target}`)),
|
|
157
|
-
created_at: intent.created_at,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const content = JSON.stringify(normalized, null, 0);
|
|
161
|
-
return crypto.createHash("sha256").update(content).digest("hex");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Create a new intent declaration
|
|
166
|
-
* @param {Object} params - Intent parameters
|
|
167
|
-
* @param {string} params.summary - Human-readable summary
|
|
168
|
-
* @param {string[]} params.constraints - Constraints to enforce
|
|
169
|
-
* @param {Object[]} params.allowed_changes - Explicitly allowed changes
|
|
170
|
-
* @param {Object} params.scope - Scope restrictions
|
|
171
|
-
* @param {string} params.author - Who declared the intent
|
|
172
|
-
* @param {string} params.session_id - Session identifier
|
|
173
|
-
* @returns {Object} Complete intent object with hash
|
|
174
|
-
*/
|
|
175
|
-
function createIntent({
|
|
176
|
-
summary,
|
|
177
|
-
constraints = [],
|
|
178
|
-
allowed_changes = [],
|
|
179
|
-
scope = null,
|
|
180
|
-
author = null,
|
|
181
|
-
session_id = null,
|
|
182
|
-
}) {
|
|
183
|
-
const created_at = new Date().toISOString();
|
|
184
|
-
|
|
185
|
-
const intent = {
|
|
186
|
-
summary: summary?.trim(),
|
|
187
|
-
constraints: constraints.map(c => c.trim()).filter(Boolean),
|
|
188
|
-
allowed_changes,
|
|
189
|
-
created_at,
|
|
190
|
-
version: 1,
|
|
191
|
-
hash: "", // Placeholder
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Add optional fields
|
|
195
|
-
if (scope) intent.scope = scope;
|
|
196
|
-
if (author) intent.author = author;
|
|
197
|
-
if (session_id) intent.session_id = session_id;
|
|
198
|
-
|
|
199
|
-
// Compute and set hash
|
|
200
|
-
intent.hash = computeIntentHash(intent);
|
|
201
|
-
|
|
202
|
-
return intent;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Verify intent has not been tampered with
|
|
207
|
-
* @param {Object} intent - Intent to verify
|
|
208
|
-
* @returns {Object} Verification result { valid, computed_hash, stored_hash }
|
|
209
|
-
*/
|
|
210
|
-
function verifyIntentIntegrity(intent) {
|
|
211
|
-
if (!intent || !intent.hash) {
|
|
212
|
-
return {
|
|
213
|
-
valid: false,
|
|
214
|
-
reason: "MISSING_HASH",
|
|
215
|
-
computed_hash: null,
|
|
216
|
-
stored_hash: null,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const computed = computeIntentHash(intent);
|
|
221
|
-
const stored = intent.hash;
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
valid: computed === stored,
|
|
225
|
-
reason: computed === stored ? "VERIFIED" : "HASH_MISMATCH",
|
|
226
|
-
computed_hash: computed,
|
|
227
|
-
stored_hash: stored,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Update an existing intent (creates new version with parent reference)
|
|
233
|
-
* @param {Object} currentIntent - Current intent
|
|
234
|
-
* @param {Object} updates - Fields to update
|
|
235
|
-
* @returns {Object} New intent with incremented version
|
|
236
|
-
*/
|
|
237
|
-
function updateIntent(currentIntent, updates) {
|
|
238
|
-
const parent_hash = currentIntent.hash;
|
|
239
|
-
const version = (currentIntent.version || 1) + 1;
|
|
240
|
-
|
|
241
|
-
const newIntent = {
|
|
242
|
-
...currentIntent,
|
|
243
|
-
...updates,
|
|
244
|
-
created_at: new Date().toISOString(),
|
|
245
|
-
version,
|
|
246
|
-
parent_hash,
|
|
247
|
-
hash: "", // Will be recomputed
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// Ensure constraints are clean
|
|
251
|
-
if (newIntent.constraints) {
|
|
252
|
-
newIntent.constraints = newIntent.constraints.map(c => c.trim()).filter(Boolean);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Recompute hash
|
|
256
|
-
newIntent.hash = computeIntentHash(newIntent);
|
|
257
|
-
|
|
258
|
-
return newIntent;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Check if intent is expired
|
|
263
|
-
* @param {Object} intent - Intent to check
|
|
264
|
-
* @returns {boolean} True if expired
|
|
265
|
-
*/
|
|
266
|
-
function isIntentExpired(intent) {
|
|
267
|
-
if (!intent.expires_at) return false;
|
|
268
|
-
return new Date(intent.expires_at) < new Date();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Create a minimal blocking intent (for when no intent is declared)
|
|
273
|
-
* This intent blocks ALL changes by having no allowed_changes.
|
|
274
|
-
* @returns {Object} Blocking intent
|
|
275
|
-
*/
|
|
276
|
-
function createBlockingIntent() {
|
|
277
|
-
return createIntent({
|
|
278
|
-
summary: "NO INTENT DECLARED - ALL CHANGES BLOCKED BY DEFAULT",
|
|
279
|
-
constraints: [
|
|
280
|
-
"No changes allowed without explicit intent declaration",
|
|
281
|
-
"All file operations blocked",
|
|
282
|
-
"All route additions blocked",
|
|
283
|
-
"All env var references blocked",
|
|
284
|
-
],
|
|
285
|
-
allowed_changes: [],
|
|
286
|
-
scope: {
|
|
287
|
-
directories: [],
|
|
288
|
-
file_patterns: [],
|
|
289
|
-
domains: [],
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Intent constraint types for enforcement
|
|
296
|
-
*/
|
|
297
|
-
const CONSTRAINT_TYPES = {
|
|
298
|
-
NO_NEW_ROUTES: "no_new_routes",
|
|
299
|
-
NO_AUTH_CHANGES: "no_auth_changes",
|
|
300
|
-
NO_PAYMENT_CHANGES: "no_payment_changes",
|
|
301
|
-
NO_DATABASE_MIGRATIONS: "no_database_migrations",
|
|
302
|
-
NO_ENV_ADDITIONS: "no_env_additions",
|
|
303
|
-
NO_PERMISSION_CHANGES: "no_permission_changes",
|
|
304
|
-
NO_EXTERNAL_CALLS: "no_external_calls",
|
|
305
|
-
NO_FILE_DELETIONS: "no_file_deletions",
|
|
306
|
-
SINGLE_FILE_ONLY: "single_file_only",
|
|
307
|
-
TESTS_REQUIRED: "tests_required",
|
|
308
|
-
REVIEW_REQUIRED: "review_required",
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Pre-built constraint templates
|
|
313
|
-
*/
|
|
314
|
-
const CONSTRAINT_TEMPLATES = {
|
|
315
|
-
STRICT_BUGFIX: [
|
|
316
|
-
"No new routes allowed",
|
|
317
|
-
"No auth logic changes",
|
|
318
|
-
"No new environment variables",
|
|
319
|
-
"Changes limited to specified file(s)",
|
|
320
|
-
"No new dependencies",
|
|
321
|
-
],
|
|
322
|
-
FEATURE_ADDITION: [
|
|
323
|
-
"New routes must be documented in intent",
|
|
324
|
-
"No changes to existing auth logic",
|
|
325
|
-
"New env vars must be declared",
|
|
326
|
-
"Tests required for new functionality",
|
|
327
|
-
],
|
|
328
|
-
REFACTOR: [
|
|
329
|
-
"No behavior changes",
|
|
330
|
-
"No new routes",
|
|
331
|
-
"No API contract changes",
|
|
332
|
-
"No auth boundary changes",
|
|
333
|
-
],
|
|
334
|
-
SECURITY_PATCH: [
|
|
335
|
-
"Auth changes must be explicit in intent",
|
|
336
|
-
"No new external endpoints",
|
|
337
|
-
"No permission relaxation",
|
|
338
|
-
"Review required before ship",
|
|
339
|
-
],
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
module.exports = {
|
|
343
|
-
INTENT_SCHEMA,
|
|
344
|
-
createIntent,
|
|
345
|
-
computeIntentHash,
|
|
346
|
-
verifyIntentIntegrity,
|
|
347
|
-
updateIntent,
|
|
348
|
-
isIntentExpired,
|
|
349
|
-
createBlockingIntent,
|
|
350
|
-
CONSTRAINT_TYPES,
|
|
351
|
-
CONSTRAINT_TEMPLATES,
|
|
352
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Intent Schema v2 - Enforcement-Grade Intent Declaration
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* AGENT FIREWALL™ - INTENT DECLARATION SYSTEM
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Intent is the foundation of the Agent Firewall enforcement model.
|
|
9
|
+
* All AI actions MUST be checked against declared intent.
|
|
10
|
+
*
|
|
11
|
+
* Properties:
|
|
12
|
+
* - Intent is explicit, human-written, short, and structured
|
|
13
|
+
* - Intent is captured BEFORE any AI code generation
|
|
14
|
+
* - Intent is IMMUTABLE during a session unless explicitly updated
|
|
15
|
+
* - If intent is missing → Agent Firewall defaults to BLOCK
|
|
16
|
+
*
|
|
17
|
+
* @module intent/schema
|
|
18
|
+
* @version 2.0.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
"use strict";
|
|
22
|
+
|
|
23
|
+
const crypto = require("crypto");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Intent Declaration JSON Schema
|
|
27
|
+
*/
|
|
28
|
+
const INTENT_SCHEMA = {
|
|
29
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
30
|
+
$id: "https://vibecheckai.dev/schemas/intent/v2",
|
|
31
|
+
type: "object",
|
|
32
|
+
required: ["summary", "constraints", "created_at", "hash"],
|
|
33
|
+
properties: {
|
|
34
|
+
// Core required fields
|
|
35
|
+
summary: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Human-written summary of what the change intends to accomplish",
|
|
38
|
+
minLength: 10,
|
|
39
|
+
maxLength: 500,
|
|
40
|
+
},
|
|
41
|
+
constraints: {
|
|
42
|
+
type: "array",
|
|
43
|
+
description: "Explicit constraints that MUST be respected. Violations = BLOCK.",
|
|
44
|
+
items: {
|
|
45
|
+
type: "string",
|
|
46
|
+
minLength: 5,
|
|
47
|
+
},
|
|
48
|
+
minItems: 0,
|
|
49
|
+
},
|
|
50
|
+
allowed_changes: {
|
|
51
|
+
type: "array",
|
|
52
|
+
description: "Explicit list of allowed modifications (files, routes, env vars)",
|
|
53
|
+
items: {
|
|
54
|
+
type: "object",
|
|
55
|
+
required: ["type", "target"],
|
|
56
|
+
properties: {
|
|
57
|
+
type: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: ["file_create", "file_modify", "file_delete", "route_add", "route_modify", "env_add", "permission_modify", "config_change"],
|
|
60
|
+
description: "Type of allowed change",
|
|
61
|
+
},
|
|
62
|
+
target: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Target of the change (file path, route pattern, env var name)",
|
|
65
|
+
},
|
|
66
|
+
pattern: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Glob pattern for matching multiple targets",
|
|
69
|
+
},
|
|
70
|
+
reason: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Why this change is allowed",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
created_at: {
|
|
78
|
+
type: "string",
|
|
79
|
+
format: "date-time",
|
|
80
|
+
description: "ISO timestamp when intent was declared",
|
|
81
|
+
},
|
|
82
|
+
hash: {
|
|
83
|
+
type: "string",
|
|
84
|
+
pattern: "^[a-f0-9]{64}$",
|
|
85
|
+
description: "SHA-256 hash of intent content for immutability verification",
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Optional metadata
|
|
89
|
+
session_id: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Session identifier for tracking",
|
|
92
|
+
},
|
|
93
|
+
author: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Who declared the intent (user identifier)",
|
|
96
|
+
},
|
|
97
|
+
version: {
|
|
98
|
+
type: "number",
|
|
99
|
+
description: "Intent version (increments on explicit update)",
|
|
100
|
+
minimum: 1,
|
|
101
|
+
},
|
|
102
|
+
parent_hash: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Hash of parent intent if this is an update",
|
|
105
|
+
},
|
|
106
|
+
expires_at: {
|
|
107
|
+
type: "string",
|
|
108
|
+
format: "date-time",
|
|
109
|
+
description: "Optional expiration time for the intent",
|
|
110
|
+
},
|
|
111
|
+
scope: {
|
|
112
|
+
type: "object",
|
|
113
|
+
description: "Scope restrictions for the intent",
|
|
114
|
+
properties: {
|
|
115
|
+
directories: {
|
|
116
|
+
type: "array",
|
|
117
|
+
items: { type: "string" },
|
|
118
|
+
description: "Allowed directories for changes",
|
|
119
|
+
},
|
|
120
|
+
file_patterns: {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "string" },
|
|
123
|
+
description: "Allowed file glob patterns",
|
|
124
|
+
},
|
|
125
|
+
domains: {
|
|
126
|
+
type: "array",
|
|
127
|
+
items: {
|
|
128
|
+
type: "string",
|
|
129
|
+
enum: ["auth", "payments", "routes", "contracts", "ui", "database", "config", "general"],
|
|
130
|
+
},
|
|
131
|
+
description: "Allowed domains for changes",
|
|
132
|
+
},
|
|
133
|
+
excluded_paths: {
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "string" },
|
|
136
|
+
description: "Explicitly excluded paths",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Compute content hash for intent immutability verification
|
|
146
|
+
* @param {Object} intent - Intent object (without hash)
|
|
147
|
+
* @returns {string} SHA-256 hash
|
|
148
|
+
*/
|
|
149
|
+
function computeIntentHash(intent) {
|
|
150
|
+
// Normalize intent for consistent hashing
|
|
151
|
+
const normalized = {
|
|
152
|
+
summary: intent.summary?.trim() || "",
|
|
153
|
+
constraints: (intent.constraints || []).map(c => c.trim()).sort(),
|
|
154
|
+
allowed_changes: (intent.allowed_changes || [])
|
|
155
|
+
.map(c => ({ type: c.type, target: c.target, pattern: c.pattern }))
|
|
156
|
+
.sort((a, b) => `${a.type}:${a.target}`.localeCompare(`${b.type}:${b.target}`)),
|
|
157
|
+
created_at: intent.created_at,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const content = JSON.stringify(normalized, null, 0);
|
|
161
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a new intent declaration
|
|
166
|
+
* @param {Object} params - Intent parameters
|
|
167
|
+
* @param {string} params.summary - Human-readable summary
|
|
168
|
+
* @param {string[]} params.constraints - Constraints to enforce
|
|
169
|
+
* @param {Object[]} params.allowed_changes - Explicitly allowed changes
|
|
170
|
+
* @param {Object} params.scope - Scope restrictions
|
|
171
|
+
* @param {string} params.author - Who declared the intent
|
|
172
|
+
* @param {string} params.session_id - Session identifier
|
|
173
|
+
* @returns {Object} Complete intent object with hash
|
|
174
|
+
*/
|
|
175
|
+
function createIntent({
|
|
176
|
+
summary,
|
|
177
|
+
constraints = [],
|
|
178
|
+
allowed_changes = [],
|
|
179
|
+
scope = null,
|
|
180
|
+
author = null,
|
|
181
|
+
session_id = null,
|
|
182
|
+
}) {
|
|
183
|
+
const created_at = new Date().toISOString();
|
|
184
|
+
|
|
185
|
+
const intent = {
|
|
186
|
+
summary: summary?.trim(),
|
|
187
|
+
constraints: constraints.map(c => c.trim()).filter(Boolean),
|
|
188
|
+
allowed_changes,
|
|
189
|
+
created_at,
|
|
190
|
+
version: 1,
|
|
191
|
+
hash: "", // Placeholder
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Add optional fields
|
|
195
|
+
if (scope) intent.scope = scope;
|
|
196
|
+
if (author) intent.author = author;
|
|
197
|
+
if (session_id) intent.session_id = session_id;
|
|
198
|
+
|
|
199
|
+
// Compute and set hash
|
|
200
|
+
intent.hash = computeIntentHash(intent);
|
|
201
|
+
|
|
202
|
+
return intent;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Verify intent has not been tampered with
|
|
207
|
+
* @param {Object} intent - Intent to verify
|
|
208
|
+
* @returns {Object} Verification result { valid, computed_hash, stored_hash }
|
|
209
|
+
*/
|
|
210
|
+
function verifyIntentIntegrity(intent) {
|
|
211
|
+
if (!intent || !intent.hash) {
|
|
212
|
+
return {
|
|
213
|
+
valid: false,
|
|
214
|
+
reason: "MISSING_HASH",
|
|
215
|
+
computed_hash: null,
|
|
216
|
+
stored_hash: null,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const computed = computeIntentHash(intent);
|
|
221
|
+
const stored = intent.hash;
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
valid: computed === stored,
|
|
225
|
+
reason: computed === stored ? "VERIFIED" : "HASH_MISMATCH",
|
|
226
|
+
computed_hash: computed,
|
|
227
|
+
stored_hash: stored,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Update an existing intent (creates new version with parent reference)
|
|
233
|
+
* @param {Object} currentIntent - Current intent
|
|
234
|
+
* @param {Object} updates - Fields to update
|
|
235
|
+
* @returns {Object} New intent with incremented version
|
|
236
|
+
*/
|
|
237
|
+
function updateIntent(currentIntent, updates) {
|
|
238
|
+
const parent_hash = currentIntent.hash;
|
|
239
|
+
const version = (currentIntent.version || 1) + 1;
|
|
240
|
+
|
|
241
|
+
const newIntent = {
|
|
242
|
+
...currentIntent,
|
|
243
|
+
...updates,
|
|
244
|
+
created_at: new Date().toISOString(),
|
|
245
|
+
version,
|
|
246
|
+
parent_hash,
|
|
247
|
+
hash: "", // Will be recomputed
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Ensure constraints are clean
|
|
251
|
+
if (newIntent.constraints) {
|
|
252
|
+
newIntent.constraints = newIntent.constraints.map(c => c.trim()).filter(Boolean);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Recompute hash
|
|
256
|
+
newIntent.hash = computeIntentHash(newIntent);
|
|
257
|
+
|
|
258
|
+
return newIntent;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Check if intent is expired
|
|
263
|
+
* @param {Object} intent - Intent to check
|
|
264
|
+
* @returns {boolean} True if expired
|
|
265
|
+
*/
|
|
266
|
+
function isIntentExpired(intent) {
|
|
267
|
+
if (!intent.expires_at) return false;
|
|
268
|
+
return new Date(intent.expires_at) < new Date();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Create a minimal blocking intent (for when no intent is declared)
|
|
273
|
+
* This intent blocks ALL changes by having no allowed_changes.
|
|
274
|
+
* @returns {Object} Blocking intent
|
|
275
|
+
*/
|
|
276
|
+
function createBlockingIntent() {
|
|
277
|
+
return createIntent({
|
|
278
|
+
summary: "NO INTENT DECLARED - ALL CHANGES BLOCKED BY DEFAULT",
|
|
279
|
+
constraints: [
|
|
280
|
+
"No changes allowed without explicit intent declaration",
|
|
281
|
+
"All file operations blocked",
|
|
282
|
+
"All route additions blocked",
|
|
283
|
+
"All env var references blocked",
|
|
284
|
+
],
|
|
285
|
+
allowed_changes: [],
|
|
286
|
+
scope: {
|
|
287
|
+
directories: [],
|
|
288
|
+
file_patterns: [],
|
|
289
|
+
domains: [],
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Intent constraint types for enforcement
|
|
296
|
+
*/
|
|
297
|
+
const CONSTRAINT_TYPES = {
|
|
298
|
+
NO_NEW_ROUTES: "no_new_routes",
|
|
299
|
+
NO_AUTH_CHANGES: "no_auth_changes",
|
|
300
|
+
NO_PAYMENT_CHANGES: "no_payment_changes",
|
|
301
|
+
NO_DATABASE_MIGRATIONS: "no_database_migrations",
|
|
302
|
+
NO_ENV_ADDITIONS: "no_env_additions",
|
|
303
|
+
NO_PERMISSION_CHANGES: "no_permission_changes",
|
|
304
|
+
NO_EXTERNAL_CALLS: "no_external_calls",
|
|
305
|
+
NO_FILE_DELETIONS: "no_file_deletions",
|
|
306
|
+
SINGLE_FILE_ONLY: "single_file_only",
|
|
307
|
+
TESTS_REQUIRED: "tests_required",
|
|
308
|
+
REVIEW_REQUIRED: "review_required",
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Pre-built constraint templates
|
|
313
|
+
*/
|
|
314
|
+
const CONSTRAINT_TEMPLATES = {
|
|
315
|
+
STRICT_BUGFIX: [
|
|
316
|
+
"No new routes allowed",
|
|
317
|
+
"No auth logic changes",
|
|
318
|
+
"No new environment variables",
|
|
319
|
+
"Changes limited to specified file(s)",
|
|
320
|
+
"No new dependencies",
|
|
321
|
+
],
|
|
322
|
+
FEATURE_ADDITION: [
|
|
323
|
+
"New routes must be documented in intent",
|
|
324
|
+
"No changes to existing auth logic",
|
|
325
|
+
"New env vars must be declared",
|
|
326
|
+
"Tests required for new functionality",
|
|
327
|
+
],
|
|
328
|
+
REFACTOR: [
|
|
329
|
+
"No behavior changes",
|
|
330
|
+
"No new routes",
|
|
331
|
+
"No API contract changes",
|
|
332
|
+
"No auth boundary changes",
|
|
333
|
+
],
|
|
334
|
+
SECURITY_PATCH: [
|
|
335
|
+
"Auth changes must be explicit in intent",
|
|
336
|
+
"No new external endpoints",
|
|
337
|
+
"No permission relaxation",
|
|
338
|
+
"Review required before ship",
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
module.exports = {
|
|
343
|
+
INTENT_SCHEMA,
|
|
344
|
+
createIntent,
|
|
345
|
+
computeIntentHash,
|
|
346
|
+
verifyIntentIntegrity,
|
|
347
|
+
updateIntent,
|
|
348
|
+
isIntentExpired,
|
|
349
|
+
createBlockingIntent,
|
|
350
|
+
CONSTRAINT_TYPES,
|
|
351
|
+
CONSTRAINT_TEMPLATES,
|
|
352
|
+
};
|