@vibecheckai/cli 3.2.6 → 3.3.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/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/package-lock.json +0 -165
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reality State Engine
|
|
3
|
+
*
|
|
4
|
+
* Maintains a unified, authoritative model of the repository.
|
|
5
|
+
* Consolidates data from project-map, route-integrity, and context systems.
|
|
6
|
+
*
|
|
7
|
+
* This is the single source of truth for the Firewall Agent.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const crypto = require("crypto");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} FileMeta
|
|
18
|
+
* @property {string} hash - Content hash (SHA-256)
|
|
19
|
+
* @property {Date} modified - Last modified timestamp
|
|
20
|
+
* @property {number} size - File size in bytes
|
|
21
|
+
* @property {string} domain - Classified domain (auth, payments, routes, etc.)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} Route
|
|
26
|
+
* @property {string} method - HTTP method
|
|
27
|
+
* @property {string} path - Route path
|
|
28
|
+
* @property {string} file - Source file
|
|
29
|
+
* @property {number} line - Line number
|
|
30
|
+
* @property {string[]} middleware - Applied middleware
|
|
31
|
+
* @property {boolean} authenticated - Requires authentication
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} Service
|
|
36
|
+
* @property {string} name - Service name
|
|
37
|
+
* @property {string} file - Source file
|
|
38
|
+
* @property {string[]} dependencies - Service dependencies
|
|
39
|
+
* @property {string} type - Service type (database, cache, api, etc.)
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {Object} EnvVar
|
|
44
|
+
* @property {string} name - Variable name
|
|
45
|
+
* @property {string} source - Where defined (.env file or usage)
|
|
46
|
+
* @property {boolean} declared - Is declared in .env files
|
|
47
|
+
* @property {boolean} used - Is used in code
|
|
48
|
+
* @property {string[]} usedIn - Files where used
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {Object} Import
|
|
53
|
+
* @property {string} from - Source file
|
|
54
|
+
* @property {string} to - Import target
|
|
55
|
+
* @property {string} type - Import type (internal, external)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Object} RealityState
|
|
60
|
+
* @property {Map<string, FileMeta>} files - File metadata map
|
|
61
|
+
* @property {Route[]} routes - All registered routes
|
|
62
|
+
* @property {Service[]} services - All detected services
|
|
63
|
+
* @property {Map<string, EnvVar>} envVars - Environment variables
|
|
64
|
+
* @property {Import[]} imports - Import graph edges
|
|
65
|
+
* @property {Object} buildDAG - Build dependency graph
|
|
66
|
+
* @property {Date} lastUpdated - Last snapshot timestamp
|
|
67
|
+
* @property {string} snapshotHash - Hash of entire state
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
// Cache for reality state
|
|
71
|
+
let cachedState = null;
|
|
72
|
+
let cacheHash = null;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calculate file hash
|
|
76
|
+
*/
|
|
77
|
+
function hashFile(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(filePath);
|
|
80
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Calculate state hash for cache invalidation
|
|
88
|
+
*/
|
|
89
|
+
function calculateStateHash(projectRoot) {
|
|
90
|
+
const guardrailPath = path.join(projectRoot, ".guardrail");
|
|
91
|
+
const vibecheckPath = path.join(projectRoot, ".vibecheck");
|
|
92
|
+
|
|
93
|
+
let combined = "";
|
|
94
|
+
|
|
95
|
+
// Include key data file hashes
|
|
96
|
+
const dataFiles = [
|
|
97
|
+
path.join(guardrailPath, "project-map.json"),
|
|
98
|
+
path.join(guardrailPath, "route-integrity.json"),
|
|
99
|
+
path.join(guardrailPath, "api-contracts.json"),
|
|
100
|
+
path.join(guardrailPath, "context-snapshot.json"),
|
|
101
|
+
path.join(vibecheckPath, "truth", "truthpack.json"),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
for (const file of dataFiles) {
|
|
105
|
+
if (fs.existsSync(file)) {
|
|
106
|
+
const stat = fs.statSync(file);
|
|
107
|
+
combined += `${file}:${stat.mtimeMs};`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return crypto.createHash("md5").update(combined).digest("hex");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Classify file domain based on path and content
|
|
116
|
+
*/
|
|
117
|
+
function classifyFileDomain(filePath) {
|
|
118
|
+
const s = filePath.toLowerCase();
|
|
119
|
+
|
|
120
|
+
if (s.includes("auth") || s.includes("login") || s.includes("session")) return "auth";
|
|
121
|
+
if (s.includes("stripe") || s.includes("payment") || s.includes("billing")) return "payments";
|
|
122
|
+
if (s.includes("routes") || s.includes("router") || s.includes("/api/")) return "routes";
|
|
123
|
+
if (s.includes("schema") || s.includes("contract") || s.includes("openapi")) return "contracts";
|
|
124
|
+
if (s.includes("middleware")) return "middleware";
|
|
125
|
+
if (s.includes("prisma") || s.includes("database") || s.includes("/db/")) return "database";
|
|
126
|
+
if (s.includes("test") || s.includes("spec")) return "test";
|
|
127
|
+
if (s.includes("config") || s.includes(".env")) return "config";
|
|
128
|
+
if (s.includes("/core/") || s.includes("/lib/")) return "core";
|
|
129
|
+
if (s.includes("components") || s.includes("pages") || s.includes("views")) return "ui";
|
|
130
|
+
|
|
131
|
+
return "general";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Load project map data
|
|
136
|
+
*/
|
|
137
|
+
function loadProjectMap(projectRoot) {
|
|
138
|
+
const mapPath = path.join(projectRoot, ".guardrail", "project-map.json");
|
|
139
|
+
if (fs.existsSync(mapPath)) {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(fs.readFileSync(mapPath, "utf-8"));
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load route integrity data
|
|
151
|
+
*/
|
|
152
|
+
function loadRouteIntegrity(projectRoot) {
|
|
153
|
+
const routePath = path.join(projectRoot, ".guardrail", "route-integrity.json");
|
|
154
|
+
if (fs.existsSync(routePath)) {
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(fs.readFileSync(routePath, "utf-8"));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load API contracts data
|
|
166
|
+
*/
|
|
167
|
+
function loadApiContracts(projectRoot) {
|
|
168
|
+
const contractsPath = path.join(projectRoot, ".guardrail", "api-contracts.json");
|
|
169
|
+
if (fs.existsSync(contractsPath)) {
|
|
170
|
+
try {
|
|
171
|
+
return JSON.parse(fs.readFileSync(contractsPath, "utf-8"));
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Load truthpack data
|
|
181
|
+
*/
|
|
182
|
+
function loadTruthpack(projectRoot) {
|
|
183
|
+
const truthPath = path.join(projectRoot, ".vibecheck", "truth", "truthpack.json");
|
|
184
|
+
if (fs.existsSync(truthPath)) {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(fs.readFileSync(truthPath, "utf-8"));
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Extract routes from various data sources
|
|
196
|
+
*/
|
|
197
|
+
function extractRoutes(projectMap, routeIntegrity, apiContracts, truthpack) {
|
|
198
|
+
const routes = [];
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
|
|
201
|
+
// From API contracts
|
|
202
|
+
if (apiContracts?.routes) {
|
|
203
|
+
for (const route of apiContracts.routes) {
|
|
204
|
+
const key = `${route.method || "GET"}:${route.path}`;
|
|
205
|
+
if (!seen.has(key)) {
|
|
206
|
+
seen.add(key);
|
|
207
|
+
routes.push({
|
|
208
|
+
method: route.method || "GET",
|
|
209
|
+
path: route.path,
|
|
210
|
+
file: route.file || "unknown",
|
|
211
|
+
line: route.line || 0,
|
|
212
|
+
middleware: route.middleware || [],
|
|
213
|
+
authenticated: route.authenticated || false,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// From route integrity
|
|
220
|
+
if (routeIntegrity?.routes) {
|
|
221
|
+
for (const route of routeIntegrity.routes) {
|
|
222
|
+
const key = `${route.method || "GET"}:${route.path}`;
|
|
223
|
+
if (!seen.has(key)) {
|
|
224
|
+
seen.add(key);
|
|
225
|
+
routes.push({
|
|
226
|
+
method: route.method || "GET",
|
|
227
|
+
path: route.path,
|
|
228
|
+
file: route.file || "unknown",
|
|
229
|
+
line: route.line || 0,
|
|
230
|
+
middleware: route.middleware || [],
|
|
231
|
+
authenticated: route.requiresAuth || false,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// From truthpack
|
|
238
|
+
if (truthpack?.routes) {
|
|
239
|
+
for (const route of truthpack.routes) {
|
|
240
|
+
const key = `${route.method || "GET"}:${route.path}`;
|
|
241
|
+
if (!seen.has(key)) {
|
|
242
|
+
seen.add(key);
|
|
243
|
+
routes.push({
|
|
244
|
+
method: route.method || "GET",
|
|
245
|
+
path: route.path,
|
|
246
|
+
file: route.file || route.pointer?.file || "unknown",
|
|
247
|
+
line: route.line || route.pointer?.line || 0,
|
|
248
|
+
middleware: route.middleware || [],
|
|
249
|
+
authenticated: route.authenticated || false,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// From project map API routes
|
|
256
|
+
if (projectMap?.apiRoutes) {
|
|
257
|
+
for (const routePath of projectMap.apiRoutes) {
|
|
258
|
+
const key = `GET:${routePath}`;
|
|
259
|
+
if (!seen.has(key)) {
|
|
260
|
+
seen.add(key);
|
|
261
|
+
routes.push({
|
|
262
|
+
method: "GET",
|
|
263
|
+
path: routePath,
|
|
264
|
+
file: "unknown",
|
|
265
|
+
line: 0,
|
|
266
|
+
middleware: [],
|
|
267
|
+
authenticated: false,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return routes;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Extract environment variables from various data sources
|
|
278
|
+
*/
|
|
279
|
+
function extractEnvVars(projectRoot, projectMap, truthpack) {
|
|
280
|
+
const envVars = new Map();
|
|
281
|
+
|
|
282
|
+
// From project map
|
|
283
|
+
if (projectMap?.envVars?.variables) {
|
|
284
|
+
for (const varName of projectMap.envVars.variables) {
|
|
285
|
+
envVars.set(varName, {
|
|
286
|
+
name: varName,
|
|
287
|
+
source: "project-map",
|
|
288
|
+
declared: true,
|
|
289
|
+
used: true,
|
|
290
|
+
usedIn: [],
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// From truthpack
|
|
296
|
+
if (truthpack?.env) {
|
|
297
|
+
for (const env of truthpack.env) {
|
|
298
|
+
const name = env.name || env.key;
|
|
299
|
+
if (name) {
|
|
300
|
+
const existing = envVars.get(name) || {
|
|
301
|
+
name,
|
|
302
|
+
source: "truthpack",
|
|
303
|
+
declared: false,
|
|
304
|
+
used: false,
|
|
305
|
+
usedIn: [],
|
|
306
|
+
};
|
|
307
|
+
existing.declared = env.declared !== false;
|
|
308
|
+
existing.used = env.used !== false;
|
|
309
|
+
if (env.file) {
|
|
310
|
+
existing.usedIn.push(env.file);
|
|
311
|
+
}
|
|
312
|
+
envVars.set(name, existing);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Scan .env files directly
|
|
318
|
+
const envFileNames = [".env", ".env.local", ".env.example", ".env.development", ".env.production"];
|
|
319
|
+
for (const envFile of envFileNames) {
|
|
320
|
+
const envPath = path.join(projectRoot, envFile);
|
|
321
|
+
if (fs.existsSync(envPath)) {
|
|
322
|
+
try {
|
|
323
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
324
|
+
const matches = content.match(/^([A-Z][A-Z0-9_]+)=/gm) || [];
|
|
325
|
+
for (const m of matches) {
|
|
326
|
+
const name = m.replace("=", "");
|
|
327
|
+
const existing = envVars.get(name) || {
|
|
328
|
+
name,
|
|
329
|
+
source: envFile,
|
|
330
|
+
declared: true,
|
|
331
|
+
used: false,
|
|
332
|
+
usedIn: [],
|
|
333
|
+
};
|
|
334
|
+
existing.declared = true;
|
|
335
|
+
existing.source = envFile;
|
|
336
|
+
envVars.set(name, existing);
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
// Ignore read errors
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return envVars;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Extract services from project data
|
|
349
|
+
*/
|
|
350
|
+
function extractServices(projectMap, truthpack) {
|
|
351
|
+
const services = [];
|
|
352
|
+
const seen = new Set();
|
|
353
|
+
|
|
354
|
+
// From truthpack services
|
|
355
|
+
if (truthpack?.services) {
|
|
356
|
+
for (const service of truthpack.services) {
|
|
357
|
+
if (!seen.has(service.name)) {
|
|
358
|
+
seen.add(service.name);
|
|
359
|
+
services.push({
|
|
360
|
+
name: service.name,
|
|
361
|
+
file: service.file || "unknown",
|
|
362
|
+
dependencies: service.dependencies || [],
|
|
363
|
+
type: service.type || "unknown",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Infer services from project structure
|
|
370
|
+
if (projectMap?.directories) {
|
|
371
|
+
const serviceDirs = projectMap.directories.filter(d =>
|
|
372
|
+
d.includes("service") || d.includes("providers")
|
|
373
|
+
);
|
|
374
|
+
for (const dir of serviceDirs) {
|
|
375
|
+
const name = path.basename(dir);
|
|
376
|
+
if (!seen.has(name)) {
|
|
377
|
+
seen.add(name);
|
|
378
|
+
services.push({
|
|
379
|
+
name,
|
|
380
|
+
file: dir,
|
|
381
|
+
dependencies: [],
|
|
382
|
+
type: "inferred",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return services;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Build import graph from dependency data
|
|
393
|
+
*/
|
|
394
|
+
function buildImportGraph(projectRoot) {
|
|
395
|
+
const imports = [];
|
|
396
|
+
|
|
397
|
+
// Try to load dependency graph if it exists
|
|
398
|
+
const graphPath = path.join(projectRoot, ".guardrail", "dependency-graph.json");
|
|
399
|
+
if (fs.existsSync(graphPath)) {
|
|
400
|
+
try {
|
|
401
|
+
const graph = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
|
|
402
|
+
if (graph.edges) {
|
|
403
|
+
imports.push(...graph.edges);
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
// Ignore parse errors
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return imports;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Scan source files for file metadata
|
|
415
|
+
*/
|
|
416
|
+
function scanSourceFiles(projectRoot, maxFiles = 500) {
|
|
417
|
+
const files = new Map();
|
|
418
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".json"];
|
|
419
|
+
const ignoreDirs = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
420
|
+
|
|
421
|
+
function scan(dir, depth = 0) {
|
|
422
|
+
if (depth > 6 || files.size >= maxFiles) return;
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
426
|
+
for (const entry of entries) {
|
|
427
|
+
if (files.size >= maxFiles) break;
|
|
428
|
+
|
|
429
|
+
const fullPath = path.join(dir, entry.name);
|
|
430
|
+
const relativePath = path.relative(projectRoot, fullPath).replace(/\\/g, "/");
|
|
431
|
+
|
|
432
|
+
if (entry.isDirectory()) {
|
|
433
|
+
if (!entry.name.startsWith(".") && !ignoreDirs.includes(entry.name)) {
|
|
434
|
+
scan(fullPath, depth + 1);
|
|
435
|
+
}
|
|
436
|
+
} else if (entry.isFile()) {
|
|
437
|
+
const ext = path.extname(entry.name);
|
|
438
|
+
if (extensions.includes(ext)) {
|
|
439
|
+
try {
|
|
440
|
+
const stat = fs.statSync(fullPath);
|
|
441
|
+
files.set(relativePath, {
|
|
442
|
+
hash: hashFile(fullPath),
|
|
443
|
+
modified: stat.mtime,
|
|
444
|
+
size: stat.size,
|
|
445
|
+
domain: classifyFileDomain(relativePath),
|
|
446
|
+
});
|
|
447
|
+
} catch {
|
|
448
|
+
// Skip files we can't stat
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
// Skip directories we can't read
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
scan(projectRoot);
|
|
459
|
+
return files;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Build the complete reality state
|
|
464
|
+
* @param {string} projectRoot - Project root directory
|
|
465
|
+
* @param {Object} options - Options
|
|
466
|
+
* @param {boolean} options.forceRefresh - Force cache refresh
|
|
467
|
+
* @param {boolean} options.scanFiles - Scan source files for metadata
|
|
468
|
+
* @returns {RealityState} The reality state
|
|
469
|
+
*/
|
|
470
|
+
function buildRealityState(projectRoot, options = {}) {
|
|
471
|
+
const { forceRefresh = false, scanFiles = true } = options;
|
|
472
|
+
|
|
473
|
+
// Check cache
|
|
474
|
+
const currentHash = calculateStateHash(projectRoot);
|
|
475
|
+
if (!forceRefresh && cachedState && cacheHash === currentHash) {
|
|
476
|
+
return cachedState;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Load all data sources
|
|
480
|
+
const projectMap = loadProjectMap(projectRoot);
|
|
481
|
+
const routeIntegrity = loadRouteIntegrity(projectRoot);
|
|
482
|
+
const apiContracts = loadApiContracts(projectRoot);
|
|
483
|
+
const truthpack = loadTruthpack(projectRoot);
|
|
484
|
+
|
|
485
|
+
// Build unified state
|
|
486
|
+
const state = {
|
|
487
|
+
files: scanFiles ? scanSourceFiles(projectRoot) : new Map(),
|
|
488
|
+
routes: extractRoutes(projectMap, routeIntegrity, apiContracts, truthpack),
|
|
489
|
+
services: extractServices(projectMap, truthpack),
|
|
490
|
+
envVars: extractEnvVars(projectRoot, projectMap, truthpack),
|
|
491
|
+
imports: buildImportGraph(projectRoot),
|
|
492
|
+
buildDAG: {
|
|
493
|
+
nodes: projectMap?.directories || [],
|
|
494
|
+
dependencies: projectMap?.dependencies || {},
|
|
495
|
+
},
|
|
496
|
+
lastUpdated: new Date(),
|
|
497
|
+
snapshotHash: currentHash,
|
|
498
|
+
|
|
499
|
+
// Metadata
|
|
500
|
+
meta: {
|
|
501
|
+
framework: projectMap?.framework || "unknown",
|
|
502
|
+
language: projectMap?.language || "unknown",
|
|
503
|
+
hasTypescript: projectMap?.hasTypescript || false,
|
|
504
|
+
hasPrisma: projectMap?.hasPrisma || false,
|
|
505
|
+
monorepo: projectMap?.monorepo || null,
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Update cache
|
|
510
|
+
cachedState = state;
|
|
511
|
+
cacheHash = currentHash;
|
|
512
|
+
|
|
513
|
+
return state;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Get reality state (cached if possible)
|
|
518
|
+
*/
|
|
519
|
+
function getRealityState(projectRoot, options = {}) {
|
|
520
|
+
return buildRealityState(projectRoot, options);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Invalidate the cache
|
|
525
|
+
*/
|
|
526
|
+
function invalidateCache() {
|
|
527
|
+
cachedState = null;
|
|
528
|
+
cacheHash = null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Check if a route exists in reality
|
|
533
|
+
*/
|
|
534
|
+
function routeExists(state, method, routePath) {
|
|
535
|
+
return state.routes.some(r =>
|
|
536
|
+
r.method.toUpperCase() === method.toUpperCase() &&
|
|
537
|
+
(r.path === routePath || matchRoutePattern(r.path, routePath))
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Check if an env var exists in reality
|
|
543
|
+
*/
|
|
544
|
+
function envVarExists(state, varName) {
|
|
545
|
+
const envVar = state.envVars.get(varName);
|
|
546
|
+
return envVar && envVar.declared;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Check if a file exists in reality
|
|
551
|
+
*/
|
|
552
|
+
function fileExists(state, filePath) {
|
|
553
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
554
|
+
return state.files.has(normalized);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Match route patterns (handles dynamic segments)
|
|
559
|
+
*/
|
|
560
|
+
function matchRoutePattern(pattern, path) {
|
|
561
|
+
// Convert pattern to regex
|
|
562
|
+
const regexPattern = pattern
|
|
563
|
+
.replace(/:[^/]+/g, "[^/]+") // :param -> [^/]+
|
|
564
|
+
.replace(/\*/g, ".*"); // * -> .*
|
|
565
|
+
|
|
566
|
+
try {
|
|
567
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
568
|
+
return regex.test(path);
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Get files by domain
|
|
576
|
+
*/
|
|
577
|
+
function getFilesByDomain(state, domain) {
|
|
578
|
+
const files = [];
|
|
579
|
+
for (const [path, meta] of state.files) {
|
|
580
|
+
if (meta.domain === domain) {
|
|
581
|
+
files.push({ path, ...meta });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return files;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Get summary of reality state
|
|
589
|
+
*/
|
|
590
|
+
function getStateSummary(state) {
|
|
591
|
+
return {
|
|
592
|
+
fileCount: state.files.size,
|
|
593
|
+
routeCount: state.routes.length,
|
|
594
|
+
serviceCount: state.services.length,
|
|
595
|
+
envVarCount: state.envVars.size,
|
|
596
|
+
importCount: state.imports.length,
|
|
597
|
+
lastUpdated: state.lastUpdated,
|
|
598
|
+
snapshotHash: state.snapshotHash,
|
|
599
|
+
domains: getDomainCounts(state),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Get domain counts
|
|
605
|
+
*/
|
|
606
|
+
function getDomainCounts(state) {
|
|
607
|
+
const counts = {};
|
|
608
|
+
for (const [, meta] of state.files) {
|
|
609
|
+
counts[meta.domain] = (counts[meta.domain] || 0) + 1;
|
|
610
|
+
}
|
|
611
|
+
return counts;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
module.exports = {
|
|
615
|
+
buildRealityState,
|
|
616
|
+
getRealityState,
|
|
617
|
+
invalidateCache,
|
|
618
|
+
routeExists,
|
|
619
|
+
envVarExists,
|
|
620
|
+
fileExists,
|
|
621
|
+
getFilesByDomain,
|
|
622
|
+
getStateSummary,
|
|
623
|
+
classifyFileDomain,
|
|
624
|
+
hashFile,
|
|
625
|
+
};
|