@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.
Files changed (34) hide show
  1. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -98
  2. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -318
  3. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -484
  4. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -418
  5. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -333
  6. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -622
  7. package/bin/runners/lib/agent-firewall/intent/index.js +102 -102
  8. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -352
  9. package/bin/runners/lib/agent-firewall/intent/store.js +283 -283
  10. package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
  11. package/bin/runners/lib/engine/ast-cache.js +210 -210
  12. package/bin/runners/lib/engine/auth-extractor.js +211 -211
  13. package/bin/runners/lib/engine/billing-extractor.js +112 -112
  14. package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
  15. package/bin/runners/lib/engine/env-extractor.js +207 -207
  16. package/bin/runners/lib/engine/express-extractor.js +208 -208
  17. package/bin/runners/lib/engine/extractors.js +849 -849
  18. package/bin/runners/lib/engine/index.js +207 -207
  19. package/bin/runners/lib/engine/repo-index.js +514 -514
  20. package/bin/runners/lib/engine/types.js +124 -124
  21. package/bin/runners/runIntent.js +906 -906
  22. package/bin/runners/runPacks.js +2089 -2089
  23. package/bin/runners/runReality.js +178 -1
  24. package/bin/runners/runShield.js +1282 -1282
  25. package/mcp-server/handlers/index.ts +2 -2
  26. package/mcp-server/handlers/tool-handler.ts +47 -8
  27. package/mcp-server/lib/executor.ts +5 -5
  28. package/mcp-server/lib/index.ts +14 -4
  29. package/mcp-server/lib/sandbox.test.ts +4 -4
  30. package/mcp-server/lib/sandbox.ts +2 -2
  31. package/mcp-server/package.json +1 -1
  32. package/mcp-server/registry.test.ts +18 -12
  33. package/mcp-server/tsconfig.json +1 -0
  34. package/package.json +2 -1
@@ -1,622 +1,634 @@
1
- /**
2
- * Intent Alignment Engine - Core Enforcement Logic
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * AGENT FIREWALL™ - INTENT ALIGNMENT ENGINE
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- *
8
- * For every Change Event:
9
- * - Compare change against declared intent
10
- * - Enforce STRICT matching rules
11
- * - BLOCK if intent is violated
12
- *
13
- * This is NOT advisory. This is enforcement.
14
- *
15
- * @module intent/alignment-engine
16
- * @version 2.0.0
17
- */
18
-
19
- "use strict";
20
-
21
- const path = require("path");
22
- const { minimatch } = require("minimatch") || { minimatch: null };
23
-
24
- /**
25
- * Violation codes for machine-readable errors
26
- */
27
- const VIOLATION_CODES = {
28
- NO_INTENT: "NO_INTENT_DECLARED",
29
- INTENT_EXPIRED: "INTENT_EXPIRED",
30
- INTENT_CORRUPTED: "INTENT_INTEGRITY_FAILED",
31
- UNDECLARED_ROUTE: "UNDECLARED_ROUTE",
32
- UNDECLARED_ENV: "UNDECLARED_ENV_VAR",
33
- UNDECLARED_FILE: "UNDECLARED_FILE_CHANGE",
34
- CONSTRAINT_VIOLATED: "CONSTRAINT_VIOLATED",
35
- SCOPE_VIOLATION: "SCOPE_VIOLATION",
36
- DOMAIN_VIOLATION: "DOMAIN_NOT_ALLOWED",
37
- PERMISSION_CHANGE: "UNAUTHORIZED_PERMISSION_CHANGE",
38
- AUTH_MODIFICATION: "UNAUTHORIZED_AUTH_MODIFICATION",
39
- PAYMENT_MODIFICATION: "UNAUTHORIZED_PAYMENT_MODIFICATION",
40
- MOCK_DATA_DETECTED: "MOCK_DATA_IN_PRODUCTION_CODE",
41
- TODO_DETECTED: "UNRESOLVED_TODO_PLACEHOLDER",
42
- FAKE_HANDLER: "FAKE_HANDLER_DETECTED",
43
- UI_WITHOUT_BACKEND: "UI_SUCCESS_WITHOUT_BACKEND_PROOF",
44
- };
45
-
46
- /**
47
- * Alignment Check Result
48
- * @typedef {Object} AlignmentResult
49
- * @property {boolean} aligned - Whether change is aligned with intent
50
- * @property {string} decision - PASS or BLOCK
51
- * @property {Object[]} violations - Array of violations
52
- * @property {string} intent_hash - Hash of intent used for checking
53
- */
54
-
55
- /**
56
- * Violation object structure
57
- * @typedef {Object} Violation
58
- * @property {string} code - Machine-readable violation code
59
- * @property {string} rule - Human-readable rule name
60
- * @property {string} message - Detailed violation message
61
- * @property {string} resource - Resource that caused violation
62
- * @property {string} intent_ref - Reference to violated intent element
63
- * @property {string} severity - Always "block" for violations
64
- */
65
-
66
- /**
67
- * Check if a path matches any allowed pattern
68
- * @param {string} filePath - Path to check
69
- * @param {Object[]} allowed_changes - Allowed changes from intent
70
- * @returns {boolean} True if allowed
71
- */
72
- function isFileChangeAllowed(filePath, allowed_changes = []) {
73
- const normalizedPath = filePath.replace(/\\/g, "/");
74
-
75
- for (const allowed of allowed_changes) {
76
- if (allowed.type === "file_create" || allowed.type === "file_modify" || allowed.type === "file_delete") {
77
- // Check exact target match
78
- if (allowed.target && allowed.target.replace(/\\/g, "/") === normalizedPath) {
79
- return true;
80
- }
81
-
82
- // Check pattern match
83
- if (allowed.pattern) {
84
- try {
85
- // Use minimatch if available, otherwise simple glob
86
- if (typeof minimatch === "function") {
87
- if (minimatch(normalizedPath, allowed.pattern, { matchBase: true })) {
88
- return true;
89
- }
90
- } else {
91
- // Simple pattern matching fallback
92
- const regex = new RegExp(
93
- "^" + allowed.pattern
94
- .replace(/\*\*/g, "{{GLOBSTAR}}")
95
- .replace(/\*/g, "[^/]*")
96
- .replace(/{{GLOBSTAR}}/g, ".*")
97
- .replace(/\?/g, ".") + "$"
98
- );
99
- if (regex.test(normalizedPath)) {
100
- return true;
101
- }
102
- }
103
- } catch {
104
- // Skip invalid patterns
105
- }
106
- }
107
- }
108
- }
109
-
110
- return false;
111
- }
112
-
113
- /**
114
- * Check if a route is allowed by intent
115
- * @param {string} method - HTTP method
116
- * @param {string} routePath - Route path
117
- * @param {Object[]} allowed_changes - Allowed changes from intent
118
- * @returns {boolean} True if allowed
119
- */
120
- function isRouteAllowed(method, routePath, allowed_changes = []) {
121
- for (const allowed of allowed_changes) {
122
- if (allowed.type === "route_add" || allowed.type === "route_modify") {
123
- // Check exact match
124
- if (allowed.target === routePath) {
125
- return true;
126
- }
127
-
128
- // Check pattern match (e.g., /api/users/*)
129
- if (allowed.pattern) {
130
- try {
131
- const regex = new RegExp(
132
- "^" + allowed.pattern
133
- .replace(/\*/g, "[^/]+")
134
- .replace(/\*\*/g, ".*") + "$"
135
- );
136
- if (regex.test(routePath)) {
137
- return true;
138
- }
139
- } catch {
140
- // Skip invalid patterns
141
- }
142
- }
143
- }
144
- }
145
-
146
- return false;
147
- }
148
-
149
- /**
150
- * Check if env var addition is allowed by intent
151
- * @param {string} envVar - Environment variable name
152
- * @param {Object[]} allowed_changes - Allowed changes from intent
153
- * @returns {boolean} True if allowed
154
- */
155
- function isEnvVarAllowed(envVar, allowed_changes = []) {
156
- for (const allowed of allowed_changes) {
157
- if (allowed.type === "env_add") {
158
- if (allowed.target === envVar) {
159
- return true;
160
- }
161
- // Pattern match (e.g., STRIPE_*)
162
- if (allowed.pattern) {
163
- try {
164
- const regex = new RegExp("^" + allowed.pattern.replace(/\*/g, ".*") + "$");
165
- if (regex.test(envVar)) {
166
- return true;
167
- }
168
- } catch {
169
- // Skip invalid patterns
170
- }
171
- }
172
- }
173
- }
174
-
175
- return false;
176
- }
177
-
178
- /**
179
- * Check if path is within allowed scope
180
- * @param {string} filePath - File path to check
181
- * @param {Object} scope - Intent scope restrictions
182
- * @returns {boolean} True if within scope
183
- */
184
- function isWithinScope(filePath, scope) {
185
- if (!scope) return true; // No scope = everything allowed
186
-
187
- const normalizedPath = filePath.replace(/\\/g, "/");
188
-
189
- // Check excluded paths first
190
- if (scope.excluded_paths) {
191
- for (const excluded of scope.excluded_paths) {
192
- const normalizedExcluded = excluded.replace(/\\/g, "/");
193
- if (normalizedPath.startsWith(normalizedExcluded) || normalizedPath === normalizedExcluded) {
194
- return false;
195
- }
196
- }
197
- }
198
-
199
- // Check directory restrictions
200
- if (scope.directories && scope.directories.length > 0) {
201
- const inAllowedDir = scope.directories.some(dir => {
202
- const normalizedDir = dir.replace(/\\/g, "/");
203
- return normalizedPath.startsWith(normalizedDir);
204
- });
205
- if (!inAllowedDir) {
206
- return false;
207
- }
208
- }
209
-
210
- // Check file pattern restrictions
211
- if (scope.file_patterns && scope.file_patterns.length > 0) {
212
- const matchesPattern = scope.file_patterns.some(pattern => {
213
- try {
214
- if (typeof minimatch === "function") {
215
- return minimatch(normalizedPath, pattern, { matchBase: true });
216
- }
217
- const regex = new RegExp(
218
- "^" + pattern
219
- .replace(/\*\*/g, "{{GLOBSTAR}}")
220
- .replace(/\*/g, "[^/]*")
221
- .replace(/{{GLOBSTAR}}/g, ".*") + "$"
222
- );
223
- return regex.test(normalizedPath);
224
- } catch {
225
- return false;
226
- }
227
- });
228
- if (!matchesPattern) {
229
- return false;
230
- }
231
- }
232
-
233
- return true;
234
- }
235
-
236
- /**
237
- * Check if domain is allowed by intent
238
- * @param {string} domain - Domain classification
239
- * @param {Object} scope - Intent scope restrictions
240
- * @returns {boolean} True if allowed
241
- */
242
- function isDomainAllowed(domain, scope) {
243
- if (!scope || !scope.domains || scope.domains.length === 0) {
244
- return true; // No domain restrictions
245
- }
246
- return scope.domains.includes(domain);
247
- }
248
-
249
- /**
250
- * Check constraint violations
251
- * @param {string[]} constraints - Intent constraints
252
- * @param {Object} changeEvent - Change event to check
253
- * @returns {Object[]} Array of constraint violations
254
- */
255
- function checkConstraintViolations(constraints, changeEvent) {
256
- const violations = [];
257
-
258
- for (let i = 0; i < constraints.length; i++) {
259
- const constraint = constraints[i].toLowerCase();
260
-
261
- // No new routes
262
- if (constraint.includes("no new routes") || constraint.includes("no_new_routes")) {
263
- if (changeEvent.type === "route_add" ||
264
- (changeEvent.claims && changeEvent.claims.some(c => c.type === "route"))) {
265
- violations.push({
266
- code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
267
- rule: "constraint_no_new_routes",
268
- message: "New route addition blocked by constraint",
269
- resource: changeEvent.location,
270
- intent_ref: `constraints[${i}]`,
271
- severity: "block",
272
- });
273
- }
274
- }
275
-
276
- // No auth changes
277
- if (constraint.includes("no auth") || constraint.includes("no_auth_changes")) {
278
- if (changeEvent.domain === "auth" ||
279
- (changeEvent.claims && changeEvent.claims.some(c => c.type === "auth_boundary"))) {
280
- violations.push({
281
- code: VIOLATION_CODES.AUTH_MODIFICATION,
282
- rule: "constraint_no_auth_changes",
283
- message: "Auth modification blocked by constraint",
284
- resource: changeEvent.location,
285
- intent_ref: `constraints[${i}]`,
286
- severity: "block",
287
- });
288
- }
289
- }
290
-
291
- // No new environment variables
292
- if (constraint.includes("no new env") || constraint.includes("no_env_additions")) {
293
- if (changeEvent.type === "env_ref" && !changeEvent.env_exists) {
294
- violations.push({
295
- code: VIOLATION_CODES.UNDECLARED_ENV,
296
- rule: "constraint_no_env_additions",
297
- message: `New env var '${changeEvent.resource}' blocked by constraint`,
298
- resource: changeEvent.resource,
299
- intent_ref: `constraints[${i}]`,
300
- severity: "block",
301
- });
302
- }
303
- }
304
-
305
- // No payment changes
306
- if (constraint.includes("no payment") || constraint.includes("no_payment_changes")) {
307
- if (changeEvent.domain === "payments") {
308
- violations.push({
309
- code: VIOLATION_CODES.PAYMENT_MODIFICATION,
310
- rule: "constraint_no_payment_changes",
311
- message: "Payment code modification blocked by constraint",
312
- resource: changeEvent.location,
313
- intent_ref: `constraints[${i}]`,
314
- severity: "block",
315
- });
316
- }
317
- }
318
-
319
- // Tests required
320
- if (constraint.includes("tests required") || constraint.includes("tests_required")) {
321
- if (!changeEvent.includes_tests) {
322
- violations.push({
323
- code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
324
- rule: "constraint_tests_required",
325
- message: "Tests required by constraint but none provided",
326
- resource: changeEvent.location,
327
- intent_ref: `constraints[${i}]`,
328
- severity: "block",
329
- });
330
- }
331
- }
332
-
333
- // Single file only
334
- if (constraint.includes("single file") || constraint.includes("single_file_only")) {
335
- if (changeEvent.file_count > 1) {
336
- violations.push({
337
- code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
338
- rule: "constraint_single_file",
339
- message: `Multiple files (${changeEvent.file_count}) modified but constraint requires single file`,
340
- resource: changeEvent.location,
341
- intent_ref: `constraints[${i}]`,
342
- severity: "block",
343
- });
344
- }
345
- }
346
- }
347
-
348
- return violations;
349
- }
350
-
351
- /**
352
- * Detect code quality violations (mock data, TODOs, fake handlers)
353
- * @param {Object} changeEvent - Change event with diff
354
- * @returns {Object[]} Array of violations
355
- */
356
- function detectCodeQualityViolations(changeEvent) {
357
- const violations = [];
358
- const content = changeEvent.diff?.after || changeEvent.content || "";
359
-
360
- // Mock data detection
361
- const mockPatterns = [
362
- /mock\s*data/i,
363
- /fake\s*response/i,
364
- /stub\s*data/i,
365
- /dummy\s*data/i,
366
- /\[\s*"test"\s*,\s*"data"\s*\]/,
367
- /return\s+\{\s*success:\s*true\s*\}/,
368
- /setTimeout\s*\(\s*\(\)\s*=>\s*\{[^}]*success/i,
369
- ];
370
-
371
- for (const pattern of mockPatterns) {
372
- if (pattern.test(content)) {
373
- violations.push({
374
- code: VIOLATION_CODES.MOCK_DATA_DETECTED,
375
- rule: "no_mock_data",
376
- message: "Mock/fake data detected in production code",
377
- resource: changeEvent.location,
378
- intent_ref: "enforcement_rule",
379
- severity: "block",
380
- });
381
- break;
382
- }
383
- }
384
-
385
- // TODO/FIXME detection
386
- const todoPattern = /\b(TODO|FIXME|XXX|HACK|BUG)[\s:]/i;
387
- if (todoPattern.test(content)) {
388
- violations.push({
389
- code: VIOLATION_CODES.TODO_DETECTED,
390
- rule: "no_todos",
391
- message: "Unresolved TODO/FIXME comment detected",
392
- resource: changeEvent.location,
393
- intent_ref: "enforcement_rule",
394
- severity: "block",
395
- });
396
- }
397
-
398
- // Fake handler detection
399
- const fakeHandlerPatterns = [
400
- /async\s+function\s+\w+\s*\([^)]*\)\s*\{\s*\}/,
401
- /const\s+\w+\s*=\s*async\s*\([^)]*\)\s*=>\s*\{\s*\}/,
402
- /\w+Handler\s*=\s*\(\)\s*=>\s*\{\s*\}/,
403
- /notImplemented/i,
404
- /throw\s+new\s+Error\s*\(\s*["']not\s+implemented/i,
405
- ];
406
-
407
- for (const pattern of fakeHandlerPatterns) {
408
- if (pattern.test(content)) {
409
- violations.push({
410
- code: VIOLATION_CODES.FAKE_HANDLER,
411
- rule: "no_fake_handlers",
412
- message: "Empty or placeholder handler detected",
413
- resource: changeEvent.location,
414
- intent_ref: "enforcement_rule",
415
- severity: "block",
416
- });
417
- break;
418
- }
419
- }
420
-
421
- return violations;
422
- }
423
-
424
- /**
425
- * Main alignment check function
426
- *
427
- * @param {Object} intent - Declared intent
428
- * @param {Object} changeEvent - Normalized change event
429
- * @returns {AlignmentResult} Alignment result
430
- */
431
- function checkAlignment(intent, changeEvent) {
432
- const violations = [];
433
-
434
- // BLOCK if no intent
435
- if (!intent) {
436
- return {
437
- aligned: false,
438
- decision: "BLOCK",
439
- violations: [{
440
- code: VIOLATION_CODES.NO_INTENT,
441
- rule: "intent_required",
442
- message: "No intent declared - all changes blocked by default",
443
- resource: changeEvent.location || "unknown",
444
- intent_ref: "system",
445
- severity: "block",
446
- }],
447
- intent_hash: null,
448
- };
449
- }
450
-
451
- // Check intent is the blocking intent
452
- if (intent.summary?.includes("NO INTENT DECLARED")) {
453
- return {
454
- aligned: false,
455
- decision: "BLOCK",
456
- violations: [{
457
- code: VIOLATION_CODES.NO_INTENT,
458
- rule: "intent_required",
459
- message: "No intent declared - all changes blocked by default",
460
- resource: changeEvent.location || "unknown",
461
- intent_ref: "system",
462
- severity: "block",
463
- }],
464
- intent_hash: intent.hash,
465
- };
466
- }
467
-
468
- // 1. Check file changes against allowed_changes
469
- if (changeEvent.type === "file_write" || changeEvent.type === "file_create" || changeEvent.type === "file_modify") {
470
- if (intent.allowed_changes && intent.allowed_changes.length > 0) {
471
- if (!isFileChangeAllowed(changeEvent.location, intent.allowed_changes)) {
472
- violations.push({
473
- code: VIOLATION_CODES.UNDECLARED_FILE,
474
- rule: "file_change_not_declared",
475
- message: `File change not declared in intent: ${changeEvent.location}`,
476
- resource: changeEvent.location,
477
- intent_ref: "allowed_changes",
478
- severity: "block",
479
- });
480
- }
481
- }
482
- }
483
-
484
- // 2. Check routes against allowed_changes
485
- if (changeEvent.type === "route_add") {
486
- if (!isRouteAllowed(changeEvent.method, changeEvent.resource, intent.allowed_changes)) {
487
- violations.push({
488
- code: VIOLATION_CODES.UNDECLARED_ROUTE,
489
- rule: "route_not_declared",
490
- message: `Route not declared in intent: ${changeEvent.method || "?"} ${changeEvent.resource}`,
491
- resource: changeEvent.resource,
492
- intent_ref: "allowed_changes",
493
- severity: "block",
494
- });
495
- }
496
- }
497
-
498
- // 3. Check env vars against allowed_changes
499
- if (changeEvent.type === "env_ref" && !changeEvent.env_exists) {
500
- if (!isEnvVarAllowed(changeEvent.resource, intent.allowed_changes)) {
501
- violations.push({
502
- code: VIOLATION_CODES.UNDECLARED_ENV,
503
- rule: "env_var_not_declared",
504
- message: `Environment variable not declared in intent or missing: ${changeEvent.resource}`,
505
- resource: changeEvent.resource,
506
- intent_ref: "allowed_changes",
507
- severity: "block",
508
- });
509
- }
510
- }
511
-
512
- // 4. Check scope restrictions
513
- if (intent.scope && changeEvent.location) {
514
- if (!isWithinScope(changeEvent.location, intent.scope)) {
515
- violations.push({
516
- code: VIOLATION_CODES.SCOPE_VIOLATION,
517
- rule: "scope_violation",
518
- message: `Change outside allowed scope: ${changeEvent.location}`,
519
- resource: changeEvent.location,
520
- intent_ref: "scope",
521
- severity: "block",
522
- });
523
- }
524
- }
525
-
526
- // 5. Check domain restrictions
527
- if (intent.scope && changeEvent.domain) {
528
- if (!isDomainAllowed(changeEvent.domain, intent.scope)) {
529
- violations.push({
530
- code: VIOLATION_CODES.DOMAIN_VIOLATION,
531
- rule: "domain_not_allowed",
532
- message: `Domain '${changeEvent.domain}' not allowed by intent`,
533
- resource: changeEvent.location,
534
- intent_ref: "scope.domains",
535
- severity: "block",
536
- });
537
- }
538
- }
539
-
540
- // 6. Check constraints
541
- if (intent.constraints && intent.constraints.length > 0) {
542
- const constraintViolations = checkConstraintViolations(intent.constraints, changeEvent);
543
- violations.push(...constraintViolations);
544
- }
545
-
546
- // 7. Check code quality (mock data, TODOs, fake handlers)
547
- const qualityViolations = detectCodeQualityViolations(changeEvent);
548
- violations.push(...qualityViolations);
549
-
550
- // 8. Check UI success without backend proof
551
- if (changeEvent.claims) {
552
- const uiSuccessClaims = changeEvent.claims.filter(c => c.type === "ui_success_claim");
553
- for (const claim of uiSuccessClaims) {
554
- if (!claim.backend_verified) {
555
- violations.push({
556
- code: VIOLATION_CODES.UI_WITHOUT_BACKEND,
557
- rule: "ui_success_requires_proof",
558
- message: `UI success state without backend proof: ${claim.value || claim.pointer}`,
559
- resource: claim.file || changeEvent.location,
560
- intent_ref: "enforcement_rule",
561
- severity: "block",
562
- });
563
- }
564
- }
565
- }
566
-
567
- // Final decision
568
- const aligned = violations.length === 0;
569
-
570
- return {
571
- aligned,
572
- decision: aligned ? "PASS" : "BLOCK",
573
- violations,
574
- intent_hash: intent.hash,
575
- };
576
- }
577
-
578
- /**
579
- * Batch alignment check for multiple change events
580
- * @param {Object} intent - Declared intent
581
- * @param {Object[]} changeEvents - Array of change events
582
- * @returns {AlignmentResult} Aggregated alignment result
583
- */
584
- function checkAlignmentBatch(intent, changeEvents) {
585
- const allViolations = [];
586
-
587
- for (const event of changeEvents) {
588
- const result = checkAlignment(intent, event);
589
- allViolations.push(...result.violations);
590
- }
591
-
592
- // De-duplicate violations by code + resource
593
- const seen = new Set();
594
- const uniqueViolations = allViolations.filter(v => {
595
- const key = `${v.code}:${v.resource}`;
596
- if (seen.has(key)) return false;
597
- seen.add(key);
598
- return true;
599
- });
600
-
601
- const aligned = uniqueViolations.length === 0;
602
-
603
- return {
604
- aligned,
605
- decision: aligned ? "PASS" : "BLOCK",
606
- violations: uniqueViolations,
607
- intent_hash: intent?.hash || null,
608
- };
609
- }
610
-
611
- module.exports = {
612
- checkAlignment,
613
- checkAlignmentBatch,
614
- isFileChangeAllowed,
615
- isRouteAllowed,
616
- isEnvVarAllowed,
617
- isWithinScope,
618
- isDomainAllowed,
619
- checkConstraintViolations,
620
- detectCodeQualityViolations,
621
- VIOLATION_CODES,
622
- };
1
+ /**
2
+ * Intent Alignment Engine - Core Enforcement Logic
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * AGENT FIREWALL™ - INTENT ALIGNMENT ENGINE
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * For every Change Event:
9
+ * - Compare change against declared intent
10
+ * - Enforce STRICT matching rules
11
+ * - BLOCK if intent is violated
12
+ *
13
+ * This is NOT advisory. This is enforcement.
14
+ *
15
+ * @module intent/alignment-engine
16
+ * @version 2.0.0
17
+ */
18
+
19
+ "use strict";
20
+
21
+ const path = require("path");
22
+
23
+ // Try to load minimatch, fallback to simple pattern matching if not available
24
+ let minimatch = null;
25
+ try {
26
+ minimatch = require("minimatch").minimatch;
27
+ } catch {
28
+ // minimatch not available, will use fallback
29
+ }
30
+
31
+ /**
32
+ * Violation codes for machine-readable errors
33
+ */
34
+ const VIOLATION_CODES = {
35
+ NO_INTENT: "NO_INTENT_DECLARED",
36
+ INTENT_EXPIRED: "INTENT_EXPIRED",
37
+ INTENT_CORRUPTED: "INTENT_INTEGRITY_FAILED",
38
+ UNDECLARED_ROUTE: "UNDECLARED_ROUTE",
39
+ UNDECLARED_ENV: "UNDECLARED_ENV_VAR",
40
+ UNDECLARED_FILE: "UNDECLARED_FILE_CHANGE",
41
+ CONSTRAINT_VIOLATED: "CONSTRAINT_VIOLATED",
42
+ SCOPE_VIOLATION: "SCOPE_VIOLATION",
43
+ DOMAIN_VIOLATION: "DOMAIN_NOT_ALLOWED",
44
+ PERMISSION_CHANGE: "UNAUTHORIZED_PERMISSION_CHANGE",
45
+ AUTH_MODIFICATION: "UNAUTHORIZED_AUTH_MODIFICATION",
46
+ PAYMENT_MODIFICATION: "UNAUTHORIZED_PAYMENT_MODIFICATION",
47
+ MOCK_DATA_DETECTED: "MOCK_DATA_IN_PRODUCTION_CODE",
48
+ TODO_DETECTED: "UNRESOLVED_TODO_PLACEHOLDER",
49
+ FAKE_HANDLER: "FAKE_HANDLER_DETECTED",
50
+ UI_WITHOUT_BACKEND: "UI_SUCCESS_WITHOUT_BACKEND_PROOF",
51
+ };
52
+
53
+ /**
54
+ * Alignment Check Result
55
+ * @typedef {Object} AlignmentResult
56
+ * @property {boolean} aligned - Whether change is aligned with intent
57
+ * @property {string} decision - PASS or BLOCK
58
+ * @property {Object[]} violations - Array of violations
59
+ * @property {string} intent_hash - Hash of intent used for checking
60
+ */
61
+
62
+ /**
63
+ * Violation object structure
64
+ * @typedef {Object} Violation
65
+ * @property {string} code - Machine-readable violation code
66
+ * @property {string} rule - Human-readable rule name
67
+ * @property {string} message - Detailed violation message
68
+ * @property {string} resource - Resource that caused violation
69
+ * @property {string} intent_ref - Reference to violated intent element
70
+ * @property {string} severity - Always "block" for violations
71
+ */
72
+
73
+ /**
74
+ * Check if a path matches any allowed pattern
75
+ * @param {string} filePath - Path to check
76
+ * @param {Object[]} allowed_changes - Allowed changes from intent
77
+ * @returns {boolean} True if allowed
78
+ */
79
+ function isFileChangeAllowed(filePath, allowed_changes = []) {
80
+ const normalizedPath = filePath.replace(/\\/g, "/");
81
+
82
+ for (const allowed of allowed_changes) {
83
+ if (allowed.type === "file_create" || allowed.type === "file_modify" || allowed.type === "file_delete") {
84
+ // Check exact target match
85
+ if (allowed.target && allowed.target.replace(/\\/g, "/") === normalizedPath) {
86
+ return true;
87
+ }
88
+
89
+ // Check pattern match
90
+ if (allowed.pattern) {
91
+ try {
92
+ // Use minimatch if available, otherwise simple glob
93
+ if (typeof minimatch === "function") {
94
+ if (minimatch(normalizedPath, allowed.pattern, { matchBase: true })) {
95
+ return true;
96
+ }
97
+ } else {
98
+ // Simple pattern matching fallback using placeholders
99
+ // Use placeholders to avoid escaping issues
100
+ const regex = new RegExp(
101
+ "^" + allowed.pattern
102
+ .replace(/\*\*\//g, "{{DIRSTAR}}") // Placeholder for **/
103
+ .replace(/\*\*/g, "{{GLOBSTAR}}") // Placeholder for **
104
+ .replace(/\?/g, "{{QMARK}}") // Placeholder for ?
105
+ .replace(/\./g, "\\.") // Escape dots
106
+ .replace(/\*/g, "[^/]*") // * matches anything except /
107
+ .replace(/{{DIRSTAR}}/g, "(?:.*/)?") // **/ matches zero or more dirs
108
+ .replace(/{{GLOBSTAR}}/g, ".*") // ** matches anything
109
+ .replace(/{{QMARK}}/g, ".") + "$" // ? matches single char
110
+ );
111
+ if (regex.test(normalizedPath)) {
112
+ return true;
113
+ }
114
+ }
115
+ } catch {
116
+ // Skip invalid patterns
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return false;
123
+ }
124
+
125
+ /**
126
+ * Check if a route is allowed by intent
127
+ * @param {string} method - HTTP method
128
+ * @param {string} routePath - Route path
129
+ * @param {Object[]} allowed_changes - Allowed changes from intent
130
+ * @returns {boolean} True if allowed
131
+ */
132
+ function isRouteAllowed(method, routePath, allowed_changes = []) {
133
+ for (const allowed of allowed_changes) {
134
+ if (allowed.type === "route_add" || allowed.type === "route_modify") {
135
+ // Check exact match
136
+ if (allowed.target === routePath) {
137
+ return true;
138
+ }
139
+
140
+ // Check pattern match (e.g., /api/users/*)
141
+ if (allowed.pattern) {
142
+ try {
143
+ const regex = new RegExp(
144
+ "^" + allowed.pattern
145
+ .replace(/\*/g, "[^/]+")
146
+ .replace(/\*\*/g, ".*") + "$"
147
+ );
148
+ if (regex.test(routePath)) {
149
+ return true;
150
+ }
151
+ } catch {
152
+ // Skip invalid patterns
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ return false;
159
+ }
160
+
161
+ /**
162
+ * Check if env var addition is allowed by intent
163
+ * @param {string} envVar - Environment variable name
164
+ * @param {Object[]} allowed_changes - Allowed changes from intent
165
+ * @returns {boolean} True if allowed
166
+ */
167
+ function isEnvVarAllowed(envVar, allowed_changes = []) {
168
+ for (const allowed of allowed_changes) {
169
+ if (allowed.type === "env_add") {
170
+ if (allowed.target === envVar) {
171
+ return true;
172
+ }
173
+ // Pattern match (e.g., STRIPE_*)
174
+ if (allowed.pattern) {
175
+ try {
176
+ const regex = new RegExp("^" + allowed.pattern.replace(/\*/g, ".*") + "$");
177
+ if (regex.test(envVar)) {
178
+ return true;
179
+ }
180
+ } catch {
181
+ // Skip invalid patterns
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Check if path is within allowed scope
192
+ * @param {string} filePath - File path to check
193
+ * @param {Object} scope - Intent scope restrictions
194
+ * @returns {boolean} True if within scope
195
+ */
196
+ function isWithinScope(filePath, scope) {
197
+ if (!scope) return true; // No scope = everything allowed
198
+
199
+ const normalizedPath = filePath.replace(/\\/g, "/");
200
+
201
+ // Check excluded paths first
202
+ if (scope.excluded_paths) {
203
+ for (const excluded of scope.excluded_paths) {
204
+ const normalizedExcluded = excluded.replace(/\\/g, "/");
205
+ if (normalizedPath.startsWith(normalizedExcluded) || normalizedPath === normalizedExcluded) {
206
+ return false;
207
+ }
208
+ }
209
+ }
210
+
211
+ // Check directory restrictions
212
+ if (scope.directories && scope.directories.length > 0) {
213
+ const inAllowedDir = scope.directories.some(dir => {
214
+ const normalizedDir = dir.replace(/\\/g, "/");
215
+ return normalizedPath.startsWith(normalizedDir);
216
+ });
217
+ if (!inAllowedDir) {
218
+ return false;
219
+ }
220
+ }
221
+
222
+ // Check file pattern restrictions
223
+ if (scope.file_patterns && scope.file_patterns.length > 0) {
224
+ const matchesPattern = scope.file_patterns.some(pattern => {
225
+ try {
226
+ if (typeof minimatch === "function") {
227
+ return minimatch(normalizedPath, pattern, { matchBase: true });
228
+ }
229
+ const regex = new RegExp(
230
+ "^" + pattern
231
+ .replace(/\*\*/g, "{{GLOBSTAR}}")
232
+ .replace(/\*/g, "[^/]*")
233
+ .replace(/{{GLOBSTAR}}/g, ".*") + "$"
234
+ );
235
+ return regex.test(normalizedPath);
236
+ } catch {
237
+ return false;
238
+ }
239
+ });
240
+ if (!matchesPattern) {
241
+ return false;
242
+ }
243
+ }
244
+
245
+ return true;
246
+ }
247
+
248
+ /**
249
+ * Check if domain is allowed by intent
250
+ * @param {string} domain - Domain classification
251
+ * @param {Object} scope - Intent scope restrictions
252
+ * @returns {boolean} True if allowed
253
+ */
254
+ function isDomainAllowed(domain, scope) {
255
+ if (!scope || !scope.domains || scope.domains.length === 0) {
256
+ return true; // No domain restrictions
257
+ }
258
+ return scope.domains.includes(domain);
259
+ }
260
+
261
+ /**
262
+ * Check constraint violations
263
+ * @param {string[]} constraints - Intent constraints
264
+ * @param {Object} changeEvent - Change event to check
265
+ * @returns {Object[]} Array of constraint violations
266
+ */
267
+ function checkConstraintViolations(constraints, changeEvent) {
268
+ const violations = [];
269
+
270
+ for (let i = 0; i < constraints.length; i++) {
271
+ const constraint = constraints[i].toLowerCase();
272
+
273
+ // No new routes
274
+ if (constraint.includes("no new routes") || constraint.includes("no_new_routes")) {
275
+ if (changeEvent.type === "route_add" ||
276
+ (changeEvent.claims && changeEvent.claims.some(c => c.type === "route"))) {
277
+ violations.push({
278
+ code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
279
+ rule: "constraint_no_new_routes",
280
+ message: "New route addition blocked by constraint",
281
+ resource: changeEvent.location,
282
+ intent_ref: `constraints[${i}]`,
283
+ severity: "block",
284
+ });
285
+ }
286
+ }
287
+
288
+ // No auth changes
289
+ if (constraint.includes("no auth") || constraint.includes("no_auth_changes")) {
290
+ if (changeEvent.domain === "auth" ||
291
+ (changeEvent.claims && changeEvent.claims.some(c => c.type === "auth_boundary"))) {
292
+ violations.push({
293
+ code: VIOLATION_CODES.AUTH_MODIFICATION,
294
+ rule: "constraint_no_auth_changes",
295
+ message: "Auth modification blocked by constraint",
296
+ resource: changeEvent.location,
297
+ intent_ref: `constraints[${i}]`,
298
+ severity: "block",
299
+ });
300
+ }
301
+ }
302
+
303
+ // No new environment variables
304
+ if (constraint.includes("no new env") || constraint.includes("no_env_additions")) {
305
+ if (changeEvent.type === "env_ref" && !changeEvent.env_exists) {
306
+ violations.push({
307
+ code: VIOLATION_CODES.UNDECLARED_ENV,
308
+ rule: "constraint_no_env_additions",
309
+ message: `New env var '${changeEvent.resource}' blocked by constraint`,
310
+ resource: changeEvent.resource,
311
+ intent_ref: `constraints[${i}]`,
312
+ severity: "block",
313
+ });
314
+ }
315
+ }
316
+
317
+ // No payment changes
318
+ if (constraint.includes("no payment") || constraint.includes("no_payment_changes")) {
319
+ if (changeEvent.domain === "payments") {
320
+ violations.push({
321
+ code: VIOLATION_CODES.PAYMENT_MODIFICATION,
322
+ rule: "constraint_no_payment_changes",
323
+ message: "Payment code modification blocked by constraint",
324
+ resource: changeEvent.location,
325
+ intent_ref: `constraints[${i}]`,
326
+ severity: "block",
327
+ });
328
+ }
329
+ }
330
+
331
+ // Tests required
332
+ if (constraint.includes("tests required") || constraint.includes("tests_required")) {
333
+ if (!changeEvent.includes_tests) {
334
+ violations.push({
335
+ code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
336
+ rule: "constraint_tests_required",
337
+ message: "Tests required by constraint but none provided",
338
+ resource: changeEvent.location,
339
+ intent_ref: `constraints[${i}]`,
340
+ severity: "block",
341
+ });
342
+ }
343
+ }
344
+
345
+ // Single file only
346
+ if (constraint.includes("single file") || constraint.includes("single_file_only")) {
347
+ if (changeEvent.file_count > 1) {
348
+ violations.push({
349
+ code: VIOLATION_CODES.CONSTRAINT_VIOLATED,
350
+ rule: "constraint_single_file",
351
+ message: `Multiple files (${changeEvent.file_count}) modified but constraint requires single file`,
352
+ resource: changeEvent.location,
353
+ intent_ref: `constraints[${i}]`,
354
+ severity: "block",
355
+ });
356
+ }
357
+ }
358
+ }
359
+
360
+ return violations;
361
+ }
362
+
363
+ /**
364
+ * Detect code quality violations (mock data, TODOs, fake handlers)
365
+ * @param {Object} changeEvent - Change event with diff
366
+ * @returns {Object[]} Array of violations
367
+ */
368
+ function detectCodeQualityViolations(changeEvent) {
369
+ const violations = [];
370
+ const content = changeEvent.diff?.after || changeEvent.content || "";
371
+
372
+ // Mock data detection
373
+ const mockPatterns = [
374
+ /mock\s*data/i,
375
+ /fake\s*response/i,
376
+ /stub\s*data/i,
377
+ /dummy\s*data/i,
378
+ /\[\s*"test"\s*,\s*"data"\s*\]/,
379
+ /return\s+\{\s*success:\s*true\s*\}/,
380
+ /setTimeout\s*\(\s*\(\)\s*=>\s*\{[^}]*success/i,
381
+ ];
382
+
383
+ for (const pattern of mockPatterns) {
384
+ if (pattern.test(content)) {
385
+ violations.push({
386
+ code: VIOLATION_CODES.MOCK_DATA_DETECTED,
387
+ rule: "no_mock_data",
388
+ message: "Mock/fake data detected in production code",
389
+ resource: changeEvent.location,
390
+ intent_ref: "enforcement_rule",
391
+ severity: "block",
392
+ });
393
+ break;
394
+ }
395
+ }
396
+
397
+ // TODO/FIXME detection
398
+ const todoPattern = /\b(TODO|FIXME|XXX|HACK|BUG)[\s:]/i;
399
+ if (todoPattern.test(content)) {
400
+ violations.push({
401
+ code: VIOLATION_CODES.TODO_DETECTED,
402
+ rule: "no_todos",
403
+ message: "Unresolved TODO/FIXME comment detected",
404
+ resource: changeEvent.location,
405
+ intent_ref: "enforcement_rule",
406
+ severity: "block",
407
+ });
408
+ }
409
+
410
+ // Fake handler detection
411
+ const fakeHandlerPatterns = [
412
+ /async\s+function\s+\w+\s*\([^)]*\)\s*\{\s*\}/,
413
+ /const\s+\w+\s*=\s*async\s*\([^)]*\)\s*=>\s*\{\s*\}/,
414
+ /\w+Handler\s*=\s*\(\)\s*=>\s*\{\s*\}/,
415
+ /notImplemented/i,
416
+ /throw\s+new\s+Error\s*\(\s*["']not\s+implemented/i,
417
+ ];
418
+
419
+ for (const pattern of fakeHandlerPatterns) {
420
+ if (pattern.test(content)) {
421
+ violations.push({
422
+ code: VIOLATION_CODES.FAKE_HANDLER,
423
+ rule: "no_fake_handlers",
424
+ message: "Empty or placeholder handler detected",
425
+ resource: changeEvent.location,
426
+ intent_ref: "enforcement_rule",
427
+ severity: "block",
428
+ });
429
+ break;
430
+ }
431
+ }
432
+
433
+ return violations;
434
+ }
435
+
436
+ /**
437
+ * Main alignment check function
438
+ *
439
+ * @param {Object} intent - Declared intent
440
+ * @param {Object} changeEvent - Normalized change event
441
+ * @returns {AlignmentResult} Alignment result
442
+ */
443
+ function checkAlignment(intent, changeEvent) {
444
+ const violations = [];
445
+
446
+ // BLOCK if no intent
447
+ if (!intent) {
448
+ return {
449
+ aligned: false,
450
+ decision: "BLOCK",
451
+ violations: [{
452
+ code: VIOLATION_CODES.NO_INTENT,
453
+ rule: "intent_required",
454
+ message: "No intent declared - all changes blocked by default",
455
+ resource: changeEvent.location || "unknown",
456
+ intent_ref: "system",
457
+ severity: "block",
458
+ }],
459
+ intent_hash: null,
460
+ };
461
+ }
462
+
463
+ // Check intent is the blocking intent
464
+ if (intent.summary?.includes("NO INTENT DECLARED")) {
465
+ return {
466
+ aligned: false,
467
+ decision: "BLOCK",
468
+ violations: [{
469
+ code: VIOLATION_CODES.NO_INTENT,
470
+ rule: "intent_required",
471
+ message: "No intent declared - all changes blocked by default",
472
+ resource: changeEvent.location || "unknown",
473
+ intent_ref: "system",
474
+ severity: "block",
475
+ }],
476
+ intent_hash: intent.hash,
477
+ };
478
+ }
479
+
480
+ // 1. Check file changes against allowed_changes
481
+ if (changeEvent.type === "file_write" || changeEvent.type === "file_create" || changeEvent.type === "file_modify") {
482
+ if (intent.allowed_changes && intent.allowed_changes.length > 0) {
483
+ if (!isFileChangeAllowed(changeEvent.location, intent.allowed_changes)) {
484
+ violations.push({
485
+ code: VIOLATION_CODES.UNDECLARED_FILE,
486
+ rule: "file_change_not_declared",
487
+ message: `File change not declared in intent: ${changeEvent.location}`,
488
+ resource: changeEvent.location,
489
+ intent_ref: "allowed_changes",
490
+ severity: "block",
491
+ });
492
+ }
493
+ }
494
+ }
495
+
496
+ // 2. Check routes against allowed_changes
497
+ if (changeEvent.type === "route_add") {
498
+ if (!isRouteAllowed(changeEvent.method, changeEvent.resource, intent.allowed_changes)) {
499
+ violations.push({
500
+ code: VIOLATION_CODES.UNDECLARED_ROUTE,
501
+ rule: "route_not_declared",
502
+ message: `Route not declared in intent: ${changeEvent.method || "?"} ${changeEvent.resource}`,
503
+ resource: changeEvent.resource,
504
+ intent_ref: "allowed_changes",
505
+ severity: "block",
506
+ });
507
+ }
508
+ }
509
+
510
+ // 3. Check env vars against allowed_changes
511
+ if (changeEvent.type === "env_ref" && !changeEvent.env_exists) {
512
+ if (!isEnvVarAllowed(changeEvent.resource, intent.allowed_changes)) {
513
+ violations.push({
514
+ code: VIOLATION_CODES.UNDECLARED_ENV,
515
+ rule: "env_var_not_declared",
516
+ message: `Environment variable not declared in intent or missing: ${changeEvent.resource}`,
517
+ resource: changeEvent.resource,
518
+ intent_ref: "allowed_changes",
519
+ severity: "block",
520
+ });
521
+ }
522
+ }
523
+
524
+ // 4. Check scope restrictions
525
+ if (intent.scope && changeEvent.location) {
526
+ if (!isWithinScope(changeEvent.location, intent.scope)) {
527
+ violations.push({
528
+ code: VIOLATION_CODES.SCOPE_VIOLATION,
529
+ rule: "scope_violation",
530
+ message: `Change outside allowed scope: ${changeEvent.location}`,
531
+ resource: changeEvent.location,
532
+ intent_ref: "scope",
533
+ severity: "block",
534
+ });
535
+ }
536
+ }
537
+
538
+ // 5. Check domain restrictions
539
+ if (intent.scope && changeEvent.domain) {
540
+ if (!isDomainAllowed(changeEvent.domain, intent.scope)) {
541
+ violations.push({
542
+ code: VIOLATION_CODES.DOMAIN_VIOLATION,
543
+ rule: "domain_not_allowed",
544
+ message: `Domain '${changeEvent.domain}' not allowed by intent`,
545
+ resource: changeEvent.location,
546
+ intent_ref: "scope.domains",
547
+ severity: "block",
548
+ });
549
+ }
550
+ }
551
+
552
+ // 6. Check constraints
553
+ if (intent.constraints && intent.constraints.length > 0) {
554
+ const constraintViolations = checkConstraintViolations(intent.constraints, changeEvent);
555
+ violations.push(...constraintViolations);
556
+ }
557
+
558
+ // 7. Check code quality (mock data, TODOs, fake handlers)
559
+ const qualityViolations = detectCodeQualityViolations(changeEvent);
560
+ violations.push(...qualityViolations);
561
+
562
+ // 8. Check UI success without backend proof
563
+ if (changeEvent.claims) {
564
+ const uiSuccessClaims = changeEvent.claims.filter(c => c.type === "ui_success_claim");
565
+ for (const claim of uiSuccessClaims) {
566
+ if (!claim.backend_verified) {
567
+ violations.push({
568
+ code: VIOLATION_CODES.UI_WITHOUT_BACKEND,
569
+ rule: "ui_success_requires_proof",
570
+ message: `UI success state without backend proof: ${claim.value || claim.pointer}`,
571
+ resource: claim.file || changeEvent.location,
572
+ intent_ref: "enforcement_rule",
573
+ severity: "block",
574
+ });
575
+ }
576
+ }
577
+ }
578
+
579
+ // Final decision
580
+ const aligned = violations.length === 0;
581
+
582
+ return {
583
+ aligned,
584
+ decision: aligned ? "PASS" : "BLOCK",
585
+ violations,
586
+ intent_hash: intent.hash,
587
+ };
588
+ }
589
+
590
+ /**
591
+ * Batch alignment check for multiple change events
592
+ * @param {Object} intent - Declared intent
593
+ * @param {Object[]} changeEvents - Array of change events
594
+ * @returns {AlignmentResult} Aggregated alignment result
595
+ */
596
+ function checkAlignmentBatch(intent, changeEvents) {
597
+ const allViolations = [];
598
+
599
+ for (const event of changeEvents) {
600
+ const result = checkAlignment(intent, event);
601
+ allViolations.push(...result.violations);
602
+ }
603
+
604
+ // De-duplicate violations by code + resource
605
+ const seen = new Set();
606
+ const uniqueViolations = allViolations.filter(v => {
607
+ const key = `${v.code}:${v.resource}`;
608
+ if (seen.has(key)) return false;
609
+ seen.add(key);
610
+ return true;
611
+ });
612
+
613
+ const aligned = uniqueViolations.length === 0;
614
+
615
+ return {
616
+ aligned,
617
+ decision: aligned ? "PASS" : "BLOCK",
618
+ violations: uniqueViolations,
619
+ intent_hash: intent?.hash || null,
620
+ };
621
+ }
622
+
623
+ module.exports = {
624
+ checkAlignment,
625
+ checkAlignmentBatch,
626
+ isFileChangeAllowed,
627
+ isRouteAllowed,
628
+ isEnvVarAllowed,
629
+ isWithinScope,
630
+ isDomainAllowed,
631
+ checkConstraintViolations,
632
+ detectCodeQualityViolations,
633
+ VIOLATION_CODES,
634
+ };