@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,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lawbook Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages invariant library versions and industry templates.
|
|
5
|
+
* Provides import/export functionality for sharing invariants.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Lawbook
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const crypto = require("crypto");
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
INVARIANT_TYPES,
|
|
18
|
+
INVARIANT_SEVERITY,
|
|
19
|
+
validateInvariant,
|
|
20
|
+
parseLawbook,
|
|
21
|
+
serializeLawbook,
|
|
22
|
+
} = require("./schema.js");
|
|
23
|
+
const { lawbookLogger: log, getErrorMessage } = require("../logger.js");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Built-in industry templates
|
|
27
|
+
*/
|
|
28
|
+
const INDUSTRY_TEMPLATES = {
|
|
29
|
+
// Security-focused invariants
|
|
30
|
+
security: {
|
|
31
|
+
name: "Security Essentials",
|
|
32
|
+
description: "Common security rules for web applications",
|
|
33
|
+
invariants: [
|
|
34
|
+
{
|
|
35
|
+
id: "no-eval",
|
|
36
|
+
description: "Prevent use of eval() which can lead to code injection",
|
|
37
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
38
|
+
scope: "**/*.{js,ts,jsx,tsx}",
|
|
39
|
+
pattern: "\\beval\\s*\\(",
|
|
40
|
+
severity: INVARIANT_SEVERITY.BLOCK,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "no-innerhtml",
|
|
44
|
+
description: "Prevent innerHTML usage that can lead to XSS",
|
|
45
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
46
|
+
scope: "**/*.{js,ts,jsx,tsx}",
|
|
47
|
+
pattern: "\\.innerHTML\\s*=",
|
|
48
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "no-hardcoded-secrets",
|
|
52
|
+
description: "Prevent hardcoded API keys and secrets",
|
|
53
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
54
|
+
scope: "**/*.{js,ts,jsx,tsx,json}",
|
|
55
|
+
pattern: "(api[_-]?key|secret|password|token)\\s*[=:]\\s*['\"][a-zA-Z0-9]{16,}",
|
|
56
|
+
exclude: ["**/*.test.*", "**/*.spec.*", "**/mock*"],
|
|
57
|
+
severity: INVARIANT_SEVERITY.BLOCK,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "no-cors-wildcard",
|
|
61
|
+
description: "Prevent CORS wildcard in production",
|
|
62
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
63
|
+
scope: "**/*.{js,ts}",
|
|
64
|
+
pattern: "origin:\\s*['\"]\\*['\"]",
|
|
65
|
+
exclude: ["**/*.test.*", "**/dev*"],
|
|
66
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Quality-focused invariants
|
|
72
|
+
quality: {
|
|
73
|
+
name: "Code Quality",
|
|
74
|
+
description: "Common code quality rules",
|
|
75
|
+
invariants: [
|
|
76
|
+
{
|
|
77
|
+
id: "no-console-log",
|
|
78
|
+
description: "No console.log in production code",
|
|
79
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
80
|
+
scope: "src/**/*.{js,ts,jsx,tsx}",
|
|
81
|
+
pattern: "console\\.log\\(",
|
|
82
|
+
exclude: ["**/*.test.*", "**/*.spec.*"],
|
|
83
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "no-debugger",
|
|
87
|
+
description: "No debugger statements",
|
|
88
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
89
|
+
scope: "**/*.{js,ts,jsx,tsx}",
|
|
90
|
+
pattern: "\\bdebugger\\b",
|
|
91
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "no-todo-in-main",
|
|
95
|
+
description: "No TODO comments in main branch",
|
|
96
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
97
|
+
scope: "src/**/*.{js,ts,jsx,tsx}",
|
|
98
|
+
pattern: "//\\s*TODO[:\\s]",
|
|
99
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Architecture-focused invariants
|
|
105
|
+
architecture: {
|
|
106
|
+
name: "Architecture Patterns",
|
|
107
|
+
description: "Common architectural patterns and boundaries",
|
|
108
|
+
invariants: [
|
|
109
|
+
{
|
|
110
|
+
id: "no-relative-imports-outside",
|
|
111
|
+
description: "Use absolute imports for cross-module dependencies",
|
|
112
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
113
|
+
scope: "src/**/*.{js,ts,jsx,tsx}",
|
|
114
|
+
pattern: "from\\s+['\"]\\.\\.\\/(\\.\\.\\/)+((?!node_modules)[^'\"]+)['\"]",
|
|
115
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "no-circular-json",
|
|
119
|
+
description: "Prevent circular JSON references",
|
|
120
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
121
|
+
scope: "**/*.{js,ts}",
|
|
122
|
+
pattern: "JSON\\.stringify\\([^,]+,\\s*null",
|
|
123
|
+
severity: INVARIANT_SEVERITY.INFO,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// React-specific invariants
|
|
129
|
+
react: {
|
|
130
|
+
name: "React Best Practices",
|
|
131
|
+
description: "React-specific rules and patterns",
|
|
132
|
+
invariants: [
|
|
133
|
+
{
|
|
134
|
+
id: "no-direct-state-mutation",
|
|
135
|
+
description: "Never mutate state directly",
|
|
136
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
137
|
+
scope: "**/*.{jsx,tsx}",
|
|
138
|
+
pattern: "this\\.state\\.[a-zA-Z]+\\s*=",
|
|
139
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "no-array-index-key",
|
|
143
|
+
description: "Avoid using array index as key in lists",
|
|
144
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
145
|
+
scope: "**/*.{jsx,tsx}",
|
|
146
|
+
pattern: "key=\\{\\s*index\\s*\\}|key=\\{\\s*i\\s*\\}",
|
|
147
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Node.js-specific invariants
|
|
153
|
+
nodejs: {
|
|
154
|
+
name: "Node.js Security",
|
|
155
|
+
description: "Node.js-specific security rules",
|
|
156
|
+
invariants: [
|
|
157
|
+
{
|
|
158
|
+
id: "no-sync-fs",
|
|
159
|
+
description: "Avoid synchronous file operations in server code",
|
|
160
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
161
|
+
scope: "src/**/*.{js,ts}",
|
|
162
|
+
pattern: "\\.(readFileSync|writeFileSync|existsSync)\\(",
|
|
163
|
+
exclude: ["**/scripts/**", "**/cli/**", "**/*.test.*"],
|
|
164
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "no-child-process-shell",
|
|
168
|
+
description: "Avoid shell execution in child_process",
|
|
169
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
170
|
+
scope: "**/*.{js,ts}",
|
|
171
|
+
pattern: "exec\\(['\"][^'\"]+['\"]|execSync\\(['\"][^'\"]+['\"]",
|
|
172
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Database-specific invariants
|
|
178
|
+
database: {
|
|
179
|
+
name: "Database Patterns",
|
|
180
|
+
description: "Database access patterns and security",
|
|
181
|
+
invariants: [
|
|
182
|
+
{
|
|
183
|
+
id: "no-raw-sql",
|
|
184
|
+
description: "Use parameterized queries, not raw SQL",
|
|
185
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
186
|
+
scope: "**/*.{js,ts}",
|
|
187
|
+
pattern: "\\$queryRaw`[^`]*\\$\\{|\\$executeRaw`[^`]*\\$\\{",
|
|
188
|
+
severity: INVARIANT_SEVERITY.ERROR,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "no-direct-prisma",
|
|
192
|
+
description: "Use repository pattern for database access",
|
|
193
|
+
rule: INVARIANT_TYPES.NEVER,
|
|
194
|
+
scope: "src/**/*.{js,ts}",
|
|
195
|
+
pattern: "prisma\\.(create|update|delete|findMany|findFirst)\\(",
|
|
196
|
+
exclude: ["**/repositories/**", "**/db/**"],
|
|
197
|
+
severity: INVARIANT_SEVERITY.WARNING,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Lawbook Registry class
|
|
205
|
+
*/
|
|
206
|
+
class LawbookRegistry {
|
|
207
|
+
constructor(options = {}) {
|
|
208
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
209
|
+
this.registryDir = path.join(this.projectRoot, ".vibecheck", "lawbook-registry");
|
|
210
|
+
this.libraries = new Map();
|
|
211
|
+
|
|
212
|
+
// Load registered libraries
|
|
213
|
+
this.loadRegisteredLibraries();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Load registered libraries from disk
|
|
218
|
+
*/
|
|
219
|
+
loadRegisteredLibraries() {
|
|
220
|
+
try {
|
|
221
|
+
if (!fs.existsSync(this.registryDir)) return;
|
|
222
|
+
|
|
223
|
+
const files = fs.readdirSync(this.registryDir);
|
|
224
|
+
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
if (file.endsWith(".json")) {
|
|
227
|
+
const filePath = path.join(this.registryDir, file);
|
|
228
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
229
|
+
const library = JSON.parse(content);
|
|
230
|
+
|
|
231
|
+
this.libraries.set(library.id, library);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
log.warn(`Failed to load registered libraries: ${getErrorMessage(error)}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Register a library
|
|
241
|
+
* @param {Object} library - Library to register
|
|
242
|
+
* @returns {string} Library ID
|
|
243
|
+
*/
|
|
244
|
+
registerLibrary(library) {
|
|
245
|
+
const id = library.id || this.generateId(library.name);
|
|
246
|
+
|
|
247
|
+
const registeredLibrary = {
|
|
248
|
+
id,
|
|
249
|
+
name: library.name,
|
|
250
|
+
description: library.description,
|
|
251
|
+
version: library.version || "1.0.0",
|
|
252
|
+
invariants: library.invariants || [],
|
|
253
|
+
registeredAt: new Date().toISOString(),
|
|
254
|
+
checksum: this.calculateChecksum(library.invariants),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Validate all invariants
|
|
258
|
+
for (const inv of registeredLibrary.invariants) {
|
|
259
|
+
const validation = validateInvariant(inv);
|
|
260
|
+
if (!validation.valid) {
|
|
261
|
+
throw new Error(`Invalid invariant '${inv.id}': ${validation.errors.map(e => e.message).join(", ")}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Save to registry
|
|
266
|
+
this.saveLibrary(registeredLibrary);
|
|
267
|
+
this.libraries.set(id, registeredLibrary);
|
|
268
|
+
|
|
269
|
+
return id;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get a library by ID
|
|
274
|
+
* @param {string} id - Library ID
|
|
275
|
+
* @returns {Object|null} Library or null
|
|
276
|
+
*/
|
|
277
|
+
getLibrary(id) {
|
|
278
|
+
return this.libraries.get(id) || null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* List all registered libraries
|
|
283
|
+
* @returns {Object[]} Libraries
|
|
284
|
+
*/
|
|
285
|
+
listLibraries() {
|
|
286
|
+
return Array.from(this.libraries.values()).map(lib => ({
|
|
287
|
+
id: lib.id,
|
|
288
|
+
name: lib.name,
|
|
289
|
+
description: lib.description,
|
|
290
|
+
version: lib.version,
|
|
291
|
+
invariantCount: lib.invariants.length,
|
|
292
|
+
registeredAt: lib.registeredAt,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Remove a library
|
|
298
|
+
* @param {string} id - Library ID
|
|
299
|
+
* @returns {boolean} Success
|
|
300
|
+
*/
|
|
301
|
+
removeLibrary(id) {
|
|
302
|
+
if (!this.libraries.has(id)) return false;
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const filePath = path.join(this.registryDir, `${id}.json`);
|
|
306
|
+
if (fs.existsSync(filePath)) {
|
|
307
|
+
fs.unlinkSync(filePath);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.libraries.delete(id);
|
|
311
|
+
return true;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
log.error(`Failed to remove library: ${getErrorMessage(error)}`);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Import invariants from a lawbook file
|
|
320
|
+
* @param {string} filePath - Path to lawbook file
|
|
321
|
+
* @returns {Object} Import result
|
|
322
|
+
*/
|
|
323
|
+
importFromFile(filePath) {
|
|
324
|
+
try {
|
|
325
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
326
|
+
const lawbook = parseLawbook(content);
|
|
327
|
+
|
|
328
|
+
const id = this.registerLibrary(lawbook);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
libraryId: id,
|
|
333
|
+
imported: lawbook.invariants?.length || 0,
|
|
334
|
+
};
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: error.message,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Export a library to a lawbook file
|
|
345
|
+
* @param {string} libraryId - Library ID
|
|
346
|
+
* @param {string} outputPath - Output path
|
|
347
|
+
* @returns {boolean} Success
|
|
348
|
+
*/
|
|
349
|
+
exportToFile(libraryId, outputPath) {
|
|
350
|
+
const library = this.getLibrary(libraryId);
|
|
351
|
+
if (!library) return false;
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const lawbook = {
|
|
355
|
+
version: library.version,
|
|
356
|
+
name: library.name,
|
|
357
|
+
description: library.description,
|
|
358
|
+
invariants: library.invariants,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const content = serializeLawbook(lawbook);
|
|
362
|
+
|
|
363
|
+
const dir = path.dirname(outputPath);
|
|
364
|
+
if (!fs.existsSync(dir)) {
|
|
365
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
fs.writeFileSync(outputPath, content);
|
|
369
|
+
return true;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
log.error(`Failed to export library: ${getErrorMessage(error)}`);
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get a built-in industry template
|
|
378
|
+
* @param {string} templateId - Template ID
|
|
379
|
+
* @returns {Object|null} Template or null
|
|
380
|
+
*/
|
|
381
|
+
getTemplate(templateId) {
|
|
382
|
+
return INDUSTRY_TEMPLATES[templateId] || null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* List all built-in templates
|
|
387
|
+
* @returns {Object[]} Template summaries
|
|
388
|
+
*/
|
|
389
|
+
listTemplates() {
|
|
390
|
+
return Object.entries(INDUSTRY_TEMPLATES).map(([id, template]) => ({
|
|
391
|
+
id,
|
|
392
|
+
name: template.name,
|
|
393
|
+
description: template.description,
|
|
394
|
+
invariantCount: template.invariants.length,
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Import a template into the registry
|
|
400
|
+
* @param {string} templateId - Template ID
|
|
401
|
+
* @param {string} name - Optional custom name
|
|
402
|
+
* @returns {string|null} Library ID or null
|
|
403
|
+
*/
|
|
404
|
+
importTemplate(templateId, name = null) {
|
|
405
|
+
const template = this.getTemplate(templateId);
|
|
406
|
+
if (!template) return null;
|
|
407
|
+
|
|
408
|
+
const library = {
|
|
409
|
+
name: name || template.name,
|
|
410
|
+
description: template.description,
|
|
411
|
+
version: "1.0.0",
|
|
412
|
+
invariants: template.invariants,
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
return this.registerLibrary(library);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Merge multiple libraries into one
|
|
420
|
+
* @param {string[]} libraryIds - Library IDs to merge
|
|
421
|
+
* @param {string} name - Name for merged library
|
|
422
|
+
* @returns {string|null} Merged library ID or null
|
|
423
|
+
*/
|
|
424
|
+
mergeLibraries(libraryIds, name) {
|
|
425
|
+
const invariants = [];
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
|
|
428
|
+
for (const id of libraryIds) {
|
|
429
|
+
const library = this.getLibrary(id);
|
|
430
|
+
if (!library) continue;
|
|
431
|
+
|
|
432
|
+
for (const inv of library.invariants) {
|
|
433
|
+
if (!seen.has(inv.id)) {
|
|
434
|
+
invariants.push(inv);
|
|
435
|
+
seen.add(inv.id);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (invariants.length === 0) return null;
|
|
441
|
+
|
|
442
|
+
const merged = {
|
|
443
|
+
name,
|
|
444
|
+
description: `Merged from: ${libraryIds.join(", ")}`,
|
|
445
|
+
version: "1.0.0",
|
|
446
|
+
invariants,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
return this.registerLibrary(merged);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Save a library to disk
|
|
454
|
+
* @param {Object} library - Library to save
|
|
455
|
+
*/
|
|
456
|
+
saveLibrary(library) {
|
|
457
|
+
try {
|
|
458
|
+
if (!fs.existsSync(this.registryDir)) {
|
|
459
|
+
fs.mkdirSync(this.registryDir, { recursive: true });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const filePath = path.join(this.registryDir, `${library.id}.json`);
|
|
463
|
+
fs.writeFileSync(filePath, JSON.stringify(library, null, 2));
|
|
464
|
+
} catch (error) {
|
|
465
|
+
throw new Error(`Failed to save library: ${error.message}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Generate a unique ID for a library
|
|
471
|
+
* @param {string} name - Library name
|
|
472
|
+
* @returns {string} Generated ID
|
|
473
|
+
*/
|
|
474
|
+
generateId(name) {
|
|
475
|
+
const base = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
476
|
+
const suffix = crypto.randomBytes(4).toString("hex");
|
|
477
|
+
return `${base}-${suffix}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Calculate checksum for invariants
|
|
482
|
+
* @param {Object[]} invariants - Invariants
|
|
483
|
+
* @returns {string} Checksum
|
|
484
|
+
*/
|
|
485
|
+
calculateChecksum(invariants) {
|
|
486
|
+
const content = JSON.stringify(invariants || []);
|
|
487
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Check if a library needs update based on checksum
|
|
492
|
+
* @param {string} id - Library ID
|
|
493
|
+
* @param {Object[]} invariants - New invariants
|
|
494
|
+
* @returns {boolean} Needs update
|
|
495
|
+
*/
|
|
496
|
+
needsUpdate(id, invariants) {
|
|
497
|
+
const library = this.getLibrary(id);
|
|
498
|
+
if (!library) return true;
|
|
499
|
+
|
|
500
|
+
const newChecksum = this.calculateChecksum(invariants);
|
|
501
|
+
return library.checksum !== newChecksum;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Create a registry instance
|
|
507
|
+
* @param {Object} options - Options
|
|
508
|
+
* @returns {LawbookRegistry} Registry instance
|
|
509
|
+
*/
|
|
510
|
+
function createRegistry(options = {}) {
|
|
511
|
+
return new LawbookRegistry(options);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
module.exports = { LawbookRegistry, createRegistry, INDUSTRY_TEMPLATES };
|