@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.
Files changed (84) hide show
  1. package/bin/registry.js +192 -5
  2. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  3. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  4. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  5. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  6. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  7. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  8. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  11. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  12. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  14. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  15. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  16. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  17. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  18. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  19. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  20. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  21. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  22. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  23. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  24. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  25. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  26. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  27. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  28. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  29. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  30. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  31. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  32. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  35. package/bin/runners/lib/analyzers.js +81 -18
  36. package/bin/runners/lib/authority-badge.js +425 -0
  37. package/bin/runners/lib/cli-output.js +7 -1
  38. package/bin/runners/lib/error-handler.js +16 -9
  39. package/bin/runners/lib/exit-codes.js +275 -0
  40. package/bin/runners/lib/global-flags.js +37 -0
  41. package/bin/runners/lib/help-formatter.js +413 -0
  42. package/bin/runners/lib/logger.js +38 -0
  43. package/bin/runners/lib/unified-cli-output.js +604 -0
  44. package/bin/runners/lib/upsell.js +148 -0
  45. package/bin/runners/runApprove.js +1200 -0
  46. package/bin/runners/runAuth.js +324 -95
  47. package/bin/runners/runCheckpoint.js +39 -21
  48. package/bin/runners/runClassify.js +859 -0
  49. package/bin/runners/runContext.js +136 -24
  50. package/bin/runners/runDoctor.js +108 -68
  51. package/bin/runners/runFix.js +6 -5
  52. package/bin/runners/runGuard.js +212 -118
  53. package/bin/runners/runInit.js +3 -2
  54. package/bin/runners/runMcp.js +130 -52
  55. package/bin/runners/runPolish.js +43 -20
  56. package/bin/runners/runProve.js +1 -2
  57. package/bin/runners/runReport.js +3 -2
  58. package/bin/runners/runScan.js +63 -44
  59. package/bin/runners/runShip.js +3 -4
  60. package/bin/runners/runValidate.js +19 -2
  61. package/bin/runners/runWatch.js +104 -53
  62. package/bin/vibecheck.js +106 -19
  63. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  64. package/mcp-server/agent-firewall-interceptor.js +367 -31
  65. package/mcp-server/authority-tools.js +569 -0
  66. package/mcp-server/conductor/conflict-resolver.js +588 -0
  67. package/mcp-server/conductor/execution-planner.js +544 -0
  68. package/mcp-server/conductor/index.js +377 -0
  69. package/mcp-server/conductor/lock-manager.js +615 -0
  70. package/mcp-server/conductor/request-queue.js +550 -0
  71. package/mcp-server/conductor/session-manager.js +500 -0
  72. package/mcp-server/conductor/tools.js +510 -0
  73. package/mcp-server/index.js +1149 -243
  74. package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
  75. package/mcp-server/lib/logger.cjs +30 -0
  76. package/mcp-server/logger.js +173 -0
  77. package/mcp-server/package.json +2 -2
  78. package/mcp-server/premium-tools.js +2 -2
  79. package/mcp-server/tier-auth.js +245 -35
  80. package/mcp-server/truth-firewall-tools.js +145 -15
  81. package/mcp-server/vibecheck-tools.js +2 -2
  82. package/package.json +2 -3
  83. package/mcp-server/index.old.js +0 -4137
  84. package/mcp-server/package-lock.json +0 -165
@@ -0,0 +1,544 @@
1
+ /**
2
+ * Conductor Execution Planner
3
+ *
4
+ * Plans deterministic execution ordering for multi-agent operations.
5
+ * Uses topological sorting to respect dependencies.
6
+ *
7
+ * Codename: Conductor
8
+ */
9
+
10
+ "use strict";
11
+
12
+ import path from "path";
13
+
14
+ /**
15
+ * @typedef {Object} ExecutionStep
16
+ * @property {number} order - Execution order (0 = first)
17
+ * @property {string} proposalId - Proposal to execute
18
+ * @property {string} agentId - Agent that owns the proposal
19
+ * @property {string[]} dependsOn - Proposal IDs this depends on
20
+ * @property {string[]} blockedBy - Proposal IDs blocking this
21
+ * @property {boolean} canExecuteNow - Can execute immediately
22
+ * @property {string} status - pending, executing, completed, failed
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} ExecutionPlan
27
+ * @property {string} planId - Unique plan ID
28
+ * @property {ExecutionStep[]} steps - Ordered execution steps
29
+ * @property {boolean} canExecute - Can the plan be executed
30
+ * @property {Object[]} conflicts - Unresolved conflicts blocking execution
31
+ * @property {Date} createdAt - When plan was created
32
+ */
33
+
34
+ /**
35
+ * Tier priority mapping (higher = more priority)
36
+ */
37
+ const TIER_PRIORITY = {
38
+ ENTERPRISE: 4,
39
+ PRO: 3,
40
+ STARTER: 2,
41
+ FREE: 1,
42
+ };
43
+
44
+ /**
45
+ * Execution Planner class
46
+ */
47
+ class ExecutionPlanner {
48
+ constructor(conflictResolver = null) {
49
+ this.conflictResolver = conflictResolver;
50
+ this.plans = new Map(); // planId -> ExecutionPlan
51
+ this.executionHistory = []; // For auditing
52
+ }
53
+
54
+ /**
55
+ * Set the conflict resolver
56
+ * @param {Object} resolver - Conflict resolver instance
57
+ */
58
+ setConflictResolver(resolver) {
59
+ this.conflictResolver = resolver;
60
+ }
61
+
62
+ /**
63
+ * Create an execution plan from proposals
64
+ * @param {Object[]} proposals - Proposals to plan
65
+ * @param {Object} options - Planning options
66
+ * @returns {ExecutionPlan} Execution plan
67
+ */
68
+ createPlan(proposals, options = {}) {
69
+ const planId = `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
70
+
71
+ // Build dependency graph
72
+ const graph = this.buildDependencyGraph(proposals);
73
+
74
+ // Check for cycles
75
+ const cycles = this.detectCycles(graph);
76
+ if (cycles.length > 0) {
77
+ return {
78
+ planId,
79
+ steps: [],
80
+ canExecute: false,
81
+ conflicts: cycles.map(c => ({
82
+ type: "circular_dependency",
83
+ proposals: c,
84
+ description: `Circular dependency detected: ${c.join(" -> ")}`,
85
+ })),
86
+ createdAt: new Date(),
87
+ };
88
+ }
89
+
90
+ // Topological sort
91
+ const sortedOrder = this.topologicalSort(graph);
92
+
93
+ if (!sortedOrder) {
94
+ return {
95
+ planId,
96
+ steps: [],
97
+ canExecute: false,
98
+ conflicts: [{ type: "sort_failed", description: "Could not determine execution order" }],
99
+ createdAt: new Date(),
100
+ };
101
+ }
102
+
103
+ // Apply tier-based priority within independent groups
104
+ const prioritizedOrder = this.applyTierPriority(sortedOrder, proposals);
105
+
106
+ // Build execution steps
107
+ const steps = this.buildExecutionSteps(prioritizedOrder, proposals, graph);
108
+
109
+ // Check for blocking conflicts
110
+ const blockingConflicts = this.getBlockingConflicts(proposals);
111
+
112
+ const plan = {
113
+ planId,
114
+ steps,
115
+ canExecute: blockingConflicts.length === 0,
116
+ conflicts: blockingConflicts,
117
+ createdAt: new Date(),
118
+ };
119
+
120
+ this.plans.set(planId, plan);
121
+
122
+ return plan;
123
+ }
124
+
125
+ /**
126
+ * Build a dependency graph from proposals
127
+ * @param {Object[]} proposals - Proposals to analyze
128
+ * @returns {Map} Dependency graph (proposalId -> Set<dependsOn>)
129
+ */
130
+ buildDependencyGraph(proposals) {
131
+ const graph = new Map();
132
+
133
+ // Initialize all nodes
134
+ for (const proposal of proposals) {
135
+ graph.set(proposal.proposalId, new Set());
136
+ }
137
+
138
+ // Build dependencies based on file overlaps and assumptions
139
+ for (const proposalA of proposals) {
140
+ const filesA = this.extractAffectedFiles(proposalA);
141
+ const assumptionsA = proposalA.assumptions || [];
142
+
143
+ for (const proposalB of proposals) {
144
+ if (proposalA.proposalId === proposalB.proposalId) continue;
145
+
146
+ const operationsB = proposalB.operations || [];
147
+
148
+ // Check if A's assumptions depend on B's operations
149
+ for (const assumption of assumptionsA) {
150
+ for (const operation of operationsB) {
151
+ if (this.assumptionDependsOnOperation(assumption, operation)) {
152
+ // A depends on B (B must execute first)
153
+ graph.get(proposalA.proposalId).add(proposalB.proposalId);
154
+ }
155
+ }
156
+ }
157
+
158
+ // Check for file-based dependencies
159
+ const filesB = this.extractAffectedFiles(proposalB);
160
+ for (const fileA of filesA) {
161
+ for (const fileB of filesB) {
162
+ const dependency = this.checkFileDependency(fileA, fileB, proposalA, proposalB);
163
+ if (dependency) {
164
+ graph.get(dependency.dependent).add(dependency.dependsOn);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ return graph;
172
+ }
173
+
174
+ /**
175
+ * Check if an assumption depends on an operation
176
+ * @param {Object} assumption - Assumption to check
177
+ * @param {Object} operation - Operation to check against
178
+ * @returns {boolean} Has dependency
179
+ */
180
+ assumptionDependsOnOperation(assumption, operation) {
181
+ const assumptionTarget = this.normalizePath(assumption.target || assumption.path || "");
182
+ const operationTarget = this.normalizePath(operation.path || operation.file || "");
183
+
184
+ if (!assumptionTarget || !operationTarget) return false;
185
+
186
+ // If assumption is about a file that the operation creates/modifies
187
+ if (assumptionTarget === operationTarget) {
188
+ // file_exists depends on create
189
+ if (assumption.type === "file_exists" && operation.type === "create") {
190
+ return true;
191
+ }
192
+ // file_contains depends on modify that adds content
193
+ if (assumption.type === "file_contains" && operation.type === "modify") {
194
+ return true;
195
+ }
196
+ }
197
+
198
+ return false;
199
+ }
200
+
201
+ /**
202
+ * Check for file-based dependency between operations
203
+ * @param {Object} fileA - First file operation
204
+ * @param {Object} fileB - Second file operation
205
+ * @param {Object} proposalA - First proposal
206
+ * @param {Object} proposalB - Second proposal
207
+ * @returns {Object|null} Dependency info or null
208
+ */
209
+ checkFileDependency(fileA, fileB, proposalA, proposalB) {
210
+ const pathA = this.normalizePath(fileA.path);
211
+ const pathB = this.normalizePath(fileB.path);
212
+
213
+ if (pathA !== pathB) return null;
214
+
215
+ // Create before modify/read
216
+ if (fileB.operation === "create" && fileA.operation === "modify") {
217
+ return { dependent: proposalA.proposalId, dependsOn: proposalB.proposalId };
218
+ }
219
+ if (fileA.operation === "create" && fileB.operation === "modify") {
220
+ return { dependent: proposalB.proposalId, dependsOn: proposalA.proposalId };
221
+ }
222
+
223
+ return null;
224
+ }
225
+
226
+ /**
227
+ * Extract affected files from a proposal
228
+ * @param {Object} proposal - Proposal to analyze
229
+ * @returns {Object[]} Affected files
230
+ */
231
+ extractAffectedFiles(proposal) {
232
+ const files = [];
233
+ for (const op of proposal.operations || []) {
234
+ if (op.path || op.file || op.filePath) {
235
+ files.push({
236
+ path: op.path || op.file || op.filePath,
237
+ operation: op.type || op.operation || "modify",
238
+ });
239
+ }
240
+ }
241
+ return files;
242
+ }
243
+
244
+ /**
245
+ * Detect cycles in the dependency graph
246
+ * @param {Map} graph - Dependency graph
247
+ * @returns {string[][]} Detected cycles
248
+ */
249
+ detectCycles(graph) {
250
+ const cycles = [];
251
+ const visited = new Set();
252
+ const inStack = new Set();
253
+
254
+ const dfs = (node, path) => {
255
+ if (inStack.has(node)) {
256
+ const cycleStart = path.indexOf(node);
257
+ cycles.push(path.slice(cycleStart));
258
+ return;
259
+ }
260
+
261
+ if (visited.has(node)) return;
262
+
263
+ visited.add(node);
264
+ inStack.add(node);
265
+ path.push(node);
266
+
267
+ const deps = graph.get(node) || new Set();
268
+ for (const dep of deps) {
269
+ dfs(dep, [...path]);
270
+ }
271
+
272
+ inStack.delete(node);
273
+ };
274
+
275
+ for (const node of graph.keys()) {
276
+ if (!visited.has(node)) {
277
+ dfs(node, []);
278
+ }
279
+ }
280
+
281
+ return cycles;
282
+ }
283
+
284
+ /**
285
+ * Perform topological sort
286
+ * @param {Map} graph - Dependency graph
287
+ * @returns {string[]|null} Sorted order or null if not possible
288
+ */
289
+ topologicalSort(graph) {
290
+ const inDegree = new Map();
291
+ const sorted = [];
292
+ const queue = [];
293
+
294
+ // Initialize in-degrees
295
+ for (const node of graph.keys()) {
296
+ inDegree.set(node, 0);
297
+ }
298
+
299
+ // Calculate in-degrees
300
+ for (const [, deps] of graph) {
301
+ for (const dep of deps) {
302
+ inDegree.set(dep, (inDegree.get(dep) || 0) + 1);
303
+ }
304
+ }
305
+
306
+ // Find nodes with no dependencies
307
+ for (const [node, degree] of inDegree) {
308
+ if (degree === 0) {
309
+ queue.push(node);
310
+ }
311
+ }
312
+
313
+ // Process queue
314
+ while (queue.length > 0) {
315
+ const node = queue.shift();
316
+ sorted.push(node);
317
+
318
+ const deps = graph.get(node) || new Set();
319
+ for (const dep of deps) {
320
+ inDegree.set(dep, inDegree.get(dep) - 1);
321
+ if (inDegree.get(dep) === 0) {
322
+ queue.push(dep);
323
+ }
324
+ }
325
+ }
326
+
327
+ // Check if all nodes were processed
328
+ if (sorted.length !== graph.size) {
329
+ return null; // Cycle detected
330
+ }
331
+
332
+ // Reverse to get correct order (dependencies first)
333
+ return sorted.reverse();
334
+ }
335
+
336
+ /**
337
+ * Apply tier-based priority to execution order
338
+ * @param {string[]} sortedOrder - Topologically sorted order
339
+ * @param {Object[]} proposals - Proposals
340
+ * @returns {string[]} Priority-adjusted order
341
+ */
342
+ applyTierPriority(sortedOrder, proposals) {
343
+ const proposalMap = new Map(proposals.map(p => [p.proposalId, p]));
344
+
345
+ // Group by dependency level
346
+ const levels = this.groupByDependencyLevel(sortedOrder, proposalMap);
347
+
348
+ // Sort within each level by tier priority
349
+ const prioritized = [];
350
+ for (const level of levels) {
351
+ level.sort((a, b) => {
352
+ const proposalA = proposalMap.get(a);
353
+ const proposalB = proposalMap.get(b);
354
+ const priorityA = TIER_PRIORITY[proposalA?.tier] || 1;
355
+ const priorityB = TIER_PRIORITY[proposalB?.tier] || 1;
356
+ return priorityB - priorityA; // Higher priority first
357
+ });
358
+ prioritized.push(...level);
359
+ }
360
+
361
+ return prioritized;
362
+ }
363
+
364
+ /**
365
+ * Group proposals by dependency level
366
+ * @param {string[]} sorted - Sorted order
367
+ * @param {Map} proposalMap - Proposal map
368
+ * @returns {string[][]} Grouped levels
369
+ */
370
+ groupByDependencyLevel(sorted, proposalMap) {
371
+ // Simple grouping - items that can execute in parallel at each step
372
+ // For now, return as individual levels
373
+ return sorted.map(id => [id]);
374
+ }
375
+
376
+ /**
377
+ * Build execution steps from sorted order
378
+ * @param {string[]} order - Execution order
379
+ * @param {Object[]} proposals - Proposals
380
+ * @param {Map} graph - Dependency graph
381
+ * @returns {ExecutionStep[]} Execution steps
382
+ */
383
+ buildExecutionSteps(order, proposals, graph) {
384
+ const proposalMap = new Map(proposals.map(p => [p.proposalId, p]));
385
+ const steps = [];
386
+ const completed = new Set();
387
+
388
+ for (let i = 0; i < order.length; i++) {
389
+ const proposalId = order[i];
390
+ const proposal = proposalMap.get(proposalId);
391
+
392
+ if (!proposal) continue;
393
+
394
+ const dependencies = Array.from(graph.get(proposalId) || []);
395
+ const blockedBy = dependencies.filter(dep => !completed.has(dep));
396
+
397
+ steps.push({
398
+ order: i,
399
+ proposalId,
400
+ agentId: proposal.agentId,
401
+ sessionId: proposal.sessionId,
402
+ tier: proposal.tier,
403
+ intent: proposal.intent,
404
+ dependsOn: dependencies,
405
+ blockedBy,
406
+ canExecuteNow: blockedBy.length === 0,
407
+ status: "pending",
408
+ });
409
+
410
+ // For planning purposes, mark as "completed"
411
+ completed.add(proposalId);
412
+ }
413
+
414
+ return steps;
415
+ }
416
+
417
+ /**
418
+ * Get blocking conflicts from the conflict resolver
419
+ * @param {Object[]} proposals - Proposals to check
420
+ * @returns {Object[]} Blocking conflicts
421
+ */
422
+ getBlockingConflicts(proposals) {
423
+ if (!this.conflictResolver) return [];
424
+
425
+ const blocking = [];
426
+
427
+ for (const proposal of proposals) {
428
+ const result = this.conflictResolver.canProceed(proposal.proposalId);
429
+ if (!result.canProceed) {
430
+ blocking.push(...result.mustResolve);
431
+ }
432
+ }
433
+
434
+ return blocking;
435
+ }
436
+
437
+ /**
438
+ * Update step status
439
+ * @param {string} planId - Plan ID
440
+ * @param {string} proposalId - Proposal ID
441
+ * @param {string} status - New status
442
+ */
443
+ updateStepStatus(planId, proposalId, status) {
444
+ const plan = this.plans.get(planId);
445
+ if (!plan) return;
446
+
447
+ const step = plan.steps.find(s => s.proposalId === proposalId);
448
+ if (step) {
449
+ step.status = status;
450
+
451
+ // Update canExecuteNow for dependent steps
452
+ if (status === "completed") {
453
+ for (const s of plan.steps) {
454
+ s.blockedBy = s.blockedBy.filter(b => b !== proposalId);
455
+ s.canExecuteNow = s.blockedBy.length === 0 && s.status === "pending";
456
+ }
457
+ }
458
+
459
+ // Record in history
460
+ this.executionHistory.push({
461
+ planId,
462
+ proposalId,
463
+ status,
464
+ timestamp: new Date(),
465
+ });
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Get next executable steps from a plan
471
+ * @param {string} planId - Plan ID
472
+ * @returns {ExecutionStep[]} Executable steps
473
+ */
474
+ getNextExecutableSteps(planId) {
475
+ const plan = this.plans.get(planId);
476
+ if (!plan) return [];
477
+
478
+ return plan.steps.filter(s => s.canExecuteNow && s.status === "pending");
479
+ }
480
+
481
+ /**
482
+ * Check if plan is complete
483
+ * @param {string} planId - Plan ID
484
+ * @returns {boolean} Is complete
485
+ */
486
+ isPlanComplete(planId) {
487
+ const plan = this.plans.get(planId);
488
+ if (!plan) return false;
489
+
490
+ return plan.steps.every(s => s.status === "completed" || s.status === "failed");
491
+ }
492
+
493
+ /**
494
+ * Get plan summary
495
+ * @param {string} planId - Plan ID
496
+ * @returns {Object} Plan summary
497
+ */
498
+ getPlanSummary(planId) {
499
+ const plan = this.plans.get(planId);
500
+ if (!plan) return null;
501
+
502
+ const pending = plan.steps.filter(s => s.status === "pending").length;
503
+ const executing = plan.steps.filter(s => s.status === "executing").length;
504
+ const completed = plan.steps.filter(s => s.status === "completed").length;
505
+ const failed = plan.steps.filter(s => s.status === "failed").length;
506
+
507
+ return {
508
+ planId,
509
+ totalSteps: plan.steps.length,
510
+ pending,
511
+ executing,
512
+ completed,
513
+ failed,
514
+ canExecute: plan.canExecute,
515
+ isComplete: pending === 0 && executing === 0,
516
+ conflicts: plan.conflicts.length,
517
+ };
518
+ }
519
+
520
+ /**
521
+ * Normalize a file path
522
+ * @param {string} filePath - Path to normalize
523
+ * @returns {string} Normalized path
524
+ */
525
+ normalizePath(filePath) {
526
+ if (!filePath) return "";
527
+ return path.resolve(filePath).replace(/\\/g, "/").toLowerCase();
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Create a new execution planner
533
+ * @param {Object} conflictResolver - Optional conflict resolver
534
+ * @returns {ExecutionPlanner} New planner
535
+ */
536
+ function createExecutionPlanner(conflictResolver = null) {
537
+ return new ExecutionPlanner(conflictResolver);
538
+ }
539
+
540
+ export {
541
+ ExecutionPlanner,
542
+ createExecutionPlanner,
543
+ TIER_PRIORITY,
544
+ };