@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,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lawbook Distributor
|
|
3
|
+
*
|
|
4
|
+
* Syncs org-level policies to projects via API.
|
|
5
|
+
* Manages version control and conflict resolution.
|
|
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 { parseLawbook, serializeLawbook, validateInvariant } = require("./schema.js");
|
|
17
|
+
const { lawbookLogger: log, getErrorMessage } = require("../logger.js");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} SyncResult
|
|
21
|
+
* @property {boolean} success - Sync successful
|
|
22
|
+
* @property {number} added - Invariants added
|
|
23
|
+
* @property {number} updated - Invariants updated
|
|
24
|
+
* @property {number} removed - Invariants removed
|
|
25
|
+
* @property {string[]} conflicts - Conflict descriptions
|
|
26
|
+
* @property {string} version - Synced version
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Merge strategies for conflict resolution
|
|
31
|
+
*/
|
|
32
|
+
const MERGE_STRATEGIES = {
|
|
33
|
+
ORG_WINS: "org_wins", // Org policies override local
|
|
34
|
+
LOCAL_WINS: "local_wins", // Local policies override org
|
|
35
|
+
STRICT: "strict", // Fail on conflicts
|
|
36
|
+
MERGE: "merge", // Merge with local taking precedence on conflicts
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Lawbook Distributor class
|
|
41
|
+
*/
|
|
42
|
+
class LawbookDistributor {
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
45
|
+
this.apiEndpoint = options.apiEndpoint || process.env.VIBECHECK_API_URL || "https://api.vibecheck.ai";
|
|
46
|
+
this.apiKey = options.apiKey || process.env.VIBECHECK_API_KEY;
|
|
47
|
+
this.cacheDir = path.join(this.projectRoot, ".vibecheck", "lawbook-cache");
|
|
48
|
+
this.syncHistoryPath = path.join(this.projectRoot, ".vibecheck", "lawbook-sync.json");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch org lawbook from API
|
|
53
|
+
* @param {string} orgId - Organization ID
|
|
54
|
+
* @param {string} libraryId - Library ID (optional)
|
|
55
|
+
* @returns {Object} Fetched lawbook
|
|
56
|
+
*/
|
|
57
|
+
async fetchOrgLawbook(orgId, libraryId = "default") {
|
|
58
|
+
try {
|
|
59
|
+
const url = `${this.apiEndpoint}/v1/orgs/${orgId}/lawbooks/${libraryId}`;
|
|
60
|
+
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
headers: {
|
|
63
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Failed to fetch lawbook: ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
|
|
74
|
+
// Cache the result
|
|
75
|
+
this.cacheLawbook(orgId, libraryId, data);
|
|
76
|
+
|
|
77
|
+
return data;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
// Try to use cached version
|
|
80
|
+
const cached = this.getCachedLawbook(orgId, libraryId);
|
|
81
|
+
if (cached) {
|
|
82
|
+
log.warn(`Using cached lawbook: ${getErrorMessage(error)}`);
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sync org lawbook to local project
|
|
91
|
+
* @param {string} orgId - Organization ID
|
|
92
|
+
* @param {Object} options - Sync options
|
|
93
|
+
* @returns {SyncResult} Sync result
|
|
94
|
+
*/
|
|
95
|
+
async sync(orgId, options = {}) {
|
|
96
|
+
const {
|
|
97
|
+
libraryId = "default",
|
|
98
|
+
strategy = MERGE_STRATEGIES.MERGE,
|
|
99
|
+
dryRun = false,
|
|
100
|
+
} = options;
|
|
101
|
+
|
|
102
|
+
const result = {
|
|
103
|
+
success: false,
|
|
104
|
+
added: 0,
|
|
105
|
+
updated: 0,
|
|
106
|
+
removed: 0,
|
|
107
|
+
conflicts: [],
|
|
108
|
+
version: null,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Fetch org lawbook
|
|
113
|
+
const orgLawbook = await this.fetchOrgLawbook(orgId, libraryId);
|
|
114
|
+
result.version = orgLawbook.version;
|
|
115
|
+
|
|
116
|
+
// Load local lawbook
|
|
117
|
+
const localPath = path.join(this.projectRoot, ".vibecheck", "invariants.yaml");
|
|
118
|
+
let localLawbook = null;
|
|
119
|
+
|
|
120
|
+
if (fs.existsSync(localPath)) {
|
|
121
|
+
const content = fs.readFileSync(localPath, "utf-8");
|
|
122
|
+
localLawbook = parseLawbook(content);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Merge lawbooks
|
|
126
|
+
const merged = this.mergeLawbooks(orgLawbook, localLawbook, strategy, result);
|
|
127
|
+
|
|
128
|
+
// Apply changes if not dry run
|
|
129
|
+
if (!dryRun && result.conflicts.length === 0) {
|
|
130
|
+
const dir = path.dirname(localPath);
|
|
131
|
+
if (!fs.existsSync(dir)) {
|
|
132
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const content = serializeLawbook(merged);
|
|
136
|
+
fs.writeFileSync(localPath, content);
|
|
137
|
+
|
|
138
|
+
// Record sync
|
|
139
|
+
this.recordSync(orgId, libraryId, result.version);
|
|
140
|
+
|
|
141
|
+
result.success = true;
|
|
142
|
+
} else if (dryRun) {
|
|
143
|
+
result.success = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
result.success = false;
|
|
149
|
+
result.conflicts.push(`Sync error: ${error.message}`);
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Merge org and local lawbooks
|
|
156
|
+
* @param {Object} orgLawbook - Org lawbook
|
|
157
|
+
* @param {Object} localLawbook - Local lawbook
|
|
158
|
+
* @param {string} strategy - Merge strategy
|
|
159
|
+
* @param {Object} result - Result object to update
|
|
160
|
+
* @returns {Object} Merged lawbook
|
|
161
|
+
*/
|
|
162
|
+
mergeLawbooks(orgLawbook, localLawbook, strategy, result) {
|
|
163
|
+
const merged = {
|
|
164
|
+
version: orgLawbook.version,
|
|
165
|
+
name: localLawbook?.name || orgLawbook.name,
|
|
166
|
+
description: localLawbook?.description || orgLawbook.description,
|
|
167
|
+
extends: orgLawbook.name,
|
|
168
|
+
invariants: [],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Index invariants by ID
|
|
172
|
+
const orgInvariants = new Map();
|
|
173
|
+
const localInvariants = new Map();
|
|
174
|
+
|
|
175
|
+
for (const inv of orgLawbook.invariants || []) {
|
|
176
|
+
inv._source = "org";
|
|
177
|
+
orgInvariants.set(inv.id, inv);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const inv of localLawbook?.invariants || []) {
|
|
181
|
+
inv._source = "local";
|
|
182
|
+
localInvariants.set(inv.id, inv);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Process org invariants
|
|
186
|
+
for (const [id, orgInv] of orgInvariants) {
|
|
187
|
+
const localInv = localInvariants.get(id);
|
|
188
|
+
|
|
189
|
+
if (!localInv) {
|
|
190
|
+
// New from org
|
|
191
|
+
merged.invariants.push({ ...orgInv, _source: undefined });
|
|
192
|
+
result.added++;
|
|
193
|
+
} else {
|
|
194
|
+
// Exists in both - check for conflicts
|
|
195
|
+
const conflict = this.detectConflict(orgInv, localInv);
|
|
196
|
+
|
|
197
|
+
if (conflict) {
|
|
198
|
+
switch (strategy) {
|
|
199
|
+
case MERGE_STRATEGIES.ORG_WINS:
|
|
200
|
+
merged.invariants.push({ ...orgInv, _source: undefined });
|
|
201
|
+
result.updated++;
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case MERGE_STRATEGIES.LOCAL_WINS:
|
|
205
|
+
merged.invariants.push({ ...localInv, _source: undefined });
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case MERGE_STRATEGIES.STRICT:
|
|
209
|
+
result.conflicts.push(`Conflict on '${id}': ${conflict}`);
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case MERGE_STRATEGIES.MERGE:
|
|
213
|
+
default:
|
|
214
|
+
// Merge with local taking precedence
|
|
215
|
+
const mergedInv = { ...orgInv, ...localInv, _source: undefined };
|
|
216
|
+
merged.invariants.push(mergedInv);
|
|
217
|
+
result.updated++;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
// No conflict - use org version (might have updates)
|
|
222
|
+
merged.invariants.push({ ...orgInv, _source: undefined });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
localInvariants.delete(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add remaining local-only invariants
|
|
230
|
+
for (const [, localInv] of localInvariants) {
|
|
231
|
+
merged.invariants.push({ ...localInv, _source: undefined });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return merged;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Detect conflict between org and local invariant
|
|
239
|
+
* @param {Object} orgInv - Org invariant
|
|
240
|
+
* @param {Object} localInv - Local invariant
|
|
241
|
+
* @returns {string|null} Conflict description or null
|
|
242
|
+
*/
|
|
243
|
+
detectConflict(orgInv, localInv) {
|
|
244
|
+
// Check for rule type conflict
|
|
245
|
+
if (orgInv.rule !== localInv.rule) {
|
|
246
|
+
return `Rule type mismatch: org='${orgInv.rule}', local='${localInv.rule}'`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for severity conflict
|
|
250
|
+
if (orgInv.severity !== localInv.severity) {
|
|
251
|
+
return `Severity mismatch: org='${orgInv.severity}', local='${localInv.severity}'`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Check for scope conflict
|
|
255
|
+
if (orgInv.scope !== localInv.scope) {
|
|
256
|
+
return `Scope mismatch: org='${orgInv.scope}', local='${localInv.scope}'`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Cache a lawbook locally
|
|
264
|
+
* @param {string} orgId - Org ID
|
|
265
|
+
* @param {string} libraryId - Library ID
|
|
266
|
+
* @param {Object} lawbook - Lawbook to cache
|
|
267
|
+
*/
|
|
268
|
+
cacheLawbook(orgId, libraryId, lawbook) {
|
|
269
|
+
try {
|
|
270
|
+
if (!fs.existsSync(this.cacheDir)) {
|
|
271
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const cachePath = path.join(this.cacheDir, `${orgId}-${libraryId}.json`);
|
|
275
|
+
fs.writeFileSync(cachePath, JSON.stringify({
|
|
276
|
+
lawbook,
|
|
277
|
+
cachedAt: new Date().toISOString(),
|
|
278
|
+
}, null, 2));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
log.warn(`Failed to cache lawbook: ${getErrorMessage(error)}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get cached lawbook
|
|
286
|
+
* @param {string} orgId - Org ID
|
|
287
|
+
* @param {string} libraryId - Library ID
|
|
288
|
+
* @returns {Object|null} Cached lawbook or null
|
|
289
|
+
*/
|
|
290
|
+
getCachedLawbook(orgId, libraryId) {
|
|
291
|
+
try {
|
|
292
|
+
const cachePath = path.join(this.cacheDir, `${orgId}-${libraryId}.json`);
|
|
293
|
+
|
|
294
|
+
if (fs.existsSync(cachePath)) {
|
|
295
|
+
const content = fs.readFileSync(cachePath, "utf-8");
|
|
296
|
+
const cached = JSON.parse(content);
|
|
297
|
+
|
|
298
|
+
// Check cache age (24 hours max)
|
|
299
|
+
const cacheAge = Date.now() - new Date(cached.cachedAt).getTime();
|
|
300
|
+
if (cacheAge < 24 * 60 * 60 * 1000) {
|
|
301
|
+
return cached.lawbook;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
log.warn(`Failed to read cached lawbook: ${getErrorMessage(error)}`);
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Record a sync event
|
|
312
|
+
* @param {string} orgId - Org ID
|
|
313
|
+
* @param {string} libraryId - Library ID
|
|
314
|
+
* @param {string} version - Synced version
|
|
315
|
+
*/
|
|
316
|
+
recordSync(orgId, libraryId, version) {
|
|
317
|
+
try {
|
|
318
|
+
let history = [];
|
|
319
|
+
|
|
320
|
+
if (fs.existsSync(this.syncHistoryPath)) {
|
|
321
|
+
const content = fs.readFileSync(this.syncHistoryPath, "utf-8");
|
|
322
|
+
history = JSON.parse(content);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
history.push({
|
|
326
|
+
orgId,
|
|
327
|
+
libraryId,
|
|
328
|
+
version,
|
|
329
|
+
syncedAt: new Date().toISOString(),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Keep last 50 entries
|
|
333
|
+
if (history.length > 50) {
|
|
334
|
+
history = history.slice(-50);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const dir = path.dirname(this.syncHistoryPath);
|
|
338
|
+
if (!fs.existsSync(dir)) {
|
|
339
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fs.writeFileSync(this.syncHistoryPath, JSON.stringify(history, null, 2));
|
|
343
|
+
} catch (error) {
|
|
344
|
+
log.warn(`Failed to record sync: ${getErrorMessage(error)}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get sync history
|
|
350
|
+
* @returns {Object[]} Sync history
|
|
351
|
+
*/
|
|
352
|
+
getSyncHistory() {
|
|
353
|
+
try {
|
|
354
|
+
if (fs.existsSync(this.syncHistoryPath)) {
|
|
355
|
+
const content = fs.readFileSync(this.syncHistoryPath, "utf-8");
|
|
356
|
+
return JSON.parse(content);
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
log.warn(`Failed to read sync history: ${getErrorMessage(error)}`);
|
|
360
|
+
}
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Check if sync is needed
|
|
366
|
+
* @param {string} orgId - Org ID
|
|
367
|
+
* @param {string} libraryId - Library ID
|
|
368
|
+
* @returns {Object} Sync status
|
|
369
|
+
*/
|
|
370
|
+
async checkSyncStatus(orgId, libraryId = "default") {
|
|
371
|
+
const history = this.getSyncHistory();
|
|
372
|
+
const lastSync = history.filter(h => h.orgId === orgId && h.libraryId === libraryId).pop();
|
|
373
|
+
|
|
374
|
+
// Fetch current version from API
|
|
375
|
+
try {
|
|
376
|
+
const url = `${this.apiEndpoint}/v1/orgs/${orgId}/lawbooks/${libraryId}/version`;
|
|
377
|
+
const response = await fetch(url, {
|
|
378
|
+
headers: {
|
|
379
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (response.ok) {
|
|
384
|
+
const data = await response.json();
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
needsSync: !lastSync || lastSync.version !== data.version,
|
|
388
|
+
currentVersion: data.version,
|
|
389
|
+
lastSyncVersion: lastSync?.version || null,
|
|
390
|
+
lastSyncTime: lastSync?.syncedAt || null,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
// Ignore - will use cached data
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
needsSync: !lastSync,
|
|
399
|
+
currentVersion: null,
|
|
400
|
+
lastSyncVersion: lastSync?.version || null,
|
|
401
|
+
lastSyncTime: lastSync?.syncedAt || null,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Publish local invariants to org (requires admin)
|
|
407
|
+
* @param {string} orgId - Org ID
|
|
408
|
+
* @param {Object[]} invariants - Invariants to publish
|
|
409
|
+
* @returns {Object} Publish result
|
|
410
|
+
*/
|
|
411
|
+
async publishToOrg(orgId, invariants, options = {}) {
|
|
412
|
+
const {
|
|
413
|
+
libraryId = "default",
|
|
414
|
+
message = "Published from local project",
|
|
415
|
+
} = options;
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
// Validate all invariants first
|
|
419
|
+
for (const inv of invariants) {
|
|
420
|
+
const validation = validateInvariant(inv);
|
|
421
|
+
if (!validation.valid) {
|
|
422
|
+
throw new Error(`Invalid invariant '${inv.id}': ${validation.errors.map(e => e.message).join(", ")}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const url = `${this.apiEndpoint}/v1/orgs/${orgId}/lawbooks/${libraryId}/publish`;
|
|
427
|
+
|
|
428
|
+
const response = await fetch(url, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: {
|
|
431
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
432
|
+
"Content-Type": "application/json",
|
|
433
|
+
},
|
|
434
|
+
body: JSON.stringify({
|
|
435
|
+
invariants,
|
|
436
|
+
message,
|
|
437
|
+
publishedAt: new Date().toISOString(),
|
|
438
|
+
}),
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (!response.ok) {
|
|
442
|
+
const error = await response.json().catch(() => ({}));
|
|
443
|
+
throw new Error(error.message || `Publish failed: ${response.status}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return await response.json();
|
|
447
|
+
} catch (error) {
|
|
448
|
+
return {
|
|
449
|
+
success: false,
|
|
450
|
+
error: error.message,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Create a distributor instance
|
|
458
|
+
* @param {Object} options - Options
|
|
459
|
+
* @returns {LawbookDistributor} Distributor instance
|
|
460
|
+
*/
|
|
461
|
+
function createDistributor(options = {}) {
|
|
462
|
+
return new LawbookDistributor(options);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = { LawbookDistributor, createDistributor, MERGE_STRATEGIES };
|