Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
Feature: Board of Directors Consensus Mechanism#2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base:feat-cli-service-options
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = { extends: "eslint:recommended", env: { node: true, es6: true }, parserOptions: { ecmaVersion: 2022, sourceType: "module" } }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,146 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import LLMClient from './llm-client.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import DIRECTORS from './directors.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class BoardMeeting { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.llm = new LLMClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.directors = Object.values(DIRECTORS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async conductMeeting(topic) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(`\n📢 Board Meeting Called: "${topic}"\n`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Phase 1: Proposal Generation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Phase 1: Generating Proposals..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const proposals = await this.generateProposals(topic); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Phase 2: Anonymization | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Phase 2: Anonymizing Proposals..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const anonymizedProposals = this.anonymizeProposals(proposals); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Phase 3: Voting | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Phase 3: Voting..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const votes = await this.collectVotes(topic, anonymizedProposals); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Phase 4: Tally | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = this.tallyVotes(votes, anonymizedProposals); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async generateProposals(topic) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const proposalPromises = this.directors.map(async (director) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(` - ${director.name} is thinking...`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await this.llm.complete( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| director.systemPrompt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `Please provide your perspective and a concrete proposal on the following topic:\n"${topic}"\nKeep your response concise (under 200 words).` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { director: director.name, content: response }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`Error from ${director.name}:`, error.message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { director: director.name, content: "Abstained due to technical difficulties." }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Promise.all(proposalPromises); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anonymizeProposals(proposals) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return proposals.map((p, index) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: String.fromCharCode(65 + index), // A, B, C, D, E | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CopilotAI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: String.fromCharCode(65+index),// A, B, C, D, E | |
| id: `P${index+1}`,// P1, P2, P3, ... |
CopilotAIDec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code instructs directors not to vote for their own proposal (line 79), but doesn't enforce this constraint. A director could still vote for their own proposal if the LLM doesn't follow instructions properly.
Consider adding validation after parsing the vote:
constvote=this.parseVote(response,director.name);if(vote.choice===ownId){console.warn(`${director.name} attempted to vote for their own proposal. Invalidating vote.`);returnnull;}returnvote;| returnthis.parseVote(response,director.name); | |
| constvote=this.parseVote(response,director.name); | |
| if(vote.choice===ownId){ | |
| console.warn(`${director.name} attempted to vote for their own proposal. Invalidating vote.`); | |
| returnnull; | |
| } | |
| returnvote; |
CopilotAIDec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The topic field in the return object is an empty string, but it should be populated with the actual topic that was passed to the method. This could be useful for debugging or logging purposes.
Consider changing:
return{topic: topic,// or pass topic as parameter to tallyVoteswinner: winner,scores: scores,votes: validVotes,proposals: anonymizedProposals};CopilotAIDec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The winner determination logic doesn't handle ties properly. When multiple proposals have the same maximum score, the first one encountered in the iteration order will always win. This could lead to unexpected results in tie situations.
Consider either:
- Detecting and reporting ties explicitly
- Implementing a tie-breaking mechanism (e.g., random selection, reporting all tied proposals)
| // Find winner | |
| letmaxScore=-1; | |
| letwinnerId=null; | |
| Object.entries(scores).forEach(([id,score])=>{ | |
| if(score>maxScore){ | |
| maxScore=score; | |
| winnerId=id; | |
| } | |
| }); | |
| constwinner=anonymizedProposals.find(p=>p.id===winnerId); | |
| return{ | |
| topic: "", | |
| winner: winner, | |
| scores: scores, | |
| votes: validVotes, | |
| proposals: anonymizedProposals | |
| // Find winners (handle ties) | |
| letmaxScore=-1; | |
| Object.values(scores).forEach(score=>{ | |
| if(score>maxScore){ | |
| maxScore=score; | |
| } | |
| }); | |
| // Collect all proposal IDs with maxScore | |
| constwinnerIds=Object.entries(scores) | |
| .filter(([id,score])=>score===maxScore) | |
| .map(([id,score])=>id); | |
| constwinners=anonymizedProposals.filter(p=>winnerIds.includes(p.id)); | |
| return{ | |
| topic: "", | |
| winners: winners, | |
| scores: scores, | |
| votes: validVotes, | |
| proposals: anonymizedProposals, | |
| tie: winners.length>1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| export const DIRECTORS = { | ||
| PRAGMATIST: { | ||
| name: "The Pragmatist", | ||
| role: "Senior Engineer focused on delivery", | ||
| systemPrompt: `You are The Pragmatist. | ||
| Your core values are: Simplicity, Speed, MVP, YAGNI (You Ain't Gonna Need It). | ||
| You dislike over-engineering, complex abstractions, and premature optimization. | ||
| When analyzing a problem, propose the simplest, most direct solution that works. | ||
| Focus on "boring" technology and proven patterns.` | ||
| }, | ||
| ARCHITECT: { | ||
| name: "The Architect", | ||
| role: "Principal Software Architect", | ||
| systemPrompt: `You are The Architect. | ||
| Your core values are: Scalability, Maintainability, Design Patterns, SOLID principles. | ||
| You think in terms of systems, interfaces, and long-term evolution. | ||
| When analyzing a problem, propose a solution that is robust, decoupled, and extensible. | ||
| You are willing to accept initial complexity for future flexibility.` | ||
| }, | ||
| SECURITY: { | ||
| name: "The Security Officer", | ||
| role: "Security Engineer", | ||
| systemPrompt: `You are The Security Officer. | ||
| Your core values are: Zero Trust, Data Privacy, Input Validation, Secure Defaults. | ||
| You view every feature as a potential attack vector. | ||
| When analyzing a problem, focus on how it could be exploited and how to prevent it. | ||
| Prioritize safety over convenience or speed.` | ||
| }, | ||
| PERFORMANCE: { | ||
| name: "The Performance Zealot", | ||
| role: "Performance Engineer", | ||
| systemPrompt: `You are The Performance Zealot. | ||
| Your core values are: Latency, Throughput, Memory Usage, O(n). | ||
| You obsess over CPU cycles and database queries. | ||
| When analyzing a problem, propose the solution that is most efficient. | ||
| You are willing to sacrifice readability for raw speed if necessary.` | ||
| }, | ||
| USER_ADVOCATE: { | ||
| name: "The User Advocate", | ||
| role: "Product Engineer / DX Specialist", | ||
| systemPrompt: `You are The User Advocate. | ||
| Your core values are: User Experience (UX), Developer Experience (DX), Clarity, Documentation. | ||
| You care about how the code feels to use and read. | ||
| When analyzing a problem, propose the solution that is most intuitive and well-documented. | ||
| You believe code is for humans first, machines second.` | ||
| } | ||
| }; | ||
| export default DIRECTORS; |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,78 @@ | ||||
| import { spawn } from 'child_process'; | ||||
Comment on lines
+1
to
+2
CopilotAI | ||||
| import { spawn } from 'child_process'; |
CopilotAIDec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OpenAI API call doesn't specify a max_tokens parameter, while the Anthropic call sets it to 4000. For consistency and to ensure predictable behavior, consider adding a max_tokens limit to the OpenAI request:
body: JSON.stringify({model: 'gpt-4o',max_tokens: 4000,messages: [{role: 'system',content: systemPrompt},{role: 'user',content: userPrompt}]})| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| #!/usr/bin/env node | ||
| import BoardMeeting from '../lib/consensus/board-meeting.js'; | ||
| import readline from 'readline'; | ||
| async function main() { | ||
| const args = process.argv.slice(2); | ||
| let topic = args.join(' '); | ||
| if (!topic) { | ||
| const rl = readline.createInterface({ | ||
| input: process.stdin, | ||
| output: process.stdout | ||
| }); | ||
| topic = await new Promise(resolve => { | ||
| rl.question('Enter the topic for the Board Meeting: ', (answer) => { | ||
| rl.close(); | ||
| resolve(answer); | ||
| }); | ||
| }); | ||
| } | ||
| if (!topic) { | ||
| console.error("Error: Topic is required."); | ||
| process.exit(1); | ||
| } | ||
| // Check for API keys | ||
| if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) { | ||
| console.error("Error: ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable is required."); | ||
| process.exit(1); | ||
| } | ||
| const meeting = new BoardMeeting(); | ||
| try { | ||
| const result = await meeting.conductMeeting(topic); | ||
| console.log("\n" + "=".repeat(50)); | ||
| console.log("🏁 MEETING ADJOURNED 🏁"); | ||
| console.log("=".repeat(50)); | ||
| if (result.winner) { | ||
| console.log(`\n🏆 WINNER: Proposal ${result.winner.id} (by ${result.winner.originalDirector})`); | ||
| console.log(`\n📄 THE WINNING PROPOSAL:\n${result.winner.content}`); | ||
| } else { | ||
| console.log("\n🏆 WINNER: None (No votes cast or tie with no votes)"); | ||
| } | ||
| console.log("\n" + "-".repeat(50)); | ||
| console.log("📊 VOTE TALLY"); | ||
| console.log("-".repeat(50)); | ||
| // Sort scores | ||
| const sortedScores = Object.entries(result.scores).sort((a, b) => b[1] - a[1]); | ||
| sortedScores.forEach(([id, score]) => { | ||
| const p = result.proposals.find(prop => prop.id === id); | ||
| console.log(`Proposal ${id} (${p.originalDirector}): ${score} votes`); | ||
| }); | ||
| console.log("\n" + "-".repeat(50)); | ||
| console.log("🗳️ VOTING RECORDS"); | ||
| console.log("-".repeat(50)); | ||
| result.votes.forEach(vote => { | ||
| if (vote) { | ||
| console.log(`${vote.voter} voted for ${vote.choice}`); | ||
| console.log(`Reason: ${vote.reason}\n`); | ||
| } | ||
| }); | ||
| } catch (error) { | ||
| console.error("Board meeting failed:", error); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| main(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| console.log('Test setup loaded'); |
Uh oh!
There was an error while loading. Please reload this page.
CopilotAIDec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ESLint configuration is written on a single line, making it hard to read and maintain. Consider formatting it properly: