| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- /**
- * Issueboard MCP - Manages issues and workflow during PBL generation.
- *
- * Migrated from PBL-Nano. Key changes:
- * - No Anthropic SDK dependency (initialize_question_agent removed)
- * - Question agent initialization is handled by generate-pbl.ts post-processing
- * - Operates directly on a shared PBLProjectConfig
- */
- import type { PBLProjectConfig, PBLIssue, PBLToolResult } from '../types';
- import { AgentMCP } from './agent-mcp';
- import { getQuestionAgentPrompt, getJudgeAgentPrompt } from './agent-templates';
- export class IssueboardMCP {
- private config: PBLProjectConfig;
- private agentMCP: AgentMCP;
- private language: string;
- private nextIssueId: number;
- constructor(config: PBLProjectConfig, agentMCP: AgentMCP, language: string = 'en-US') {
- this.config = config;
- this.agentMCP = agentMCP;
- this.language = language;
- this.nextIssueId = 1;
- }
- createIssueboard(): PBLToolResult {
- this.config.issueboard = {
- agent_ids: [],
- issues: [],
- current_issue_id: null,
- };
- this.nextIssueId = 1;
- return { success: true, message: 'Issueboard created successfully.' };
- }
- getIssueboard(): PBLToolResult {
- return {
- success: true,
- agent_ids: [...this.config.issueboard.agent_ids],
- issues: this.config.issueboard.issues.map((i) => ({ ...i })),
- };
- }
- updateIssueboardAgents(agentIds: string[]): PBLToolResult {
- this.config.issueboard.agent_ids = [...agentIds];
- return {
- success: true,
- message: 'Issueboard agents updated successfully.',
- };
- }
- createIssue(params: {
- title: string;
- description: string;
- person_in_charge: string;
- participants?: string[];
- notes?: string;
- parent_issue?: string | null;
- index?: number;
- }): PBLToolResult {
- const {
- title,
- description,
- person_in_charge,
- participants = [],
- notes = '',
- parent_issue = null,
- index = 0,
- } = params;
- if (!title?.trim()) {
- return { success: false, error: 'Title cannot be empty.' };
- }
- if (!person_in_charge?.trim()) {
- return { success: false, error: 'Person in charge cannot be empty.' };
- }
- if (parent_issue && !this.config.issueboard.issues.find((i) => i.id === parent_issue)) {
- return {
- success: false,
- error: `Parent issue "${parent_issue}" not found.`,
- };
- }
- const issueId = `issue_${this.nextIssueId++}`;
- const questionAgentName = `Question Agent - ${issueId}`;
- const judgeAgentName = `Judge Agent - ${issueId}`;
- const newIssue: PBLIssue = {
- id: issueId,
- title,
- description,
- person_in_charge,
- participants: [...participants],
- notes,
- parent_issue,
- index,
- is_done: false,
- is_active: false,
- generated_questions: '',
- question_agent_name: questionAgentName,
- judge_agent_name: judgeAgentName,
- };
- this.config.issueboard.issues.push(newIssue);
- // Auto-create question and judge agents
- this.agentMCP.createAgent({
- name: questionAgentName,
- system_prompt: getQuestionAgentPrompt(this.language),
- default_mode: 'chat',
- actor_role: 'Question Assistant for Issue',
- role_division: 'development',
- is_system_agent: true,
- });
- this.agentMCP.createAgent({
- name: judgeAgentName,
- system_prompt: getJudgeAgentPrompt(this.language),
- default_mode: 'chat',
- actor_role: 'Judge for Issue Completion',
- role_division: 'management',
- is_system_agent: true,
- });
- return {
- success: true,
- issue_id: issueId,
- message: 'Issue created with question and judge agents.',
- };
- }
- listIssues(): PBLToolResult {
- return {
- success: true,
- issues: this.config.issueboard.issues.map((i) => ({ ...i })),
- };
- }
- getIssue(issueId: string): PBLToolResult {
- const issue = this.config.issueboard.issues.find((i) => i.id === issueId);
- if (!issue) {
- return { success: false, error: `Issue "${issueId}" not found.` };
- }
- return { success: true, issues: [{ ...issue }] };
- }
- updateIssue(params: {
- issue_id: string;
- title?: string;
- description?: string;
- person_in_charge?: string;
- participants?: string[];
- notes?: string;
- parent_issue?: string | null;
- index?: number;
- }): PBLToolResult {
- const issue = this.config.issueboard.issues.find((i) => i.id === params.issue_id);
- if (!issue) {
- return { success: false, error: `Issue "${params.issue_id}" not found.` };
- }
- if (
- params.parent_issue !== undefined &&
- params.parent_issue !== null &&
- !this.config.issueboard.issues.find((i) => i.id === params.parent_issue)
- ) {
- return {
- success: false,
- error: `Parent issue "${params.parent_issue}" not found.`,
- };
- }
- if (params.title !== undefined) issue.title = params.title;
- if (params.description !== undefined) issue.description = params.description;
- if (params.person_in_charge !== undefined) issue.person_in_charge = params.person_in_charge;
- if (params.participants !== undefined) issue.participants = [...params.participants];
- if (params.notes !== undefined) issue.notes = params.notes;
- if (params.parent_issue !== undefined) issue.parent_issue = params.parent_issue;
- if (params.index !== undefined) issue.index = params.index;
- return { success: true, message: 'Issue updated successfully.' };
- }
- deleteIssue(issueId: string): PBLToolResult {
- const index = this.config.issueboard.issues.findIndex((i) => i.id === issueId);
- if (index === -1) {
- return { success: false, error: `Issue "${issueId}" not found.` };
- }
- this.config.issueboard.issues.splice(index, 1);
- // Remove child issues
- this.config.issueboard.issues = this.config.issueboard.issues.filter(
- (i) => i.parent_issue !== issueId,
- );
- return { success: true, message: 'Issue deleted successfully.' };
- }
- reorderIssues(issueIds: string[]): PBLToolResult {
- for (const id of issueIds) {
- if (!this.config.issueboard.issues.find((i) => i.id === id)) {
- return { success: false, error: `Issue "${id}" not found.` };
- }
- }
- const reordered: PBLIssue[] = [];
- for (let i = 0; i < issueIds.length; i++) {
- const issue = this.config.issueboard.issues.find((iss) => iss.id === issueIds[i])!;
- issue.index = i;
- reordered.push(issue);
- }
- // Append any issues not in the reorder list
- for (const issue of this.config.issueboard.issues) {
- if (!issueIds.includes(issue.id)) {
- reordered.push(issue);
- }
- }
- this.config.issueboard.issues = reordered;
- return { success: true, message: 'Issues reordered successfully.' };
- }
- activateNextIssue(): PBLToolResult {
- // Deactivate current
- const current = this.config.issueboard.issues.find((i) => i.is_active);
- if (current) {
- current.is_active = false;
- this.config.issueboard.current_issue_id = null;
- }
- // Find next incomplete issue
- const next = this.config.issueboard.issues
- .filter((i) => !i.is_done)
- .sort((a, b) => a.index - b.index)[0];
- if (!next) {
- return { success: false, error: 'No more issues to activate.' };
- }
- next.is_active = true;
- this.config.issueboard.current_issue_id = next.id;
- return {
- success: true,
- issue_id: next.id,
- message: `Activated issue: ${next.title}`,
- };
- }
- completeCurrentIssue(): PBLToolResult {
- const current = this.config.issueboard.issues.find((i) => i.is_active);
- if (!current) {
- return { success: false, error: 'No active issue to complete.' };
- }
- current.is_done = true;
- current.is_active = false;
- this.config.issueboard.current_issue_id = null;
- return {
- success: true,
- message: `Issue "${current.id}" marked as complete.`,
- };
- }
- }
|