claude-flow 3.5.20 → 3.5.22
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/.claude/helpers/hook-handler.cjs +4 -2
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +698 -55
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +11 -5
- package/v3/@claude-flow/cli/dist/src/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/init/settings-generator.js +17 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/coordination-tools.js +191 -12
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.js +224 -23
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +1 -0
- package/v3/@claude-flow/cli/dist/src/memory/ewc-consolidation.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/memory/ewc-consolidation.js +59 -0
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +53 -0
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +225 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +27 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/index.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/index.js +12 -0
- package/v3/@claude-flow/cli/dist/src/services/ruvector-training.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/services/ruvector-training.js +223 -39
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +33 -5
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -68,7 +68,8 @@ const trainCommand = {
|
|
|
68
68
|
});
|
|
69
69
|
if (initResult.success) {
|
|
70
70
|
wasmFeatures = initResult.features;
|
|
71
|
-
|
|
71
|
+
const backendLabel = initResult.backend === 'wasm' ? 'WASM' : 'JS fallback';
|
|
72
|
+
spinner.setText(`RuVector initialized [${backendLabel}]: ${wasmFeatures.join(', ')}`);
|
|
72
73
|
}
|
|
73
74
|
else {
|
|
74
75
|
output.writeln(output.warning(`WASM init failed: ${initResult.error} - falling back`));
|
|
@@ -274,7 +275,8 @@ const trainCommand = {
|
|
|
274
275
|
];
|
|
275
276
|
// Add WASM-specific metrics
|
|
276
277
|
if (useWasm && wasmFeatures.length > 0) {
|
|
277
|
-
|
|
278
|
+
const backendUsed = ruvectorStats?.backend || 'unknown';
|
|
279
|
+
tableData.push({ metric: 'Backend', value: backendUsed === 'wasm' ? 'WASM (native)' : 'JS (fallback)' }, { metric: 'WASM Features', value: wasmFeatures.slice(0, 3).join(', ') }, { metric: 'LoRA Adaptations', value: String(adaptations) }, { metric: 'Avg Loss', value: (totalLoss / Math.max(1, epochs)).toFixed(4) });
|
|
278
280
|
if (ruvectorStats?.microLoraStats) {
|
|
279
281
|
tableData.push({ metric: 'MicroLoRA Delta Norm', value: ruvectorStats.microLoraStats.deltaNorm.toFixed(6) });
|
|
280
282
|
}
|
|
@@ -299,7 +301,11 @@ const trainCommand = {
|
|
|
299
301
|
output.writeln();
|
|
300
302
|
output.writeln(output.success(`✓ ${patternsRecorded} patterns saved to ${persistence.patternsFile}`));
|
|
301
303
|
if (useWasm && wasmFeatures.length > 0) {
|
|
302
|
-
|
|
304
|
+
const backendUsed = ruvectorStats?.backend || 'unknown';
|
|
305
|
+
const backendMsg = backendUsed === 'wasm'
|
|
306
|
+
? `RuVector WASM backend: ${wasmFeatures.join(', ')}`
|
|
307
|
+
: `RuVector JS fallback (install @ruvector/learning-wasm for native speed): ${wasmFeatures.join(', ')}`;
|
|
308
|
+
output.writeln(output.highlight(`✓ ${backendMsg}`));
|
|
303
309
|
}
|
|
304
310
|
return {
|
|
305
311
|
success: true,
|
|
@@ -375,10 +381,10 @@ const statusCommand = {
|
|
|
375
381
|
: 'Not initialized',
|
|
376
382
|
},
|
|
377
383
|
{
|
|
378
|
-
component: 'RuVector
|
|
384
|
+
component: 'RuVector Training',
|
|
379
385
|
status: ruvectorStats.initialized ? output.success('Active') : output.dim('Not loaded'),
|
|
380
386
|
details: ruvectorStats.initialized
|
|
381
|
-
?
|
|
387
|
+
? `${ruvectorStats.backend === 'wasm' ? 'WASM' : 'JS fallback'} | MicroLoRA: ${ruvectorStats.totalAdaptations} adapts`
|
|
382
388
|
: 'Call neural train to initialize',
|
|
383
389
|
},
|
|
384
390
|
{
|
|
@@ -59,7 +59,7 @@ export * from './prompt.js';
|
|
|
59
59
|
export * from './commands/index.js';
|
|
60
60
|
export { MCPServerManager, createMCPServerManager, getServerManager, startMCPServer, stopMCPServer, getMCPServerStatus, type MCPServerOptions, type MCPServerStatus, } from './mcp-server.js';
|
|
61
61
|
export { initializeMemoryDatabase, generateEmbedding, generateBatchEmbeddings, storeEntry, searchEntries, getHNSWIndex, addToHNSWIndex, searchHNSWIndex, getHNSWStatus, clearHNSWIndex, quantizeInt8, dequantizeInt8, quantizedCosineSim, getQuantizationStats, batchCosineSim, softmaxAttention, topKIndices, flashAttentionSearch, type MemoryInitResult, } from './memory/memory-initializer.js';
|
|
62
|
-
export { initializeIntelligence, recordStep, recordTrajectory, findSimilarPatterns, getIntelligenceStats, getSonaCoordinator, getReasoningBank, clearIntelligence, benchmarkAdaptation, getAllPatterns, getPatternsByType, flushPatterns, deletePattern, clearAllPatterns, getNeuralDataDir, getPersistenceStatus, type SonaConfig, type TrajectoryStep, type Pattern, type IntelligenceStats, } from './memory/intelligence.js';
|
|
62
|
+
export { initializeIntelligence, recordStep, recordTrajectory, findSimilarPatterns, getIntelligenceStats, getSonaCoordinator, getReasoningBank, clearIntelligence, benchmarkAdaptation, endTrajectoryWithVerdict, distillLearning, getAllPatterns, getPatternsByType, flushPatterns, deletePattern, clearAllPatterns, getNeuralDataDir, getPersistenceStatus, type SonaConfig, type TrajectoryStep, type Pattern, type IntelligenceStats, } from './memory/intelligence.js';
|
|
63
63
|
export { EWCConsolidator, getEWCConsolidator, resetEWCConsolidator, consolidatePatterns, recordPatternOutcome, getEWCStats, type PatternWeights, type EWCConfig, type ConsolidationResult, type EWCStats, } from './memory/ewc-consolidation.js';
|
|
64
64
|
export { SONAOptimizer, getSONAOptimizer, resetSONAOptimizer, processTrajectory, getSuggestion, getSONAStats, type TrajectoryOutcome, type LearnedPattern, type RoutingSuggestion, type SONAStats, } from './memory/sona-optimizer.js';
|
|
65
65
|
export { ErrorHandler, withErrorHandling, } from './production/error-handler.js';
|
|
@@ -453,6 +453,8 @@ export { initializeMemoryDatabase, generateEmbedding, generateBatchEmbeddings, s
|
|
|
453
453
|
// Flash Attention-style batch operations
|
|
454
454
|
batchCosineSim, softmaxAttention, topKIndices, flashAttentionSearch, } from './memory/memory-initializer.js';
|
|
455
455
|
export { initializeIntelligence, recordStep, recordTrajectory, findSimilarPatterns, getIntelligenceStats, getSonaCoordinator, getReasoningBank, clearIntelligence, benchmarkAdaptation,
|
|
456
|
+
// RL loop API
|
|
457
|
+
endTrajectoryWithVerdict, distillLearning,
|
|
456
458
|
// Pattern persistence API
|
|
457
459
|
getAllPatterns, getPatternsByType, flushPatterns, deletePattern, clearAllPatterns, getNeuralDataDir, getPersistenceStatus, } from './memory/intelligence.js';
|
|
458
460
|
// EWC++ Consolidation (Prevents Catastrophic Forgetting)
|
|
@@ -153,6 +153,14 @@ export function generateSettings(options) {
|
|
|
153
153
|
* Detect if we're on Windows for platform-aware hook commands.
|
|
154
154
|
*/
|
|
155
155
|
const IS_WINDOWS = process.platform === 'win32';
|
|
156
|
+
/**
|
|
157
|
+
* Platform-aware project dir variable reference.
|
|
158
|
+
* Claude Code sets $CLAUDE_PROJECT_DIR (Unix) / %CLAUDE_PROJECT_DIR% (Windows)
|
|
159
|
+
* as an environment variable before running hook commands.
|
|
160
|
+
*/
|
|
161
|
+
function projectDirVar() {
|
|
162
|
+
return IS_WINDOWS ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
|
|
163
|
+
}
|
|
156
164
|
/**
|
|
157
165
|
* Build a cross-platform hook command.
|
|
158
166
|
* On Windows, wraps with `cmd /c` to avoid PowerShell stdin/process issues
|
|
@@ -171,11 +179,15 @@ function hookCmdEsm(script, subcommand) {
|
|
|
171
179
|
}
|
|
172
180
|
/** Shorthand for CJS hook-handler commands */
|
|
173
181
|
function hookHandlerCmd(subcommand) {
|
|
174
|
-
|
|
182
|
+
const dir = projectDirVar();
|
|
183
|
+
const quote = IS_WINDOWS ? '' : '"';
|
|
184
|
+
return hookCmd(`${quote}${dir}/.claude/helpers/hook-handler.cjs${quote}`, subcommand);
|
|
175
185
|
}
|
|
176
186
|
/** Shorthand for ESM auto-memory-hook commands */
|
|
177
187
|
function autoMemoryCmd(subcommand) {
|
|
178
|
-
|
|
188
|
+
const dir = projectDirVar();
|
|
189
|
+
const quote = IS_WINDOWS ? '' : '"';
|
|
190
|
+
return hookCmdEsm(`${quote}${dir}/.claude/helpers/auto-memory-hook.mjs${quote}`, subcommand);
|
|
179
191
|
}
|
|
180
192
|
/**
|
|
181
193
|
* Generate statusLine configuration for Claude Code
|
|
@@ -187,9 +199,11 @@ function generateStatusLineConfig(_options) {
|
|
|
187
199
|
// The script runs after each assistant message (debounced 300ms).
|
|
188
200
|
// NOTE: statusline must NOT use `cmd /c` — Claude Code manages its stdin
|
|
189
201
|
// directly for statusline commands, and `cmd /c` blocks stdin forwarding.
|
|
202
|
+
const dir = projectDirVar();
|
|
203
|
+
const quote = IS_WINDOWS ? '' : '"';
|
|
190
204
|
return {
|
|
191
205
|
type: 'command',
|
|
192
|
-
command: `node
|
|
206
|
+
command: `node ${quote}${dir}/.claude/helpers/statusline.cjs${quote}`,
|
|
193
207
|
};
|
|
194
208
|
}
|
|
195
209
|
/**
|
|
@@ -350,55 +350,234 @@ export const coordinationTools = [
|
|
|
350
350
|
},
|
|
351
351
|
{
|
|
352
352
|
name: 'coordination_consensus',
|
|
353
|
-
description: 'Manage consensus protocol',
|
|
353
|
+
description: 'Manage consensus protocol with BFT, Raft, or Quorum strategies',
|
|
354
354
|
category: 'coordination',
|
|
355
355
|
inputSchema: {
|
|
356
356
|
type: 'object',
|
|
357
357
|
properties: {
|
|
358
358
|
action: { type: 'string', enum: ['status', 'propose', 'vote', 'commit'], description: 'Action to perform' },
|
|
359
|
-
proposal: { type: 'object', description: 'Proposal data' },
|
|
359
|
+
proposal: { type: 'object', description: 'Proposal data (for propose)' },
|
|
360
|
+
proposalId: { type: 'string', description: 'Proposal ID (for vote/commit/status)' },
|
|
360
361
|
vote: { type: 'string', enum: ['accept', 'reject'], description: 'Vote' },
|
|
362
|
+
voterId: { type: 'string', description: 'Voter node ID' },
|
|
363
|
+
strategy: { type: 'string', enum: ['bft', 'raft', 'quorum'], description: 'Consensus strategy (default: raft)' },
|
|
364
|
+
quorumPreset: { type: 'string', enum: ['unanimous', 'majority', 'supermajority'], description: 'Quorum threshold preset (default: majority)' },
|
|
365
|
+
term: { type: 'number', description: 'Term number (for raft strategy)' },
|
|
361
366
|
},
|
|
362
367
|
},
|
|
363
368
|
handler: async (input) => {
|
|
364
369
|
const store = loadCoordStore();
|
|
365
370
|
const action = input.action || 'status';
|
|
371
|
+
const strategy = input.strategy || 'raft';
|
|
372
|
+
const nodeCount = Object.keys(store.nodes).length || 1;
|
|
373
|
+
// Initialize consensus storage in the coordination store if missing
|
|
374
|
+
if (!store.consensus) {
|
|
375
|
+
store.consensus = { pending: [], history: [] };
|
|
376
|
+
}
|
|
377
|
+
const consensus = store.consensus;
|
|
378
|
+
function calcRequired(strat, total, preset) {
|
|
379
|
+
if (total <= 0)
|
|
380
|
+
return 1;
|
|
381
|
+
if (strat === 'bft')
|
|
382
|
+
return Math.floor((total * 2) / 3) + 1;
|
|
383
|
+
if (strat === 'quorum') {
|
|
384
|
+
if (preset === 'unanimous')
|
|
385
|
+
return total;
|
|
386
|
+
if (preset === 'supermajority')
|
|
387
|
+
return Math.floor((total * 2) / 3) + 1;
|
|
388
|
+
}
|
|
389
|
+
return Math.floor(total / 2) + 1;
|
|
390
|
+
}
|
|
366
391
|
if (action === 'status') {
|
|
367
|
-
|
|
368
|
-
|
|
392
|
+
if (input.proposalId) {
|
|
393
|
+
// Status for specific proposal
|
|
394
|
+
const p = consensus.pending.find(x => x.proposalId === input.proposalId);
|
|
395
|
+
if (p) {
|
|
396
|
+
const votesFor = Object.values(p.votes).filter(v => v).length;
|
|
397
|
+
const votesAgainst = Object.values(p.votes).filter(v => !v).length;
|
|
398
|
+
return {
|
|
399
|
+
success: true,
|
|
400
|
+
proposalId: p.proposalId,
|
|
401
|
+
strategy: p.strategy,
|
|
402
|
+
status: p.status,
|
|
403
|
+
votesFor,
|
|
404
|
+
votesAgainst,
|
|
405
|
+
required: calcRequired(p.strategy, nodeCount, p.quorumPreset),
|
|
406
|
+
totalNodes: nodeCount,
|
|
407
|
+
resolved: false,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const h = consensus.history.find(x => x.proposalId === input.proposalId);
|
|
411
|
+
if (h)
|
|
412
|
+
return { success: true, ...h, resolved: true, historical: true };
|
|
413
|
+
return { success: false, error: 'Proposal not found' };
|
|
414
|
+
}
|
|
415
|
+
const quorum = calcRequired(strategy, nodeCount);
|
|
369
416
|
return {
|
|
370
417
|
success: true,
|
|
371
418
|
algorithm: store.topology.consensusAlgorithm,
|
|
419
|
+
strategy,
|
|
372
420
|
nodes: nodeCount,
|
|
373
421
|
quorum,
|
|
422
|
+
pendingProposals: consensus.pending.length,
|
|
423
|
+
resolvedProposals: consensus.history.length,
|
|
374
424
|
status: nodeCount >= quorum ? 'operational' : 'degraded',
|
|
375
425
|
};
|
|
376
426
|
}
|
|
377
427
|
if (action === 'propose') {
|
|
378
|
-
const proposalId = `proposal-${Date.now()}`;
|
|
428
|
+
const proposalId = `proposal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
429
|
+
const quorumPreset = input.quorumPreset || 'majority';
|
|
430
|
+
const term = input.term || 1;
|
|
431
|
+
const required = calcRequired(strategy, nodeCount, quorumPreset);
|
|
432
|
+
// Raft: one pending proposal per term
|
|
433
|
+
if (strategy === 'raft') {
|
|
434
|
+
const existing = consensus.pending.find(p => p.strategy === 'raft' && p.term === term);
|
|
435
|
+
if (existing) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: `Raft term ${term} already has pending proposal: ${existing.proposalId}`,
|
|
439
|
+
existingProposalId: existing.proposalId,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
consensus.pending.push({
|
|
444
|
+
proposalId,
|
|
445
|
+
type: 'coordination',
|
|
446
|
+
proposal: input.proposal,
|
|
447
|
+
proposedBy: input.voterId || 'system',
|
|
448
|
+
proposedAt: new Date().toISOString(),
|
|
449
|
+
votes: {},
|
|
450
|
+
status: 'pending',
|
|
451
|
+
strategy,
|
|
452
|
+
term: strategy === 'raft' ? term : undefined,
|
|
453
|
+
quorumPreset: strategy === 'quorum' ? quorumPreset : undefined,
|
|
454
|
+
byzantineVoters: strategy === 'bft' ? [] : undefined,
|
|
455
|
+
});
|
|
456
|
+
saveCoordStore(store);
|
|
379
457
|
return {
|
|
380
458
|
success: true,
|
|
381
459
|
action: 'proposed',
|
|
382
460
|
proposalId,
|
|
383
461
|
proposal: input.proposal,
|
|
462
|
+
strategy,
|
|
384
463
|
status: 'pending',
|
|
385
|
-
|
|
464
|
+
required,
|
|
465
|
+
totalNodes: nodeCount,
|
|
466
|
+
term: strategy === 'raft' ? term : undefined,
|
|
386
467
|
};
|
|
387
468
|
}
|
|
388
469
|
if (action === 'vote') {
|
|
470
|
+
const p = consensus.pending.find(x => x.proposalId === input.proposalId);
|
|
471
|
+
if (!p)
|
|
472
|
+
return { success: false, error: 'Proposal not found or already resolved' };
|
|
473
|
+
const voterId = input.voterId;
|
|
474
|
+
if (!voterId)
|
|
475
|
+
return { success: false, error: 'voterId is required' };
|
|
476
|
+
const voteValue = input.vote === 'accept';
|
|
477
|
+
const pStrategy = p.strategy || 'raft';
|
|
478
|
+
const required = calcRequired(pStrategy, nodeCount, p.quorumPreset);
|
|
479
|
+
// Double-vote prevention
|
|
480
|
+
if (voterId in p.votes) {
|
|
481
|
+
if (pStrategy === 'bft' && p.votes[voterId] !== voteValue) {
|
|
482
|
+
if (!p.byzantineVoters)
|
|
483
|
+
p.byzantineVoters = [];
|
|
484
|
+
if (!p.byzantineVoters.includes(voterId))
|
|
485
|
+
p.byzantineVoters.push(voterId);
|
|
486
|
+
delete p.votes[voterId];
|
|
487
|
+
saveCoordStore(store);
|
|
488
|
+
return {
|
|
489
|
+
success: false,
|
|
490
|
+
byzantineDetected: true,
|
|
491
|
+
message: `Byzantine behavior: voter ${voterId} attempted conflicting vote. Vote invalidated.`,
|
|
492
|
+
byzantineVoters: p.byzantineVoters,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return { success: false, error: `Voter ${voterId} has already voted on this proposal` };
|
|
496
|
+
}
|
|
497
|
+
// BFT cross-proposal conflict check
|
|
498
|
+
if (pStrategy === 'bft') {
|
|
499
|
+
for (const other of consensus.pending) {
|
|
500
|
+
if (other.proposalId === p.proposalId)
|
|
501
|
+
continue;
|
|
502
|
+
if (voterId in other.votes && other.votes[voterId] !== voteValue) {
|
|
503
|
+
if (!p.byzantineVoters)
|
|
504
|
+
p.byzantineVoters = [];
|
|
505
|
+
if (!p.byzantineVoters.includes(voterId))
|
|
506
|
+
p.byzantineVoters.push(voterId);
|
|
507
|
+
saveCoordStore(store);
|
|
508
|
+
return {
|
|
509
|
+
success: false,
|
|
510
|
+
byzantineDetected: true,
|
|
511
|
+
message: `Byzantine behavior: voter ${voterId} cast conflicting votes across proposals.`,
|
|
512
|
+
byzantineVoters: p.byzantineVoters,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
p.votes[voterId] = voteValue;
|
|
518
|
+
const votesFor = Object.values(p.votes).filter(v => v).length;
|
|
519
|
+
const votesAgainst = Object.values(p.votes).filter(v => !v).length;
|
|
520
|
+
// Resolution check
|
|
521
|
+
let resolved = false;
|
|
522
|
+
let result;
|
|
523
|
+
if (votesFor >= required) {
|
|
524
|
+
resolved = true;
|
|
525
|
+
result = 'approved';
|
|
526
|
+
}
|
|
527
|
+
else if (votesAgainst >= required) {
|
|
528
|
+
resolved = true;
|
|
529
|
+
result = 'rejected';
|
|
530
|
+
}
|
|
531
|
+
else if (pStrategy === 'quorum' && p.quorumPreset === 'unanimous' && votesAgainst > 0) {
|
|
532
|
+
resolved = true;
|
|
533
|
+
result = 'rejected';
|
|
534
|
+
}
|
|
535
|
+
if (resolved && result) {
|
|
536
|
+
p.status = result;
|
|
537
|
+
consensus.history.push({
|
|
538
|
+
proposalId: p.proposalId,
|
|
539
|
+
result,
|
|
540
|
+
votes: { for: votesFor, against: votesAgainst },
|
|
541
|
+
decidedAt: new Date().toISOString(),
|
|
542
|
+
strategy: pStrategy,
|
|
543
|
+
term: p.term,
|
|
544
|
+
byzantineDetected: p.byzantineVoters?.length ? p.byzantineVoters : undefined,
|
|
545
|
+
});
|
|
546
|
+
consensus.pending = consensus.pending.filter(x => x.proposalId !== p.proposalId);
|
|
547
|
+
}
|
|
548
|
+
saveCoordStore(store);
|
|
389
549
|
return {
|
|
390
550
|
success: true,
|
|
391
551
|
action: 'voted',
|
|
552
|
+
proposalId: p.proposalId,
|
|
553
|
+
voterId,
|
|
392
554
|
vote: input.vote,
|
|
393
|
-
|
|
555
|
+
strategy: pStrategy,
|
|
556
|
+
votesFor,
|
|
557
|
+
votesAgainst,
|
|
558
|
+
required,
|
|
559
|
+
totalNodes: nodeCount,
|
|
560
|
+
resolved,
|
|
561
|
+
result: resolved ? result : undefined,
|
|
562
|
+
status: p.status,
|
|
394
563
|
};
|
|
395
564
|
}
|
|
396
565
|
if (action === 'commit') {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
566
|
+
// Commit is a no-op confirmation for already-resolved proposals
|
|
567
|
+
if (input.proposalId) {
|
|
568
|
+
const h = consensus.history.find(x => x.proposalId === input.proposalId);
|
|
569
|
+
if (h) {
|
|
570
|
+
return {
|
|
571
|
+
success: true,
|
|
572
|
+
action: 'committed',
|
|
573
|
+
proposalId: input.proposalId,
|
|
574
|
+
result: h.result,
|
|
575
|
+
committedAt: new Date().toISOString(),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return { success: false, error: 'Proposal not found in resolved history. Vote must reach quorum first.' };
|
|
579
|
+
}
|
|
580
|
+
return { success: false, error: 'proposalId is required for commit' };
|
|
402
581
|
}
|
|
403
582
|
return { success: false, error: 'Unknown action' };
|
|
404
583
|
},
|