open-coleslaw 0.2.1 → 0.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/dist/index.js +300 -1884
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -308,47 +308,6 @@ function rowToWorker(row) {
|
|
|
308
308
|
costUsd: row.cost_usd
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
|
-
function createWorker(worker) {
|
|
312
|
-
const db = getDb();
|
|
313
|
-
const id = worker.id ?? uuidv43();
|
|
314
|
-
const status = worker.status ?? "pending";
|
|
315
|
-
const spawnedAt = worker.spawnedAt ?? Date.now();
|
|
316
|
-
const completedAt = worker.completedAt ?? null;
|
|
317
|
-
const costUsd = worker.costUsd ?? 0;
|
|
318
|
-
db.prepare(
|
|
319
|
-
`INSERT INTO workers (id, leader_id, meeting_id, task_description, task_type, status, input_context, output_result, error_message, dependencies, spawned_at, completed_at, cost_usd)
|
|
320
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
321
|
-
).run(
|
|
322
|
-
id,
|
|
323
|
-
worker.leaderId,
|
|
324
|
-
worker.meetingId,
|
|
325
|
-
worker.taskDescription,
|
|
326
|
-
worker.taskType,
|
|
327
|
-
status,
|
|
328
|
-
worker.inputContext,
|
|
329
|
-
worker.outputResult,
|
|
330
|
-
worker.errorMessage,
|
|
331
|
-
JSON.stringify(worker.dependencies),
|
|
332
|
-
spawnedAt,
|
|
333
|
-
completedAt,
|
|
334
|
-
costUsd
|
|
335
|
-
);
|
|
336
|
-
return {
|
|
337
|
-
id,
|
|
338
|
-
leaderId: worker.leaderId,
|
|
339
|
-
meetingId: worker.meetingId,
|
|
340
|
-
taskDescription: worker.taskDescription,
|
|
341
|
-
taskType: worker.taskType,
|
|
342
|
-
status,
|
|
343
|
-
inputContext: worker.inputContext,
|
|
344
|
-
outputResult: worker.outputResult,
|
|
345
|
-
errorMessage: worker.errorMessage,
|
|
346
|
-
dependencies: worker.dependencies,
|
|
347
|
-
spawnedAt,
|
|
348
|
-
completedAt,
|
|
349
|
-
costUsd
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
311
|
function getWorker(id) {
|
|
353
312
|
const db = getDb();
|
|
354
313
|
const row = db.prepare("SELECT * FROM workers WHERE id = ?").get(id);
|
|
@@ -554,20 +513,6 @@ function getTasksFromMinutes(meetingId) {
|
|
|
554
513
|
}
|
|
555
514
|
|
|
556
515
|
// src/types/agent.ts
|
|
557
|
-
var TIER_CONFIGS = {
|
|
558
|
-
orchestrator: {
|
|
559
|
-
model: "claude-opus-4-6",
|
|
560
|
-
maxTurns: 10
|
|
561
|
-
},
|
|
562
|
-
leader: {
|
|
563
|
-
model: "claude-sonnet-4-6",
|
|
564
|
-
maxTurns: 20
|
|
565
|
-
},
|
|
566
|
-
worker: {
|
|
567
|
-
model: "claude-sonnet-4-6",
|
|
568
|
-
maxTurns: 30
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
516
|
var DEPARTMENT_TOOLS = {
|
|
572
517
|
architecture: ["Read", "Grep", "Glob"],
|
|
573
518
|
engineering: ["Read", "Grep", "Glob", "Write", "Edit", "Bash"],
|
|
@@ -576,13 +521,6 @@ var DEPARTMENT_TOOLS = {
|
|
|
576
521
|
research: ["Read", "Grep", "Glob", "WebSearch"]
|
|
577
522
|
};
|
|
578
523
|
|
|
579
|
-
// src/types/meeting.ts
|
|
580
|
-
var DEFAULT_MEETING_CONFIG = {
|
|
581
|
-
maxRoundsPerItem: 3,
|
|
582
|
-
convergenceThreshold: 0.8,
|
|
583
|
-
model: "claude-sonnet-4-6"
|
|
584
|
-
};
|
|
585
|
-
|
|
586
524
|
// src/agents/departments.ts
|
|
587
525
|
var DEPARTMENT_REGISTRY = /* @__PURE__ */ new Map([
|
|
588
526
|
[
|
|
@@ -836,1487 +774,6 @@ var LeaderPool = class {
|
|
|
836
774
|
}
|
|
837
775
|
};
|
|
838
776
|
|
|
839
|
-
// src/agents/leader-prompts.ts
|
|
840
|
-
function rulesBlock(extraRules) {
|
|
841
|
-
const base = [
|
|
842
|
-
"Never modify files outside the project root unless explicitly told to.",
|
|
843
|
-
"Never commit, push, or deploy without a confirmed user decision.",
|
|
844
|
-
"If you encounter ambiguity that could lead to significant rework, emit @USER_DECISION_NEEDED immediately rather than guessing.",
|
|
845
|
-
"Keep responses concise. Prefer structured output (lists, tables) over prose.",
|
|
846
|
-
"When delegating to workers, provide clear task descriptions with explicit acceptance criteria.",
|
|
847
|
-
"Respect the tool allowlist for your department \u2014 do not attempt to use tools you have not been granted.",
|
|
848
|
-
"Report cost and token usage whenever you complete a significant sub-task."
|
|
849
|
-
];
|
|
850
|
-
const rules = extraRules ? [...base, ...extraRules] : base;
|
|
851
|
-
return rules.map((r, i) => `${i + 1}. ${r}`).join("\n");
|
|
852
|
-
}
|
|
853
|
-
function meetingProtocol() {
|
|
854
|
-
return `## MEETING PROTOCOL
|
|
855
|
-
|
|
856
|
-
When participating in a meeting you MUST follow these rules:
|
|
857
|
-
|
|
858
|
-
1. **Opening phase** \u2014 Listen to the agenda presented by the orchestrator. Acknowledge understanding. Surface any concerns or dependencies your department has regarding the agenda items.
|
|
859
|
-
|
|
860
|
-
2. **Discussion phase** \u2014 Contribute your department's perspective on each agenda item. Be specific: reference files, modules, or prior decisions. If you disagree with another leader, state your reasoning clearly and propose an alternative.
|
|
861
|
-
|
|
862
|
-
3. **When to emit @USER_DECISION_NEEDED** \u2014 Emit this tag ONLY when:
|
|
863
|
-
- Two or more leaders have irreconcilable positions after a full discussion round.
|
|
864
|
-
- A decision has significant cost, security, or architectural implications that exceed the meeting's delegated authority.
|
|
865
|
-
- The user explicitly asked to be looped in on a particular topic.
|
|
866
|
-
Include: a concise summary of the options, who supports each option, and your recommended default.
|
|
867
|
-
|
|
868
|
-
4. **Synthesis phase** \u2014 Confirm or amend the proposed action items. Ensure your department's commitments are accurate and achievable.
|
|
869
|
-
|
|
870
|
-
5. **Post-meeting** \u2014 Execute your assigned action items by spawning workers or performing lightweight tasks directly.`;
|
|
871
|
-
}
|
|
872
|
-
function workforceManagement(deptDescription, workerTypes) {
|
|
873
|
-
return `## WORKFORCE MANAGEMENT
|
|
874
|
-
|
|
875
|
-
You lead the department: ${deptDescription}
|
|
876
|
-
|
|
877
|
-
Available worker types you can spawn: ${workerTypes.join(", ")}
|
|
878
|
-
|
|
879
|
-
### When to spawn workers
|
|
880
|
-
- Spawn workers for tasks that require focused execution (file changes, test runs, research).
|
|
881
|
-
- Do NOT spawn workers for simple questions you can answer from context.
|
|
882
|
-
- Prefer spawning multiple independent workers in parallel over sequential single-worker chains.
|
|
883
|
-
|
|
884
|
-
### How to spawn workers
|
|
885
|
-
When you decide a worker is needed, output a structured worker-spawn request:
|
|
886
|
-
\`\`\`
|
|
887
|
-
SPAWN_WORKER:
|
|
888
|
-
type: <worker-type>
|
|
889
|
-
task: <one-line description>
|
|
890
|
-
context: <relevant files, decisions, or constraints>
|
|
891
|
-
acceptance_criteria:
|
|
892
|
-
- <criterion 1>
|
|
893
|
-
- <criterion 2>
|
|
894
|
-
\`\`\`
|
|
895
|
-
|
|
896
|
-
### Aggregating results
|
|
897
|
-
When workers complete, review their output:
|
|
898
|
-
- If a worker succeeded \u2014 incorporate the result and move forward.
|
|
899
|
-
- If a worker failed \u2014 diagnose the failure. Retry with adjusted instructions or escalate in the meeting.
|
|
900
|
-
- Summarise aggregated results before reporting back to the meeting.`;
|
|
901
|
-
}
|
|
902
|
-
var IDENTITIES = {
|
|
903
|
-
"arch-leader": `## IDENTITY
|
|
904
|
-
|
|
905
|
-
You are the **Architecture Leader**. You own system design decisions for this project.
|
|
906
|
-
|
|
907
|
-
Your responsibilities:
|
|
908
|
-
- Evaluate and propose system architecture (module boundaries, data flow, APIs).
|
|
909
|
-
- Design database schemas and data models.
|
|
910
|
-
- Analyse dependency graphs and flag coupling or circular-dependency risks.
|
|
911
|
-
- Ensure new features fit the existing architecture; propose refactors when they do not.
|
|
912
|
-
- Produce architecture decision records (ADRs) when significant choices are made.
|
|
913
|
-
|
|
914
|
-
You are a planner, not an implementer. You produce blueprints and hand implementation to Engineering.`,
|
|
915
|
-
"eng-leader": `## IDENTITY
|
|
916
|
-
|
|
917
|
-
You are the **Engineering Leader**. You own code quality and delivery for this project.
|
|
918
|
-
|
|
919
|
-
Your responsibilities:
|
|
920
|
-
- Break down approved designs into implementable tasks.
|
|
921
|
-
- Assign coding work to feature-dev, bug-fixer, and refactorer workers.
|
|
922
|
-
- Review worker output for correctness, style, and adherence to project conventions.
|
|
923
|
-
- Coordinate with QA to ensure changes are testable.
|
|
924
|
-
- Flag technical debt and propose refactoring when it reaches a threshold.
|
|
925
|
-
|
|
926
|
-
You write and ship code through your workers. You translate architecture into working software.`,
|
|
927
|
-
"qa-leader": `## IDENTITY
|
|
928
|
-
|
|
929
|
-
You are the **QA Leader**. You own quality assurance, testing strategy, and security posture.
|
|
930
|
-
|
|
931
|
-
Your responsibilities:
|
|
932
|
-
- Define test plans: unit tests, integration tests, and end-to-end flows.
|
|
933
|
-
- Spawn test-writer workers to create tests for new or changed code.
|
|
934
|
-
- Spawn test-runner workers to execute test suites and report results.
|
|
935
|
-
- Spawn security-auditor workers when new dependencies or sensitive code paths are introduced.
|
|
936
|
-
- Spawn perf-tester workers for performance-critical changes.
|
|
937
|
-
- Block merges that lack adequate test coverage or have failing tests.
|
|
938
|
-
|
|
939
|
-
You are the project's quality gate. Nothing ships without your sign-off.`,
|
|
940
|
-
"pm-leader": `## IDENTITY
|
|
941
|
-
|
|
942
|
-
You are the **Product Leader**. You own requirements clarity and user-facing coherence.
|
|
943
|
-
|
|
944
|
-
Your responsibilities:
|
|
945
|
-
- Analyse user requests and translate them into structured requirements.
|
|
946
|
-
- Map user flows to ensure feature completeness and good UX.
|
|
947
|
-
- Prioritise work items when resources are limited.
|
|
948
|
-
- Ensure the team is building what the user actually asked for, not what was assumed.
|
|
949
|
-
- Write acceptance criteria that other departments can verify against.
|
|
950
|
-
|
|
951
|
-
You are the voice of the user inside the team. You bridge intent and implementation.`,
|
|
952
|
-
"research-leader": `## IDENTITY
|
|
953
|
-
|
|
954
|
-
You are the **Research Leader**. You own information gathering and knowledge synthesis.
|
|
955
|
-
|
|
956
|
-
Your responsibilities:
|
|
957
|
-
- Explore the existing codebase to answer questions from other departments.
|
|
958
|
-
- Search documentation, READMEs, and external resources for relevant context.
|
|
959
|
-
- Run benchmarks when quantitative data is needed for a decision.
|
|
960
|
-
- Summarise findings in a structured, citable format.
|
|
961
|
-
- Maintain a knowledge base of discovered facts about the project.
|
|
962
|
-
|
|
963
|
-
You provide the evidence base. Other departments make decisions; you supply the facts.`
|
|
964
|
-
};
|
|
965
|
-
function getLeaderSystemPrompt(department, rules, projectContext) {
|
|
966
|
-
const dept = getDepartment(department);
|
|
967
|
-
const identity = IDENTITIES[dept.leaderRole];
|
|
968
|
-
if (!identity) {
|
|
969
|
-
throw new Error(`No identity prompt defined for leader role: ${dept.leaderRole}`);
|
|
970
|
-
}
|
|
971
|
-
const sections = [
|
|
972
|
-
identity,
|
|
973
|
-
meetingProtocol(),
|
|
974
|
-
workforceManagement(dept.description, dept.workerTypes),
|
|
975
|
-
`## RULES
|
|
976
|
-
|
|
977
|
-
${rulesBlock(rules)}`
|
|
978
|
-
];
|
|
979
|
-
if (projectContext) {
|
|
980
|
-
sections.push(projectContext);
|
|
981
|
-
}
|
|
982
|
-
return sections.join("\n\n");
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// src/agents/tiers.ts
|
|
986
|
-
function getTierConfig(tier) {
|
|
987
|
-
const config = TIER_CONFIGS[tier];
|
|
988
|
-
if (!config) {
|
|
989
|
-
throw new Error(`Unknown agent tier: ${tier}`);
|
|
990
|
-
}
|
|
991
|
-
return { ...config };
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// src/agents/worker-prompts.ts
|
|
995
|
-
var WORKER_DESCRIPTIONS = {
|
|
996
|
-
// Architecture workers
|
|
997
|
-
"schema-designer": "You design database schemas and data models. Output CREATE TABLE statements, type definitions, or ERD descriptions.",
|
|
998
|
-
"api-designer": "You design API surfaces \u2014 REST endpoints, RPC methods, or function signatures. Output OpenAPI snippets or typed interface definitions.",
|
|
999
|
-
"dependency-analyzer": "You analyse project dependencies and module coupling. Output dependency graphs, circular-dependency reports, or upgrade recommendations.",
|
|
1000
|
-
// Engineering workers
|
|
1001
|
-
"feature-dev": "You implement new features by writing production code. Follow the project conventions. Output complete, working code changes.",
|
|
1002
|
-
"bug-fixer": "You diagnose and fix bugs. Read the relevant code, identify the root cause, and produce a minimal correct fix.",
|
|
1003
|
-
"refactorer": "You improve existing code without changing its behaviour. Focus on readability, performance, or reducing duplication.",
|
|
1004
|
-
// QA workers
|
|
1005
|
-
"test-writer": "You write test cases \u2014 unit, integration, or end-to-end. Ensure each test has a clear assertion and covers the acceptance criteria.",
|
|
1006
|
-
"test-runner": "You execute test suites and report results. Run commands, capture output, and summarise pass/fail counts and failures.",
|
|
1007
|
-
"security-auditor": "You audit code for security vulnerabilities \u2014 injection, auth issues, insecure dependencies, exposed secrets. Output a structured finding list.",
|
|
1008
|
-
"perf-tester": "You run performance tests and benchmarks. Measure response time, throughput, or resource usage and report quantitative results.",
|
|
1009
|
-
// Product workers
|
|
1010
|
-
"requirements-analyzer": "You analyse user requests and existing documentation to produce structured requirements with acceptance criteria.",
|
|
1011
|
-
"user-flow-mapper": "You trace user-facing flows through the system \u2014 from input to output \u2014 and document each step, decision point, and edge case.",
|
|
1012
|
-
// Research workers
|
|
1013
|
-
"code-explorer": "You explore the codebase to answer specific questions. Read files, trace call chains, and summarise your findings.",
|
|
1014
|
-
"doc-searcher": "You search documentation, READMEs, comments, and external references to find relevant information.",
|
|
1015
|
-
"benchmark-runner": "You run benchmarks and collect quantitative data. Output structured results with methodology notes.",
|
|
1016
|
-
// Cross-cutting workers
|
|
1017
|
-
"minutes-writer": "You write structured meeting minutes from a transcript. Output: summary, decisions, action items, and @mentions.",
|
|
1018
|
-
compactor: "You compact long conversation transcripts into a shorter summary that preserves all decisions, action items, and open questions."
|
|
1019
|
-
};
|
|
1020
|
-
function buildWorkerPrompt(opts) {
|
|
1021
|
-
const { workerType, department, task, context, projectContext } = opts;
|
|
1022
|
-
const description = WORKER_DESCRIPTIONS[workerType];
|
|
1023
|
-
if (!description) {
|
|
1024
|
-
throw new Error(`Unknown worker type: ${workerType}`);
|
|
1025
|
-
}
|
|
1026
|
-
const sections = [
|
|
1027
|
-
`## IDENTITY
|
|
1028
|
-
|
|
1029
|
-
You are a **${workerType}** worker in the **${department}** department.
|
|
1030
|
-
|
|
1031
|
-
${description}`,
|
|
1032
|
-
`## TASK
|
|
1033
|
-
|
|
1034
|
-
${task}`
|
|
1035
|
-
];
|
|
1036
|
-
if (context) {
|
|
1037
|
-
sections.push(`## CONTEXT
|
|
1038
|
-
|
|
1039
|
-
${context}`);
|
|
1040
|
-
}
|
|
1041
|
-
if (projectContext) {
|
|
1042
|
-
sections.push(projectContext);
|
|
1043
|
-
}
|
|
1044
|
-
sections.push(`## OUTPUT RULES
|
|
1045
|
-
|
|
1046
|
-
1. Stay focused on the single task above. Do not wander into unrelated work.
|
|
1047
|
-
2. If the task is impossible or blocked, explain why clearly and stop \u2014 do not produce partial or guessed output.
|
|
1048
|
-
3. Prefer structured output: code blocks, lists, tables.
|
|
1049
|
-
4. Include file paths (always absolute) when referencing code.
|
|
1050
|
-
5. When your task is complete, end with a brief summary of what you did and any caveats.
|
|
1051
|
-
6. Respect the tool allowlist for the ${department} department \u2014 do not attempt to use tools you have not been granted.
|
|
1052
|
-
7. Do not commit, push, or deploy. Your output will be reviewed by your leader before any permanent action is taken.`);
|
|
1053
|
-
return sections.join("\n\n");
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// src/agents/agent-factory.ts
|
|
1057
|
-
var ORCHESTRATOR_SYSTEM_PROMPT = `## IDENTITY
|
|
1058
|
-
|
|
1059
|
-
You are the **Orchestrator** \u2014 a proxy and router, NOT a CEO. Your job is to receive the user's request, decompose it into department-level concerns, convene meetings, and route work to the appropriate leaders. You do not make product, architecture, or engineering decisions yourself.
|
|
1060
|
-
|
|
1061
|
-
## CORE RESPONSIBILITIES
|
|
1062
|
-
|
|
1063
|
-
1. **Request analysis** \u2014 When the user submits a request, determine which departments are relevant. Most requests involve 2-4 departments.
|
|
1064
|
-
|
|
1065
|
-
2. **Meeting convening** \u2014 Create a meeting with an agenda derived from the request. Invite the relevant leaders. You chair the meeting but you do not dominate it.
|
|
1066
|
-
|
|
1067
|
-
3. **Auto-routing** \u2014 For straightforward, single-department tasks (e.g., "run the tests"), skip the full meeting flow and route directly to the responsible leader.
|
|
1068
|
-
|
|
1069
|
-
4. **@USER mentions** \u2014 When a leader emits @USER_DECISION_NEEDED during a meeting:
|
|
1070
|
-
- Pause the meeting.
|
|
1071
|
-
- Surface the decision to the user with full context (options, trade-offs, supporters).
|
|
1072
|
-
- Resume the meeting once the user responds.
|
|
1073
|
-
|
|
1074
|
-
5. **Progress tracking** \u2014 Monitor worker completion events. Nudge leaders if a task is overdue or failed. Report final results back to the user.
|
|
1075
|
-
|
|
1076
|
-
## RULES
|
|
1077
|
-
|
|
1078
|
-
1. You are a facilitator. Do NOT override leader recommendations unless they conflict with an explicit user instruction.
|
|
1079
|
-
2. Keep your own token usage minimal \u2014 delegate analysis and execution to leaders and workers.
|
|
1080
|
-
3. Always preserve the user's exact wording when forwarding a request to a meeting.
|
|
1081
|
-
4. If the user's request is ambiguous, ask a clarifying question BEFORE convening a meeting.
|
|
1082
|
-
5. Never modify files, run tests, or execute code directly. All execution happens through workers spawned by leaders.
|
|
1083
|
-
6. When multiple departments disagree, facilitate resolution. Escalate to the user only when the team cannot converge after a full discussion round.
|
|
1084
|
-
7. After a meeting completes, provide the user with a concise summary: decisions made, action items, and any pending @USER items.
|
|
1085
|
-
8. Respect budget limits. If projected cost approaches the meeting budget, warn the user and request approval before proceeding.
|
|
1086
|
-
|
|
1087
|
-
## DEPARTMENT OVERVIEW
|
|
1088
|
-
|
|
1089
|
-
You can route work to these departments:
|
|
1090
|
-
- **architecture** \u2014 System design, schemas, API surfaces, dependency analysis.
|
|
1091
|
-
- **engineering** \u2014 Feature implementation, bug fixes, refactoring.
|
|
1092
|
-
- **qa** \u2014 Testing, security audits, performance testing.
|
|
1093
|
-
- **product** \u2014 Requirements analysis, user-flow mapping, prioritisation.
|
|
1094
|
-
- **research** \u2014 Codebase exploration, documentation search, benchmarks.
|
|
1095
|
-
|
|
1096
|
-
## OUTPUT FORMAT
|
|
1097
|
-
|
|
1098
|
-
When you need to convene a meeting, output:
|
|
1099
|
-
\`\`\`
|
|
1100
|
-
CONVENE_MEETING:
|
|
1101
|
-
topic: <meeting topic>
|
|
1102
|
-
agenda:
|
|
1103
|
-
- <item 1>
|
|
1104
|
-
- <item 2>
|
|
1105
|
-
departments:
|
|
1106
|
-
- <dept 1>
|
|
1107
|
-
- <dept 2>
|
|
1108
|
-
\`\`\`
|
|
1109
|
-
|
|
1110
|
-
When you route directly to a leader (no meeting), output:
|
|
1111
|
-
\`\`\`
|
|
1112
|
-
DIRECT_ROUTE:
|
|
1113
|
-
department: <department>
|
|
1114
|
-
task: <task description>
|
|
1115
|
-
\`\`\`
|
|
1116
|
-
`;
|
|
1117
|
-
function createAgentConfig(opts) {
|
|
1118
|
-
const { tier, role, department, task, context } = opts;
|
|
1119
|
-
const tierCfg = getTierConfig(tier);
|
|
1120
|
-
const allowedTools = [...DEPARTMENT_TOOLS[department] ?? []];
|
|
1121
|
-
let systemPrompt;
|
|
1122
|
-
switch (tier) {
|
|
1123
|
-
case "orchestrator": {
|
|
1124
|
-
systemPrompt = ORCHESTRATOR_SYSTEM_PROMPT;
|
|
1125
|
-
break;
|
|
1126
|
-
}
|
|
1127
|
-
case "leader": {
|
|
1128
|
-
systemPrompt = getLeaderSystemPrompt(department);
|
|
1129
|
-
break;
|
|
1130
|
-
}
|
|
1131
|
-
case "worker": {
|
|
1132
|
-
if (!task) {
|
|
1133
|
-
throw new Error("Worker agents require a task description");
|
|
1134
|
-
}
|
|
1135
|
-
systemPrompt = buildWorkerPrompt({
|
|
1136
|
-
workerType: role,
|
|
1137
|
-
department,
|
|
1138
|
-
task,
|
|
1139
|
-
context
|
|
1140
|
-
});
|
|
1141
|
-
break;
|
|
1142
|
-
}
|
|
1143
|
-
default: {
|
|
1144
|
-
const _exhaustive = tier;
|
|
1145
|
-
throw new Error(`Unknown tier: ${_exhaustive}`);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
const model = tier === "worker" && department === "research" ? "claude-haiku-4-5" : tierCfg.model;
|
|
1149
|
-
return {
|
|
1150
|
-
model,
|
|
1151
|
-
maxTurns: tierCfg.maxTurns,
|
|
1152
|
-
allowedTools
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// src/agents/claude-invoker.ts
|
|
1157
|
-
import { spawn } from "child_process";
|
|
1158
|
-
import { execSync } from "child_process";
|
|
1159
|
-
var _claudeAvailable = null;
|
|
1160
|
-
function isClaudeAvailable() {
|
|
1161
|
-
if (_claudeAvailable !== null) return _claudeAvailable;
|
|
1162
|
-
try {
|
|
1163
|
-
execSync("which claude", { stdio: "ignore" });
|
|
1164
|
-
_claudeAvailable = true;
|
|
1165
|
-
} catch {
|
|
1166
|
-
_claudeAvailable = false;
|
|
1167
|
-
}
|
|
1168
|
-
return _claudeAvailable;
|
|
1169
|
-
}
|
|
1170
|
-
function isMockMode() {
|
|
1171
|
-
if (process.env["COLESLAW_MOCK"] === "1") return true;
|
|
1172
|
-
return !isClaudeAvailable();
|
|
1173
|
-
}
|
|
1174
|
-
async function invokeMock(options) {
|
|
1175
|
-
await new Promise((resolve) => setTimeout(resolve, 30 + Math.random() * 70));
|
|
1176
|
-
const lower = options.prompt.toLowerCase();
|
|
1177
|
-
let output;
|
|
1178
|
-
if (lower.includes("opening") || lower.includes("initial position")) {
|
|
1179
|
-
output = "[Mock] Acknowledged the agenda. From my department perspective, I see several important considerations. We should ensure proper separation of concerns and define clear boundaries. Key concern: we must avoid tight coupling between new and existing components.";
|
|
1180
|
-
} else if (lower.includes("synthesis") || lower.includes("final position")) {
|
|
1181
|
-
output = "[Mock] FINAL POSITION: I support the agreed approach. Action items for my department: (1) Deliver the agreed outputs, (2) Coordinate with dependent departments, (3) Report completion status once done.";
|
|
1182
|
-
} else if (lower.includes("discussion") || lower.includes("perspective")) {
|
|
1183
|
-
output = "[Mock] Building on the previous points, I propose we move forward with the discussed approach. This aligns with existing patterns and allows parallel work across departments. I can have my team start on the deliverables immediately.";
|
|
1184
|
-
} else if (lower.includes("schema") || lower.includes("design")) {
|
|
1185
|
-
output = "[Mock] Schema analysis complete. Proposed 3 tables with proper foreign-key relationships and indexes. No circular dependencies detected.";
|
|
1186
|
-
} else if (lower.includes("test")) {
|
|
1187
|
-
output = "[Mock] Test suite generated: 8 unit tests, 2 integration tests. All assertions use strict equality. Coverage target: 90%.";
|
|
1188
|
-
} else if (lower.includes("implement") || lower.includes("build") || lower.includes("feature")) {
|
|
1189
|
-
output = "[Mock] Implementation complete. Created 2 new files, modified 1 existing file. All changes follow project conventions.";
|
|
1190
|
-
} else if (lower.includes("research") || lower.includes("explore")) {
|
|
1191
|
-
output = "[Mock] Research complete. Found 5 relevant code references and 2 documentation entries. Summary provided in structured format.";
|
|
1192
|
-
} else if (lower.includes("security") || lower.includes("audit")) {
|
|
1193
|
-
output = "[Mock] Security audit complete. No critical vulnerabilities found. 1 advisory: ensure input validation on user-facing endpoints.";
|
|
1194
|
-
} else if (lower.includes("fix") || lower.includes("bug")) {
|
|
1195
|
-
output = "[Mock] Bug fix applied. Root cause identified and corrected. Fix verified with regression test.";
|
|
1196
|
-
} else {
|
|
1197
|
-
output = "[Mock] Task completed successfully. Output ready for review.";
|
|
1198
|
-
}
|
|
1199
|
-
return {
|
|
1200
|
-
success: true,
|
|
1201
|
-
output,
|
|
1202
|
-
costUsd: 0
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
async function invokeReal(options) {
|
|
1206
|
-
const {
|
|
1207
|
-
prompt,
|
|
1208
|
-
systemPrompt,
|
|
1209
|
-
allowedTools,
|
|
1210
|
-
maxTurns,
|
|
1211
|
-
cwd,
|
|
1212
|
-
timeoutMs = 3e5
|
|
1213
|
-
} = options;
|
|
1214
|
-
const args = [
|
|
1215
|
-
"--print",
|
|
1216
|
-
"--output-format",
|
|
1217
|
-
"json",
|
|
1218
|
-
"--no-session-persistence"
|
|
1219
|
-
];
|
|
1220
|
-
if (systemPrompt) {
|
|
1221
|
-
args.push("--append-system-prompt", systemPrompt);
|
|
1222
|
-
}
|
|
1223
|
-
if (allowedTools.length > 0) {
|
|
1224
|
-
args.push("--allowedTools", allowedTools.join(","));
|
|
1225
|
-
}
|
|
1226
|
-
logger.info("Invoking Claude CLI", {
|
|
1227
|
-
promptLength: prompt.length,
|
|
1228
|
-
toolCount: allowedTools.length
|
|
1229
|
-
});
|
|
1230
|
-
return new Promise((resolve) => {
|
|
1231
|
-
const child = spawn("claude", args, {
|
|
1232
|
-
cwd: cwd ?? process.cwd(),
|
|
1233
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1234
|
-
env: { ...process.env }
|
|
1235
|
-
});
|
|
1236
|
-
child.stdin.write(prompt);
|
|
1237
|
-
child.stdin.end();
|
|
1238
|
-
const stdoutChunks = [];
|
|
1239
|
-
const stderrChunks = [];
|
|
1240
|
-
child.stdout.on("data", (chunk) => {
|
|
1241
|
-
stdoutChunks.push(chunk);
|
|
1242
|
-
});
|
|
1243
|
-
child.stderr.on("data", (chunk) => {
|
|
1244
|
-
stderrChunks.push(chunk);
|
|
1245
|
-
});
|
|
1246
|
-
const timer = setTimeout(() => {
|
|
1247
|
-
logger.warn("Claude CLI timed out, killing process", { timeoutMs });
|
|
1248
|
-
child.kill("SIGTERM");
|
|
1249
|
-
setTimeout(() => {
|
|
1250
|
-
if (!child.killed) child.kill("SIGKILL");
|
|
1251
|
-
}, 5e3);
|
|
1252
|
-
}, timeoutMs);
|
|
1253
|
-
child.on("close", (code) => {
|
|
1254
|
-
clearTimeout(timer);
|
|
1255
|
-
const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
1256
|
-
const rawStderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
1257
|
-
if (code !== 0) {
|
|
1258
|
-
logger.error("Claude CLI exited with non-zero code", {
|
|
1259
|
-
exitCode: String(code)
|
|
1260
|
-
});
|
|
1261
|
-
resolve({
|
|
1262
|
-
success: false,
|
|
1263
|
-
output: "",
|
|
1264
|
-
error: rawStderr || `Claude CLI exited with code ${code}`
|
|
1265
|
-
});
|
|
1266
|
-
return;
|
|
1267
|
-
}
|
|
1268
|
-
const parsed = parseCliOutput(rawStdout);
|
|
1269
|
-
logger.info("Claude CLI invocation completed", {
|
|
1270
|
-
outputLength: String(parsed.output.length)
|
|
1271
|
-
});
|
|
1272
|
-
resolve(parsed);
|
|
1273
|
-
});
|
|
1274
|
-
child.on("error", (err) => {
|
|
1275
|
-
clearTimeout(timer);
|
|
1276
|
-
logger.error("Failed to spawn Claude CLI", { error: err.message });
|
|
1277
|
-
resolve({
|
|
1278
|
-
success: false,
|
|
1279
|
-
output: "",
|
|
1280
|
-
error: `Failed to spawn Claude CLI: ${err.message}`
|
|
1281
|
-
});
|
|
1282
|
-
});
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
function parseCliOutput(raw) {
|
|
1286
|
-
const trimmed = raw.trim();
|
|
1287
|
-
if (!trimmed) {
|
|
1288
|
-
return { success: false, output: "", error: "Empty output from Claude CLI" };
|
|
1289
|
-
}
|
|
1290
|
-
try {
|
|
1291
|
-
const json = JSON.parse(trimmed);
|
|
1292
|
-
if (json.is_error || json.error) {
|
|
1293
|
-
return {
|
|
1294
|
-
success: false,
|
|
1295
|
-
output: json.result ?? json.output ?? "",
|
|
1296
|
-
error: json.error ?? "Unknown CLI error",
|
|
1297
|
-
costUsd: json.cost_usd
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
return {
|
|
1301
|
-
success: true,
|
|
1302
|
-
output: json.result ?? json.output ?? trimmed,
|
|
1303
|
-
costUsd: json.cost_usd
|
|
1304
|
-
};
|
|
1305
|
-
} catch {
|
|
1306
|
-
return {
|
|
1307
|
-
success: true,
|
|
1308
|
-
output: trimmed
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
async function invokeClaude(options) {
|
|
1313
|
-
if (isMockMode()) {
|
|
1314
|
-
if (!isClaudeAvailable()) {
|
|
1315
|
-
logger.warn("Claude CLI not found on PATH \u2014 using mock mode");
|
|
1316
|
-
} else {
|
|
1317
|
-
logger.info("COLESLAW_MOCK=1 set \u2014 using mock mode");
|
|
1318
|
-
}
|
|
1319
|
-
return invokeMock(options);
|
|
1320
|
-
}
|
|
1321
|
-
return invokeReal(options);
|
|
1322
|
-
}
|
|
1323
|
-
function buildInvokeOptions(config, prompt, systemPrompt, cwd) {
|
|
1324
|
-
return {
|
|
1325
|
-
prompt,
|
|
1326
|
-
systemPrompt,
|
|
1327
|
-
allowedTools: config.allowedTools,
|
|
1328
|
-
maxTurns: config.maxTurns,
|
|
1329
|
-
cwd
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// src/orchestrator/meeting-runner.ts
|
|
1334
|
-
async function queryAgent(config, prompt) {
|
|
1335
|
-
const agentConfig = createAgentConfig({
|
|
1336
|
-
tier: "leader",
|
|
1337
|
-
role: config.role,
|
|
1338
|
-
department: config.department
|
|
1339
|
-
});
|
|
1340
|
-
const invokeOpts = buildInvokeOptions(
|
|
1341
|
-
agentConfig,
|
|
1342
|
-
prompt,
|
|
1343
|
-
config.systemPrompt
|
|
1344
|
-
);
|
|
1345
|
-
invokeOpts.timeoutMs = 6e5;
|
|
1346
|
-
const result = await invokeClaude(invokeOpts);
|
|
1347
|
-
if (!result.success) {
|
|
1348
|
-
logger.warn(`Agent query failed for ${config.role}: ${result.error}`);
|
|
1349
|
-
return `[Error from ${config.role}] ${result.error ?? "Unknown error during agent invocation"}`;
|
|
1350
|
-
}
|
|
1351
|
-
return result.output;
|
|
1352
|
-
}
|
|
1353
|
-
function insertTranscriptEntry(meetingId, speakerId, speakerRole, agendaItemIndex, roundNumber, content) {
|
|
1354
|
-
const db = getDb();
|
|
1355
|
-
const now = Date.now();
|
|
1356
|
-
const tokenCount = Math.ceil(content.length / 4);
|
|
1357
|
-
const result = db.prepare(
|
|
1358
|
-
`INSERT INTO transcript_entries
|
|
1359
|
-
(meeting_id, speaker_id, speaker_role, agenda_item_index, round_number, content, token_count, created_at)
|
|
1360
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1361
|
-
).run(meetingId, speakerId, speakerRole, agendaItemIndex, roundNumber, content, tokenCount, now);
|
|
1362
|
-
return {
|
|
1363
|
-
id: Number(result.lastInsertRowid),
|
|
1364
|
-
meetingId,
|
|
1365
|
-
speakerId,
|
|
1366
|
-
speakerRole,
|
|
1367
|
-
agendaItemIndex,
|
|
1368
|
-
roundNumber,
|
|
1369
|
-
content,
|
|
1370
|
-
tokenCount,
|
|
1371
|
-
createdAt: now
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
function getTranscript(meetingId) {
|
|
1375
|
-
const db = getDb();
|
|
1376
|
-
const rows = db.prepare("SELECT * FROM transcript_entries WHERE meeting_id = ? ORDER BY created_at ASC").all(meetingId);
|
|
1377
|
-
return rows.map((r) => ({
|
|
1378
|
-
id: r.id,
|
|
1379
|
-
meetingId: r.meeting_id,
|
|
1380
|
-
speakerId: r.speaker_id,
|
|
1381
|
-
speakerRole: r.speaker_role,
|
|
1382
|
-
agendaItemIndex: r.agenda_item_index,
|
|
1383
|
-
roundNumber: r.round_number,
|
|
1384
|
-
content: r.content,
|
|
1385
|
-
tokenCount: r.token_count,
|
|
1386
|
-
createdAt: r.created_at
|
|
1387
|
-
}));
|
|
1388
|
-
}
|
|
1389
|
-
var MeetingRunner = class {
|
|
1390
|
-
meetingId;
|
|
1391
|
-
leaders;
|
|
1392
|
-
maxRoundsPerItem;
|
|
1393
|
-
projectContext;
|
|
1394
|
-
constructor(meetingId, leaders, projectContext) {
|
|
1395
|
-
this.meetingId = meetingId;
|
|
1396
|
-
this.leaders = leaders;
|
|
1397
|
-
this.maxRoundsPerItem = DEFAULT_MEETING_CONFIG.maxRoundsPerItem;
|
|
1398
|
-
this.projectContext = projectContext;
|
|
1399
|
-
}
|
|
1400
|
-
// ---- public entry point -------------------------------------------------
|
|
1401
|
-
/**
|
|
1402
|
-
* Run the complete meeting lifecycle: opening -> discussion -> synthesis -> minutes.
|
|
1403
|
-
*/
|
|
1404
|
-
async run() {
|
|
1405
|
-
const meeting = getMeeting(this.meetingId);
|
|
1406
|
-
if (!meeting) {
|
|
1407
|
-
throw new Error(`Meeting not found: ${this.meetingId}`);
|
|
1408
|
-
}
|
|
1409
|
-
logger.info(`Starting meeting: ${meeting.topic}`, { meetingId: this.meetingId });
|
|
1410
|
-
try {
|
|
1411
|
-
await this.openingPhase();
|
|
1412
|
-
await this.discussionPhase();
|
|
1413
|
-
await this.synthesisPhase();
|
|
1414
|
-
await this.generateMinutes();
|
|
1415
|
-
updateMeeting(this.meetingId, {
|
|
1416
|
-
status: "completed",
|
|
1417
|
-
completedAt: Date.now()
|
|
1418
|
-
});
|
|
1419
|
-
logger.info(`Meeting completed: ${meeting.topic}`, { meetingId: this.meetingId });
|
|
1420
|
-
} catch (err) {
|
|
1421
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1422
|
-
logger.error(`Meeting failed: ${errorMsg}`, { meetingId: this.meetingId });
|
|
1423
|
-
updateMeeting(this.meetingId, {
|
|
1424
|
-
status: "failed",
|
|
1425
|
-
completedAt: Date.now()
|
|
1426
|
-
});
|
|
1427
|
-
throw err;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
// ---- phases -------------------------------------------------------------
|
|
1431
|
-
/**
|
|
1432
|
-
* Opening phase: each leader states their initial position on the meeting
|
|
1433
|
-
* topic and agenda.
|
|
1434
|
-
*/
|
|
1435
|
-
async openingPhase() {
|
|
1436
|
-
this.setPhase("opening");
|
|
1437
|
-
const meeting = getMeeting(this.meetingId);
|
|
1438
|
-
const agendaText = meeting.agenda.map((a, i) => ` ${i + 1}. ${a}`).join("\n");
|
|
1439
|
-
for (const leader of this.leaders) {
|
|
1440
|
-
const prompt = `MEETING OPENING
|
|
1441
|
-
|
|
1442
|
-
Topic: ${meeting.topic}
|
|
1443
|
-
Agenda:
|
|
1444
|
-
${agendaText}
|
|
1445
|
-
|
|
1446
|
-
Please state your initial position on this topic from your department's perspective. Identify any concerns, dependencies, or risks relevant to your area.`;
|
|
1447
|
-
const config = {
|
|
1448
|
-
role: leader.role,
|
|
1449
|
-
department: leader.department,
|
|
1450
|
-
systemPrompt: getLeaderSystemPrompt(leader.department, void 0, this.projectContext)
|
|
1451
|
-
};
|
|
1452
|
-
const response = await queryAgent(config, prompt);
|
|
1453
|
-
insertTranscriptEntry(
|
|
1454
|
-
this.meetingId,
|
|
1455
|
-
leader.id,
|
|
1456
|
-
leader.role,
|
|
1457
|
-
-1,
|
|
1458
|
-
// -1 signals the opening phase (not tied to a specific agenda item)
|
|
1459
|
-
0,
|
|
1460
|
-
response
|
|
1461
|
-
);
|
|
1462
|
-
eventBus.emitAgentEvent({
|
|
1463
|
-
kind: "message_sent",
|
|
1464
|
-
fromId: leader.id,
|
|
1465
|
-
toId: "meeting",
|
|
1466
|
-
summary: `[Opening] ${leader.role}: ${response.slice(0, 80)}...`
|
|
1467
|
-
});
|
|
1468
|
-
logger.debug(`Opening statement from ${leader.role}`, {
|
|
1469
|
-
meetingId: this.meetingId,
|
|
1470
|
-
agentId: leader.id
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
/**
|
|
1475
|
-
* Discussion phase: for each agenda item, leaders take turns responding in
|
|
1476
|
-
* round-robin fashion for up to `maxRoundsPerItem` rounds.
|
|
1477
|
-
*/
|
|
1478
|
-
async discussionPhase() {
|
|
1479
|
-
this.setPhase("discussion");
|
|
1480
|
-
const meeting = getMeeting(this.meetingId);
|
|
1481
|
-
for (let itemIdx = 0; itemIdx < meeting.agenda.length; itemIdx++) {
|
|
1482
|
-
const agendaItem = meeting.agenda[itemIdx];
|
|
1483
|
-
logger.info(`Discussing agenda item ${itemIdx + 1}: ${agendaItem}`, {
|
|
1484
|
-
meetingId: this.meetingId
|
|
1485
|
-
});
|
|
1486
|
-
for (let round = 1; round <= this.maxRoundsPerItem; round++) {
|
|
1487
|
-
for (const leader of this.leaders) {
|
|
1488
|
-
const transcript = getTranscript(this.meetingId);
|
|
1489
|
-
const transcriptText = this.formatTranscript(transcript);
|
|
1490
|
-
const prompt = `MEETING DISCUSSION \u2014 Round ${round}/${this.maxRoundsPerItem}
|
|
1491
|
-
|
|
1492
|
-
Current agenda item (${itemIdx + 1}/${meeting.agenda.length}): ${agendaItem}
|
|
1493
|
-
|
|
1494
|
-
Transcript so far:
|
|
1495
|
-
${transcriptText}
|
|
1496
|
-
|
|
1497
|
-
Provide your department's perspective on this agenda item. Build on what others have said. If you agree, say so and add specifics. If you disagree, state your reasoning and propose an alternative.`;
|
|
1498
|
-
const config = {
|
|
1499
|
-
role: leader.role,
|
|
1500
|
-
department: leader.department,
|
|
1501
|
-
systemPrompt: getLeaderSystemPrompt(leader.department, void 0, this.projectContext)
|
|
1502
|
-
};
|
|
1503
|
-
const response = await queryAgent(config, prompt);
|
|
1504
|
-
insertTranscriptEntry(
|
|
1505
|
-
this.meetingId,
|
|
1506
|
-
leader.id,
|
|
1507
|
-
leader.role,
|
|
1508
|
-
itemIdx,
|
|
1509
|
-
round,
|
|
1510
|
-
response
|
|
1511
|
-
);
|
|
1512
|
-
eventBus.emitAgentEvent({
|
|
1513
|
-
kind: "message_sent",
|
|
1514
|
-
fromId: leader.id,
|
|
1515
|
-
toId: "meeting",
|
|
1516
|
-
summary: `[Item ${itemIdx + 1}, R${round}] ${leader.role}: ${response.slice(0, 80)}...`
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
/**
|
|
1523
|
-
* Synthesis phase: each leader states their final position, commitments,
|
|
1524
|
-
* and action items.
|
|
1525
|
-
*/
|
|
1526
|
-
async synthesisPhase() {
|
|
1527
|
-
this.setPhase("synthesis");
|
|
1528
|
-
const transcript = getTranscript(this.meetingId);
|
|
1529
|
-
const transcriptText = this.formatTranscript(transcript);
|
|
1530
|
-
for (const leader of this.leaders) {
|
|
1531
|
-
const prompt = `MEETING SYNTHESIS
|
|
1532
|
-
|
|
1533
|
-
The discussion is complete. Here is the full transcript:
|
|
1534
|
-
${transcriptText}
|
|
1535
|
-
|
|
1536
|
-
State your final position. List the action items your department commits to. Flag any unresolved concerns or items requiring user decision.`;
|
|
1537
|
-
const config = {
|
|
1538
|
-
role: leader.role,
|
|
1539
|
-
department: leader.department,
|
|
1540
|
-
systemPrompt: getLeaderSystemPrompt(leader.department, void 0, this.projectContext)
|
|
1541
|
-
};
|
|
1542
|
-
const response = await queryAgent(config, prompt);
|
|
1543
|
-
insertTranscriptEntry(
|
|
1544
|
-
this.meetingId,
|
|
1545
|
-
leader.id,
|
|
1546
|
-
leader.role,
|
|
1547
|
-
-2,
|
|
1548
|
-
// -2 signals synthesis phase
|
|
1549
|
-
0,
|
|
1550
|
-
response
|
|
1551
|
-
);
|
|
1552
|
-
eventBus.emitAgentEvent({
|
|
1553
|
-
kind: "message_sent",
|
|
1554
|
-
fromId: leader.id,
|
|
1555
|
-
toId: "meeting",
|
|
1556
|
-
summary: `[Synthesis] ${leader.role}: ${response.slice(0, 80)}...`
|
|
1557
|
-
});
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Generate meeting minutes by concatenating and formatting the transcript.
|
|
1562
|
-
*
|
|
1563
|
-
* In the future this will use a dedicated minutes-writer agent. For now it
|
|
1564
|
-
* formats the transcript into a structured summary.
|
|
1565
|
-
*/
|
|
1566
|
-
async generateMinutes() {
|
|
1567
|
-
this.setPhase("minutes-generation");
|
|
1568
|
-
const meeting = getMeeting(this.meetingId);
|
|
1569
|
-
const transcript = getTranscript(this.meetingId);
|
|
1570
|
-
const sections = [];
|
|
1571
|
-
sections.push(`# Meeting Minutes`);
|
|
1572
|
-
sections.push(`## Topic: ${meeting.topic}`);
|
|
1573
|
-
sections.push(`## Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1574
|
-
sections.push(`## Participants: ${this.leaders.map((l) => l.role).join(", ")}`);
|
|
1575
|
-
sections.push("");
|
|
1576
|
-
sections.push(`## Agenda`);
|
|
1577
|
-
meeting.agenda.forEach((item, i) => {
|
|
1578
|
-
sections.push(`${i + 1}. ${item}`);
|
|
1579
|
-
});
|
|
1580
|
-
sections.push("");
|
|
1581
|
-
const openingEntries = transcript.filter((e) => e.agendaItemIndex === -1);
|
|
1582
|
-
if (openingEntries.length > 0) {
|
|
1583
|
-
sections.push(`## Opening Statements`);
|
|
1584
|
-
for (const entry of openingEntries) {
|
|
1585
|
-
sections.push(`### ${entry.speakerRole}`);
|
|
1586
|
-
sections.push(entry.content);
|
|
1587
|
-
sections.push("");
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
for (let i = 0; i < meeting.agenda.length; i++) {
|
|
1591
|
-
const itemEntries = transcript.filter((e) => e.agendaItemIndex === i);
|
|
1592
|
-
if (itemEntries.length > 0) {
|
|
1593
|
-
sections.push(`## Discussion: ${meeting.agenda[i]}`);
|
|
1594
|
-
for (const entry of itemEntries) {
|
|
1595
|
-
sections.push(`**${entry.speakerRole}** (round ${entry.roundNumber}):`);
|
|
1596
|
-
sections.push(entry.content);
|
|
1597
|
-
sections.push("");
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
const synthesisEntries = transcript.filter((e) => e.agendaItemIndex === -2);
|
|
1602
|
-
if (synthesisEntries.length > 0) {
|
|
1603
|
-
sections.push(`## Final Positions`);
|
|
1604
|
-
for (const entry of synthesisEntries) {
|
|
1605
|
-
sections.push(`### ${entry.speakerRole}`);
|
|
1606
|
-
sections.push(entry.content);
|
|
1607
|
-
sections.push("");
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
const content = sections.join("\n");
|
|
1611
|
-
const actionItems = this.leaders.map((leader, idx) => ({
|
|
1612
|
-
id: `action-${this.meetingId}-${idx}`,
|
|
1613
|
-
title: `${leader.role} deliverables`,
|
|
1614
|
-
description: `Action items committed by ${leader.role} during synthesis phase`,
|
|
1615
|
-
assignedDepartment: leader.department,
|
|
1616
|
-
assignedRole: leader.role,
|
|
1617
|
-
priority: "medium",
|
|
1618
|
-
dependencies: [],
|
|
1619
|
-
acceptanceCriteria: ["Deliverables completed as stated in final position"]
|
|
1620
|
-
}));
|
|
1621
|
-
createMinutes({
|
|
1622
|
-
meetingId: this.meetingId,
|
|
1623
|
-
format: "summary",
|
|
1624
|
-
content,
|
|
1625
|
-
actionItems
|
|
1626
|
-
});
|
|
1627
|
-
logger.info("Minutes generated", { meetingId: this.meetingId });
|
|
1628
|
-
}
|
|
1629
|
-
// ---- helpers ------------------------------------------------------------
|
|
1630
|
-
setPhase(phase) {
|
|
1631
|
-
const statusMap = {
|
|
1632
|
-
"orchestrator-phase": "pending",
|
|
1633
|
-
"convening": "convening",
|
|
1634
|
-
"opening": "opening",
|
|
1635
|
-
"discussion": "discussion",
|
|
1636
|
-
"research-break": "discussion",
|
|
1637
|
-
"synthesis": "synthesis",
|
|
1638
|
-
"minutes-generation": "minutes-generation"
|
|
1639
|
-
};
|
|
1640
|
-
updateMeeting(this.meetingId, {
|
|
1641
|
-
phase,
|
|
1642
|
-
status: statusMap[phase] ?? "discussion"
|
|
1643
|
-
});
|
|
1644
|
-
logger.debug(`Meeting phase: ${phase}`, { meetingId: this.meetingId });
|
|
1645
|
-
}
|
|
1646
|
-
formatTranscript(entries) {
|
|
1647
|
-
if (entries.length === 0) return "(No transcript entries yet)";
|
|
1648
|
-
return entries.map((e) => {
|
|
1649
|
-
let phaseLabel;
|
|
1650
|
-
if (e.agendaItemIndex === -1) phaseLabel = "Opening";
|
|
1651
|
-
else if (e.agendaItemIndex === -2) phaseLabel = "Synthesis";
|
|
1652
|
-
else phaseLabel = `Item ${e.agendaItemIndex + 1}, Round ${e.roundNumber}`;
|
|
1653
|
-
return `[${phaseLabel}] ${e.speakerRole}: ${e.content}`;
|
|
1654
|
-
}).join("\n\n");
|
|
1655
|
-
}
|
|
1656
|
-
};
|
|
1657
|
-
|
|
1658
|
-
// src/agents/project-analyzer.ts
|
|
1659
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
1660
|
-
import { join, basename, extname } from "path";
|
|
1661
|
-
var CONFIG_FILE_PATTERNS = [
|
|
1662
|
-
"tsconfig.json",
|
|
1663
|
-
"tsconfig.build.json",
|
|
1664
|
-
"tsconfig.node.json",
|
|
1665
|
-
".eslintrc",
|
|
1666
|
-
".eslintrc.js",
|
|
1667
|
-
".eslintrc.cjs",
|
|
1668
|
-
".eslintrc.json",
|
|
1669
|
-
".eslintrc.yml",
|
|
1670
|
-
".eslintrc.yaml",
|
|
1671
|
-
"eslint.config.js",
|
|
1672
|
-
"eslint.config.mjs",
|
|
1673
|
-
"eslint.config.cjs",
|
|
1674
|
-
"eslint.config.ts",
|
|
1675
|
-
".prettierrc",
|
|
1676
|
-
".prettierrc.js",
|
|
1677
|
-
".prettierrc.cjs",
|
|
1678
|
-
".prettierrc.json",
|
|
1679
|
-
".prettierrc.yml",
|
|
1680
|
-
".prettierrc.yaml",
|
|
1681
|
-
"prettier.config.js",
|
|
1682
|
-
"prettier.config.mjs",
|
|
1683
|
-
"prettier.config.cjs",
|
|
1684
|
-
"biome.json",
|
|
1685
|
-
"biome.jsonc",
|
|
1686
|
-
"vite.config.ts",
|
|
1687
|
-
"vite.config.js",
|
|
1688
|
-
"vite.config.mjs",
|
|
1689
|
-
"webpack.config.js",
|
|
1690
|
-
"webpack.config.ts",
|
|
1691
|
-
"webpack.config.mjs",
|
|
1692
|
-
"rollup.config.js",
|
|
1693
|
-
"rollup.config.ts",
|
|
1694
|
-
"rollup.config.mjs",
|
|
1695
|
-
"tsup.config.ts",
|
|
1696
|
-
"tsup.config.js",
|
|
1697
|
-
"esbuild.config.js",
|
|
1698
|
-
"esbuild.config.ts",
|
|
1699
|
-
"jest.config.js",
|
|
1700
|
-
"jest.config.ts",
|
|
1701
|
-
"jest.config.mjs",
|
|
1702
|
-
"vitest.config.ts",
|
|
1703
|
-
"vitest.config.js",
|
|
1704
|
-
"vitest.config.mts",
|
|
1705
|
-
".mocharc.yml",
|
|
1706
|
-
".mocharc.json",
|
|
1707
|
-
".mocharc.js",
|
|
1708
|
-
".babelrc",
|
|
1709
|
-
"babel.config.js",
|
|
1710
|
-
"babel.config.json",
|
|
1711
|
-
".swcrc",
|
|
1712
|
-
"turbo.json",
|
|
1713
|
-
"nx.json"
|
|
1714
|
-
];
|
|
1715
|
-
var UTIL_DIR_NAMES = ["utils", "lib", "helpers", "shared", "common"];
|
|
1716
|
-
var MANIFEST_FILES = [
|
|
1717
|
-
// -- JavaScript / TypeScript --
|
|
1718
|
-
{
|
|
1719
|
-
file: "package.json",
|
|
1720
|
-
language: "typescript",
|
|
1721
|
-
// refined later if no TS config
|
|
1722
|
-
parse: (content) => {
|
|
1723
|
-
const raw = JSON.parse(content);
|
|
1724
|
-
return {
|
|
1725
|
-
dependencies: raw.dependencies ?? {},
|
|
1726
|
-
devDependencies: raw.devDependencies ?? {},
|
|
1727
|
-
scripts: raw.scripts ?? {},
|
|
1728
|
-
metadata: { type: raw.type ?? "commonjs", name: raw.name ?? "" }
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
},
|
|
1732
|
-
// -- Python --
|
|
1733
|
-
{
|
|
1734
|
-
file: "pyproject.toml",
|
|
1735
|
-
language: "python",
|
|
1736
|
-
parse: (content) => {
|
|
1737
|
-
const deps = {};
|
|
1738
|
-
const devDeps = {};
|
|
1739
|
-
const scripts = {};
|
|
1740
|
-
for (const m of content.matchAll(/^\s*"([^"]+?)(?:[><=!~]+.*)?".*$/gm)) {
|
|
1741
|
-
deps[m[1]] = "*";
|
|
1742
|
-
}
|
|
1743
|
-
for (const m of content.matchAll(/^(\w[\w-]*)\s*=\s*"([^"]+)"/gm)) {
|
|
1744
|
-
scripts[m[1]] = m[2];
|
|
1745
|
-
}
|
|
1746
|
-
return { dependencies: deps, devDependencies: devDeps, scripts, metadata: {} };
|
|
1747
|
-
}
|
|
1748
|
-
},
|
|
1749
|
-
{
|
|
1750
|
-
file: "requirements.txt",
|
|
1751
|
-
language: "python",
|
|
1752
|
-
parse: (content) => {
|
|
1753
|
-
const deps = {};
|
|
1754
|
-
for (const line of content.split("\n")) {
|
|
1755
|
-
const trimmed = line.trim();
|
|
1756
|
-
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
1757
|
-
const match = trimmed.match(/^([a-zA-Z0-9_-]+)\s*([><=!~].*)?$/);
|
|
1758
|
-
if (match) deps[match[1]] = match[2]?.trim() ?? "*";
|
|
1759
|
-
}
|
|
1760
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: {} };
|
|
1761
|
-
}
|
|
1762
|
-
},
|
|
1763
|
-
{
|
|
1764
|
-
file: "Pipfile",
|
|
1765
|
-
language: "python",
|
|
1766
|
-
parse: (content) => {
|
|
1767
|
-
const deps = {};
|
|
1768
|
-
const inPackages = content.indexOf("[packages]");
|
|
1769
|
-
if (inPackages >= 0) {
|
|
1770
|
-
const section = content.slice(inPackages);
|
|
1771
|
-
for (const m of section.matchAll(/^(\w[\w-]*)\s*=\s*"([^"]+)"/gm)) {
|
|
1772
|
-
deps[m[1]] = m[2];
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: {} };
|
|
1776
|
-
}
|
|
1777
|
-
},
|
|
1778
|
-
// -- Java / Kotlin (Gradle) --
|
|
1779
|
-
{
|
|
1780
|
-
file: "build.gradle",
|
|
1781
|
-
language: "java",
|
|
1782
|
-
parse: (content) => {
|
|
1783
|
-
const deps = {};
|
|
1784
|
-
for (const m of content.matchAll(/(?:implementation|api|compileOnly)\s+['"]([^'"]+)['"]/g)) {
|
|
1785
|
-
const parts = m[1].split(":");
|
|
1786
|
-
if (parts.length >= 2) deps[`${parts[0]}:${parts[1]}`] = parts[2] ?? "*";
|
|
1787
|
-
}
|
|
1788
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: { buildSystem: "gradle" } };
|
|
1789
|
-
}
|
|
1790
|
-
},
|
|
1791
|
-
{
|
|
1792
|
-
file: "build.gradle.kts",
|
|
1793
|
-
language: "kotlin",
|
|
1794
|
-
parse: (content) => {
|
|
1795
|
-
const deps = {};
|
|
1796
|
-
for (const m of content.matchAll(/(?:implementation|api|compileOnly)\s*\(\s*"([^"]+)"\s*\)/g)) {
|
|
1797
|
-
const parts = m[1].split(":");
|
|
1798
|
-
if (parts.length >= 2) deps[`${parts[0]}:${parts[1]}`] = parts[2] ?? "*";
|
|
1799
|
-
}
|
|
1800
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: { buildSystem: "gradle-kts" } };
|
|
1801
|
-
}
|
|
1802
|
-
},
|
|
1803
|
-
{
|
|
1804
|
-
file: "pom.xml",
|
|
1805
|
-
language: "java",
|
|
1806
|
-
parse: (content) => {
|
|
1807
|
-
const deps = {};
|
|
1808
|
-
for (const m of content.matchAll(/<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>(?:\s*<version>([^<]+)<\/version>)?/gs)) {
|
|
1809
|
-
deps[`${m[1]}:${m[2]}`] = m[3] ?? "*";
|
|
1810
|
-
}
|
|
1811
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: { buildSystem: "maven" } };
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1814
|
-
// -- Go --
|
|
1815
|
-
{
|
|
1816
|
-
file: "go.mod",
|
|
1817
|
-
language: "go",
|
|
1818
|
-
parse: (content) => {
|
|
1819
|
-
const deps = {};
|
|
1820
|
-
for (const m of content.matchAll(/^\s+(\S+)\s+(v[\d.]+\S*)/gm)) {
|
|
1821
|
-
deps[m[1]] = m[2];
|
|
1822
|
-
}
|
|
1823
|
-
const moduleMatch = content.match(/^module\s+(\S+)/m);
|
|
1824
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: { module: moduleMatch?.[1] ?? "" } };
|
|
1825
|
-
}
|
|
1826
|
-
},
|
|
1827
|
-
// -- Rust --
|
|
1828
|
-
{
|
|
1829
|
-
file: "Cargo.toml",
|
|
1830
|
-
language: "rust",
|
|
1831
|
-
parse: (content) => {
|
|
1832
|
-
const deps = {};
|
|
1833
|
-
const devDeps = {};
|
|
1834
|
-
let inDeps = false;
|
|
1835
|
-
let inDevDeps = false;
|
|
1836
|
-
for (const line of content.split("\n")) {
|
|
1837
|
-
if (line.match(/^\[dependencies\]/)) {
|
|
1838
|
-
inDeps = true;
|
|
1839
|
-
inDevDeps = false;
|
|
1840
|
-
continue;
|
|
1841
|
-
}
|
|
1842
|
-
if (line.match(/^\[dev-dependencies\]/)) {
|
|
1843
|
-
inDevDeps = true;
|
|
1844
|
-
inDeps = false;
|
|
1845
|
-
continue;
|
|
1846
|
-
}
|
|
1847
|
-
if (line.match(/^\[/)) {
|
|
1848
|
-
inDeps = false;
|
|
1849
|
-
inDevDeps = false;
|
|
1850
|
-
continue;
|
|
1851
|
-
}
|
|
1852
|
-
const m = line.match(/^(\w[\w-]*)\s*=\s*"([^"]+)"/);
|
|
1853
|
-
if (m) {
|
|
1854
|
-
if (inDeps) deps[m[1]] = m[2];
|
|
1855
|
-
if (inDevDeps) devDeps[m[1]] = m[2];
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
return { dependencies: deps, devDependencies: devDeps, scripts: {}, metadata: {} };
|
|
1859
|
-
}
|
|
1860
|
-
},
|
|
1861
|
-
// -- Swift --
|
|
1862
|
-
{
|
|
1863
|
-
file: "Package.swift",
|
|
1864
|
-
language: "swift",
|
|
1865
|
-
parse: (content) => {
|
|
1866
|
-
const deps = {};
|
|
1867
|
-
for (const m of content.matchAll(/\.package\s*\(\s*url:\s*"([^"]+)"/g)) {
|
|
1868
|
-
const name = m[1].split("/").pop()?.replace(".git", "") ?? m[1];
|
|
1869
|
-
deps[name] = "*";
|
|
1870
|
-
}
|
|
1871
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: {} };
|
|
1872
|
-
}
|
|
1873
|
-
},
|
|
1874
|
-
// -- Dart / Flutter --
|
|
1875
|
-
{
|
|
1876
|
-
file: "pubspec.yaml",
|
|
1877
|
-
language: "dart",
|
|
1878
|
-
parse: (content) => {
|
|
1879
|
-
const deps = {};
|
|
1880
|
-
let inDeps = false;
|
|
1881
|
-
for (const line of content.split("\n")) {
|
|
1882
|
-
if (line.match(/^dependencies:/)) {
|
|
1883
|
-
inDeps = true;
|
|
1884
|
-
continue;
|
|
1885
|
-
}
|
|
1886
|
-
if (line.match(/^\S/) && inDeps) {
|
|
1887
|
-
inDeps = false;
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1890
|
-
if (inDeps) {
|
|
1891
|
-
const m = line.match(/^\s+(\w[\w_-]*):\s*(.+)?/);
|
|
1892
|
-
if (m) deps[m[1]] = m[2]?.trim() ?? "*";
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: {} };
|
|
1896
|
-
}
|
|
1897
|
-
},
|
|
1898
|
-
// -- Ruby --
|
|
1899
|
-
{
|
|
1900
|
-
file: "Gemfile",
|
|
1901
|
-
language: "ruby",
|
|
1902
|
-
parse: (content) => {
|
|
1903
|
-
const deps = {};
|
|
1904
|
-
for (const m of content.matchAll(/gem\s+['"]([^'"]+)['"]/g)) {
|
|
1905
|
-
deps[m[1]] = "*";
|
|
1906
|
-
}
|
|
1907
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: {} };
|
|
1908
|
-
}
|
|
1909
|
-
},
|
|
1910
|
-
// -- C# / .NET --
|
|
1911
|
-
{
|
|
1912
|
-
file: "*.csproj",
|
|
1913
|
-
// handled specially in the scanner
|
|
1914
|
-
language: "csharp",
|
|
1915
|
-
parse: (content) => {
|
|
1916
|
-
const deps = {};
|
|
1917
|
-
for (const m of content.matchAll(/<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g)) {
|
|
1918
|
-
deps[m[1]] = m[2];
|
|
1919
|
-
}
|
|
1920
|
-
return { dependencies: deps, devDependencies: {}, scripts: {}, metadata: { buildSystem: "dotnet" } };
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
];
|
|
1924
|
-
function detectLanguage(projectDir, manifests) {
|
|
1925
|
-
if (manifests.length === 0) return "unknown";
|
|
1926
|
-
if (existsSync(join(projectDir, "tsconfig.json"))) return "typescript";
|
|
1927
|
-
const languages = manifests.map((m) => m.language);
|
|
1928
|
-
if (languages.includes("typescript")) return "typescript";
|
|
1929
|
-
if (languages.includes("kotlin")) return "kotlin";
|
|
1930
|
-
return languages[0];
|
|
1931
|
-
}
|
|
1932
|
-
function scanManifests(projectDir) {
|
|
1933
|
-
const results = [];
|
|
1934
|
-
for (const spec of MANIFEST_FILES) {
|
|
1935
|
-
if (spec.file.includes("*")) {
|
|
1936
|
-
const ext = spec.file.replace("*", "");
|
|
1937
|
-
try {
|
|
1938
|
-
const entries = readdirSync(projectDir);
|
|
1939
|
-
for (const entry of entries) {
|
|
1940
|
-
if (entry.endsWith(ext)) {
|
|
1941
|
-
const content = readFileSync(join(projectDir, entry), "utf-8");
|
|
1942
|
-
try {
|
|
1943
|
-
const parsed = spec.parse(content, projectDir);
|
|
1944
|
-
results.push({ file: entry, language: spec.language, ...parsed });
|
|
1945
|
-
} catch {
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
} catch {
|
|
1950
|
-
}
|
|
1951
|
-
continue;
|
|
1952
|
-
}
|
|
1953
|
-
const filePath = join(projectDir, spec.file);
|
|
1954
|
-
if (!existsSync(filePath)) continue;
|
|
1955
|
-
try {
|
|
1956
|
-
const content = readFileSync(filePath, "utf-8");
|
|
1957
|
-
const parsed = spec.parse(content, projectDir);
|
|
1958
|
-
results.push({ file: spec.file, language: spec.language, ...parsed });
|
|
1959
|
-
} catch {
|
|
1960
|
-
logger.debug(`Failed to parse manifest: ${spec.file}`);
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
return results;
|
|
1964
|
-
}
|
|
1965
|
-
function readJsonSafe(filePath) {
|
|
1966
|
-
try {
|
|
1967
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
1968
|
-
return JSON.parse(raw);
|
|
1969
|
-
} catch {
|
|
1970
|
-
return null;
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
function extractExports(filePath) {
|
|
1974
|
-
try {
|
|
1975
|
-
const content = readFileSync(filePath, "utf-8");
|
|
1976
|
-
const exports = [];
|
|
1977
|
-
for (const m of content.matchAll(/export\s+(?:async\s+)?function\s+(\w+)/g)) {
|
|
1978
|
-
exports.push(m[1]);
|
|
1979
|
-
}
|
|
1980
|
-
for (const m of content.matchAll(/export\s+class\s+(\w+)/g)) {
|
|
1981
|
-
exports.push(m[1]);
|
|
1982
|
-
}
|
|
1983
|
-
for (const m of content.matchAll(/export\s+(?:const|let|var)\s+(\w+)/g)) {
|
|
1984
|
-
exports.push(m[1]);
|
|
1985
|
-
}
|
|
1986
|
-
for (const m of content.matchAll(/export\s+(?:interface|type)\s+(\w+)/g)) {
|
|
1987
|
-
exports.push(m[1]);
|
|
1988
|
-
}
|
|
1989
|
-
for (const m of content.matchAll(/export\s+enum\s+(\w+)/g)) {
|
|
1990
|
-
exports.push(m[1]);
|
|
1991
|
-
}
|
|
1992
|
-
if (/export\s+default\s/.test(content)) {
|
|
1993
|
-
exports.push("default");
|
|
1994
|
-
}
|
|
1995
|
-
return [...new Set(exports)];
|
|
1996
|
-
} catch {
|
|
1997
|
-
return [];
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
function sampleSourceFiles(dir, max) {
|
|
2001
|
-
const results = [];
|
|
2002
|
-
const extensions = /* @__PURE__ */ new Set([".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"]);
|
|
2003
|
-
function walk(currentDir, depth) {
|
|
2004
|
-
if (depth > 2 || results.length >= max) return;
|
|
2005
|
-
let entries;
|
|
2006
|
-
try {
|
|
2007
|
-
entries = readdirSync(currentDir);
|
|
2008
|
-
} catch {
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
for (const entry of entries) {
|
|
2012
|
-
if (results.length >= max) return;
|
|
2013
|
-
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist") continue;
|
|
2014
|
-
const full = join(currentDir, entry);
|
|
2015
|
-
try {
|
|
2016
|
-
const stat = statSync(full);
|
|
2017
|
-
if (stat.isDirectory()) {
|
|
2018
|
-
walk(full, depth + 1);
|
|
2019
|
-
} else if (stat.isFile() && extensions.has(extname(entry))) {
|
|
2020
|
-
results.push(full);
|
|
2021
|
-
}
|
|
2022
|
-
} catch {
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
walk(dir, 0);
|
|
2027
|
-
return results;
|
|
2028
|
-
}
|
|
2029
|
-
function detectImportStyle(projectDir, packageType) {
|
|
2030
|
-
const sourceDir = existsSync(join(projectDir, "src")) ? join(projectDir, "src") : projectDir;
|
|
2031
|
-
const files = sampleSourceFiles(sourceDir, 10);
|
|
2032
|
-
let esmCount = 0;
|
|
2033
|
-
let cjsCount = 0;
|
|
2034
|
-
for (const file of files) {
|
|
2035
|
-
try {
|
|
2036
|
-
const content = readFileSync(file, "utf-8");
|
|
2037
|
-
if (/\bimport\s+/.test(content) || /\bexport\s+/.test(content)) {
|
|
2038
|
-
esmCount++;
|
|
2039
|
-
}
|
|
2040
|
-
if (/\brequire\s*\(/.test(content) || /\bmodule\.exports\b/.test(content)) {
|
|
2041
|
-
cjsCount++;
|
|
2042
|
-
}
|
|
2043
|
-
} catch {
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
if (esmCount === 0 && cjsCount === 0) {
|
|
2047
|
-
return packageType === "module" ? "esm" : "commonjs";
|
|
2048
|
-
}
|
|
2049
|
-
if (esmCount > 0 && cjsCount > 0) return "mixed";
|
|
2050
|
-
if (esmCount > 0) return "esm";
|
|
2051
|
-
return "commonjs";
|
|
2052
|
-
}
|
|
2053
|
-
function detectTestFramework(deps) {
|
|
2054
|
-
const candidates = [
|
|
2055
|
-
["vitest", "vitest"],
|
|
2056
|
-
["jest", "jest"],
|
|
2057
|
-
["@jest/core", "jest"],
|
|
2058
|
-
["mocha", "mocha"],
|
|
2059
|
-
["ava", "ava"],
|
|
2060
|
-
["tap", "tap"],
|
|
2061
|
-
["uvu", "uvu"]
|
|
2062
|
-
];
|
|
2063
|
-
for (const [pkg, name] of candidates) {
|
|
2064
|
-
if (pkg in deps) return name;
|
|
2065
|
-
}
|
|
2066
|
-
return null;
|
|
2067
|
-
}
|
|
2068
|
-
function detectLinter(deps, configFiles) {
|
|
2069
|
-
if ("biome" in deps || "@biomejs/biome" in deps || configFiles.some((f) => f.startsWith("biome."))) {
|
|
2070
|
-
return "biome";
|
|
2071
|
-
}
|
|
2072
|
-
if ("eslint" in deps || configFiles.some((f) => f.includes("eslint"))) {
|
|
2073
|
-
return "eslint";
|
|
2074
|
-
}
|
|
2075
|
-
return null;
|
|
2076
|
-
}
|
|
2077
|
-
function detectFormatter(deps, configFiles) {
|
|
2078
|
-
if ("prettier" in deps || configFiles.some((f) => f.includes("prettier"))) {
|
|
2079
|
-
return "prettier";
|
|
2080
|
-
}
|
|
2081
|
-
if ("biome" in deps || "@biomejs/biome" in deps || configFiles.some((f) => f.startsWith("biome."))) {
|
|
2082
|
-
return "biome";
|
|
2083
|
-
}
|
|
2084
|
-
return null;
|
|
2085
|
-
}
|
|
2086
|
-
function detectBuildTool(deps, scripts) {
|
|
2087
|
-
const candidates = [
|
|
2088
|
-
["tsup", "tsup"],
|
|
2089
|
-
["esbuild", "esbuild"],
|
|
2090
|
-
["vite", "vite"],
|
|
2091
|
-
["webpack", "webpack"],
|
|
2092
|
-
["rollup", "rollup"],
|
|
2093
|
-
["@swc/core", "swc"],
|
|
2094
|
-
["turbopack", "turbopack"],
|
|
2095
|
-
["parcel", "parcel"]
|
|
2096
|
-
];
|
|
2097
|
-
for (const [pkg, name] of candidates) {
|
|
2098
|
-
if (pkg in deps) return name;
|
|
2099
|
-
}
|
|
2100
|
-
const buildScript = scripts["build"] ?? "";
|
|
2101
|
-
for (const [, name] of candidates) {
|
|
2102
|
-
if (buildScript.includes(name)) return name;
|
|
2103
|
-
}
|
|
2104
|
-
if (buildScript.includes("tsc")) return "tsc";
|
|
2105
|
-
return null;
|
|
2106
|
-
}
|
|
2107
|
-
function detectEntryPoints(projectDir, packageJson) {
|
|
2108
|
-
const entries = [];
|
|
2109
|
-
if (packageJson) {
|
|
2110
|
-
const raw = readJsonSafe(join(projectDir, "package.json"));
|
|
2111
|
-
if (raw) {
|
|
2112
|
-
if (typeof raw["main"] === "string") entries.push(raw["main"]);
|
|
2113
|
-
if (typeof raw["module"] === "string") entries.push(raw["module"]);
|
|
2114
|
-
if (raw["exports"] && typeof raw["exports"] === "object") {
|
|
2115
|
-
const exp = raw["exports"];
|
|
2116
|
-
const dot = exp["."];
|
|
2117
|
-
if (typeof dot === "string") {
|
|
2118
|
-
entries.push(dot);
|
|
2119
|
-
} else if (dot && typeof dot === "object") {
|
|
2120
|
-
const dotObj = dot;
|
|
2121
|
-
if (typeof dotObj["import"] === "string") entries.push(dotObj["import"]);
|
|
2122
|
-
if (typeof dotObj["require"] === "string") entries.push(dotObj["require"]);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
const commonEntries = ["src/index.ts", "src/main.ts", "src/index.js", "src/main.js", "index.ts", "index.js"];
|
|
2128
|
-
for (const candidate of commonEntries) {
|
|
2129
|
-
if (existsSync(join(projectDir, candidate)) && !entries.includes(candidate)) {
|
|
2130
|
-
entries.push(candidate);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
return [...new Set(entries)];
|
|
2134
|
-
}
|
|
2135
|
-
function collectUtilFiles(projectDir) {
|
|
2136
|
-
const results = [];
|
|
2137
|
-
const srcDir = join(projectDir, "src");
|
|
2138
|
-
const extensions = /* @__PURE__ */ new Set([".ts", ".js", ".mts", ".mjs"]);
|
|
2139
|
-
for (const dirName of UTIL_DIR_NAMES) {
|
|
2140
|
-
const utilDir = join(srcDir, dirName);
|
|
2141
|
-
if (!existsSync(utilDir)) continue;
|
|
2142
|
-
let entries;
|
|
2143
|
-
try {
|
|
2144
|
-
entries = readdirSync(utilDir);
|
|
2145
|
-
} catch {
|
|
2146
|
-
continue;
|
|
2147
|
-
}
|
|
2148
|
-
for (const entry of entries) {
|
|
2149
|
-
const ext = extname(entry);
|
|
2150
|
-
if (!extensions.has(ext)) continue;
|
|
2151
|
-
if (basename(entry, ext) === "index") continue;
|
|
2152
|
-
const filePath = join(utilDir, entry);
|
|
2153
|
-
try {
|
|
2154
|
-
if (!statSync(filePath).isFile()) continue;
|
|
2155
|
-
} catch {
|
|
2156
|
-
continue;
|
|
2157
|
-
}
|
|
2158
|
-
const exports = extractExports(filePath);
|
|
2159
|
-
if (exports.length > 0) {
|
|
2160
|
-
const relPath = filePath.slice(projectDir.length + 1);
|
|
2161
|
-
results.push({ file: relPath, exports });
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
return results;
|
|
2166
|
-
}
|
|
2167
|
-
async function analyzeProject(projectDir) {
|
|
2168
|
-
logger.debug(`Analyzing project: ${projectDir}`);
|
|
2169
|
-
let packageJson = null;
|
|
2170
|
-
const pkgPath = join(projectDir, "package.json");
|
|
2171
|
-
if (existsSync(pkgPath)) {
|
|
2172
|
-
const raw = readJsonSafe(pkgPath);
|
|
2173
|
-
if (raw) {
|
|
2174
|
-
packageJson = {
|
|
2175
|
-
name: raw["name"] ?? "",
|
|
2176
|
-
dependencies: raw["dependencies"] ?? {},
|
|
2177
|
-
devDependencies: raw["devDependencies"] ?? {},
|
|
2178
|
-
scripts: raw["scripts"] ?? {},
|
|
2179
|
-
type: raw["type"]
|
|
2180
|
-
};
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
const configFiles = [];
|
|
2184
|
-
for (const pattern of CONFIG_FILE_PATTERNS) {
|
|
2185
|
-
if (existsSync(join(projectDir, pattern))) {
|
|
2186
|
-
configFiles.push(pattern);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
const hasTypescript = existsSync(join(projectDir, "tsconfig.json")) || configFiles.some((f) => f.startsWith("tsconfig"));
|
|
2190
|
-
const hasSrcDir = existsSync(join(projectDir, "src"));
|
|
2191
|
-
const hasTestDir = existsSync(join(projectDir, "test")) || existsSync(join(projectDir, "tests")) || existsSync(join(projectDir, "__tests__"));
|
|
2192
|
-
const entryPoints = detectEntryPoints(projectDir, packageJson);
|
|
2193
|
-
const structure = {
|
|
2194
|
-
hasTypescript,
|
|
2195
|
-
hasSrcDir,
|
|
2196
|
-
hasTestDir,
|
|
2197
|
-
configFiles,
|
|
2198
|
-
entryPoints
|
|
2199
|
-
};
|
|
2200
|
-
const allDeps = {
|
|
2201
|
-
...packageJson?.dependencies ?? {},
|
|
2202
|
-
...packageJson?.devDependencies ?? {}
|
|
2203
|
-
};
|
|
2204
|
-
const patterns = {
|
|
2205
|
-
importStyle: detectImportStyle(projectDir, packageJson?.type),
|
|
2206
|
-
testFramework: detectTestFramework(allDeps),
|
|
2207
|
-
linter: detectLinter(allDeps, configFiles),
|
|
2208
|
-
formatter: detectFormatter(allDeps, configFiles),
|
|
2209
|
-
buildTool: detectBuildTool(allDeps, packageJson?.scripts ?? {})
|
|
2210
|
-
};
|
|
2211
|
-
const existingUtils = collectUtilFiles(projectDir);
|
|
2212
|
-
const manifests = scanManifests(projectDir);
|
|
2213
|
-
const language = detectLanguage(projectDir, manifests);
|
|
2214
|
-
logger.debug(`Project analysis complete: ${packageJson?.name ?? "(unnamed)"}, language: ${language}, manifests: ${manifests.length}`, {
|
|
2215
|
-
department: "research"
|
|
2216
|
-
});
|
|
2217
|
-
return {
|
|
2218
|
-
language,
|
|
2219
|
-
manifests,
|
|
2220
|
-
packageJson,
|
|
2221
|
-
structure,
|
|
2222
|
-
patterns,
|
|
2223
|
-
existingUtils
|
|
2224
|
-
};
|
|
2225
|
-
}
|
|
2226
|
-
function formatProjectContext(analysis) {
|
|
2227
|
-
const sections = [];
|
|
2228
|
-
sections.push("## Project Context");
|
|
2229
|
-
sections.push("");
|
|
2230
|
-
sections.push(`**Primary Language:** ${analysis.language}`);
|
|
2231
|
-
sections.push("");
|
|
2232
|
-
const nonJsManifests = analysis.manifests.filter((m) => m.file !== "package.json");
|
|
2233
|
-
if (nonJsManifests.length > 0) {
|
|
2234
|
-
sections.push("### Dependency Manifests");
|
|
2235
|
-
for (const manifest of nonJsManifests) {
|
|
2236
|
-
sections.push(`#### ${manifest.file} (${manifest.language})`);
|
|
2237
|
-
const depNames = Object.keys(manifest.dependencies);
|
|
2238
|
-
if (depNames.length > 0) {
|
|
2239
|
-
sections.push(depNames.slice(0, 30).map((d) => `- \`${d}\`: ${manifest.dependencies[d]}`).join("\n"));
|
|
2240
|
-
if (depNames.length > 30) sections.push(`- ... and ${depNames.length - 30} more`);
|
|
2241
|
-
}
|
|
2242
|
-
if (Object.keys(manifest.metadata).length > 0) {
|
|
2243
|
-
sections.push(`- Metadata: ${JSON.stringify(manifest.metadata)}`);
|
|
2244
|
-
}
|
|
2245
|
-
sections.push("");
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
if (analysis.packageJson) {
|
|
2249
|
-
const pkg = analysis.packageJson;
|
|
2250
|
-
sections.push(`**Project:** ${pkg.name || "(unnamed)"}`);
|
|
2251
|
-
sections.push(`**Module system:** ${pkg.type ?? "commonjs (default)"}`);
|
|
2252
|
-
sections.push("");
|
|
2253
|
-
const depNames = Object.keys(pkg.dependencies);
|
|
2254
|
-
if (depNames.length > 0) {
|
|
2255
|
-
sections.push("### Dependencies");
|
|
2256
|
-
sections.push(depNames.map((d) => `- \`${d}\`: ${pkg.dependencies[d]}`).join("\n"));
|
|
2257
|
-
sections.push("");
|
|
2258
|
-
}
|
|
2259
|
-
const devDepNames = Object.keys(pkg.devDependencies);
|
|
2260
|
-
if (devDepNames.length > 0) {
|
|
2261
|
-
sections.push("### Dev Dependencies");
|
|
2262
|
-
sections.push(devDepNames.map((d) => `- \`${d}\`: ${pkg.devDependencies[d]}`).join("\n"));
|
|
2263
|
-
sections.push("");
|
|
2264
|
-
}
|
|
2265
|
-
const scriptEntries = Object.entries(pkg.scripts);
|
|
2266
|
-
if (scriptEntries.length > 0) {
|
|
2267
|
-
sections.push("### Scripts");
|
|
2268
|
-
sections.push(scriptEntries.map(([k, v]) => `- \`${k}\`: \`${v}\``).join("\n"));
|
|
2269
|
-
sections.push("");
|
|
2270
|
-
}
|
|
2271
|
-
} else {
|
|
2272
|
-
sections.push("*No package.json found.*");
|
|
2273
|
-
sections.push("");
|
|
2274
|
-
}
|
|
2275
|
-
sections.push("### Project Structure");
|
|
2276
|
-
sections.push(`- TypeScript: ${analysis.structure.hasTypescript ? "yes" : "no"}`);
|
|
2277
|
-
sections.push(`- src/ directory: ${analysis.structure.hasSrcDir ? "yes" : "no"}`);
|
|
2278
|
-
sections.push(`- Test directory: ${analysis.structure.hasTestDir ? "yes" : "no"}`);
|
|
2279
|
-
if (analysis.structure.entryPoints.length > 0) {
|
|
2280
|
-
sections.push(`- Entry points: ${analysis.structure.entryPoints.map((e) => `\`${e}\``).join(", ")}`);
|
|
2281
|
-
}
|
|
2282
|
-
if (analysis.structure.configFiles.length > 0) {
|
|
2283
|
-
sections.push(`- Config files: ${analysis.structure.configFiles.map((f) => `\`${f}\``).join(", ")}`);
|
|
2284
|
-
}
|
|
2285
|
-
sections.push("");
|
|
2286
|
-
sections.push("### Detected Patterns");
|
|
2287
|
-
sections.push(`- Import style: **${analysis.patterns.importStyle}**`);
|
|
2288
|
-
sections.push(`- Test framework: ${analysis.patterns.testFramework ?? "none detected"}`);
|
|
2289
|
-
sections.push(`- Linter: ${analysis.patterns.linter ?? "none detected"}`);
|
|
2290
|
-
sections.push(`- Formatter: ${analysis.patterns.formatter ?? "none detected"}`);
|
|
2291
|
-
sections.push(`- Build tool: ${analysis.patterns.buildTool ?? "none detected"}`);
|
|
2292
|
-
sections.push("");
|
|
2293
|
-
if (analysis.existingUtils.length > 0) {
|
|
2294
|
-
sections.push("### Existing Utilities (reuse before creating new ones)");
|
|
2295
|
-
for (const util of analysis.existingUtils) {
|
|
2296
|
-
sections.push(`- **\`${util.file}\`**: ${util.exports.map((e) => `\`${e}\``).join(", ")}`);
|
|
2297
|
-
}
|
|
2298
|
-
sections.push("");
|
|
2299
|
-
}
|
|
2300
|
-
sections.push("### Key Guidance");
|
|
2301
|
-
if (analysis.patterns.importStyle === "esm") {
|
|
2302
|
-
sections.push("- Use ESM imports (`import`/`export`). Use `.js` extensions in relative imports if TypeScript with bundler resolution.");
|
|
2303
|
-
} else if (analysis.patterns.importStyle === "commonjs") {
|
|
2304
|
-
sections.push("- Use CommonJS (`require`/`module.exports`).");
|
|
2305
|
-
} else {
|
|
2306
|
-
sections.push("- Mixed import styles detected. Prefer the dominant style in the module you are editing.");
|
|
2307
|
-
}
|
|
2308
|
-
if (analysis.packageJson) {
|
|
2309
|
-
sections.push("- Do NOT run `npm install` for packages already listed in dependencies or devDependencies.");
|
|
2310
|
-
}
|
|
2311
|
-
if (analysis.patterns.testFramework) {
|
|
2312
|
-
sections.push(`- Write tests using **${analysis.patterns.testFramework}** (already installed).`);
|
|
2313
|
-
}
|
|
2314
|
-
if (analysis.patterns.buildTool) {
|
|
2315
|
-
sections.push(`- Build with **${analysis.patterns.buildTool}** (already configured).`);
|
|
2316
|
-
}
|
|
2317
|
-
return sections.join("\n");
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
777
|
// src/orchestrator/orchestrator.ts
|
|
2321
778
|
var DEPARTMENT_KEYWORDS = {
|
|
2322
779
|
architecture: [
|
|
@@ -2458,69 +915,46 @@ var Orchestrator = class {
|
|
|
2458
915
|
}
|
|
2459
916
|
// ---- start meeting ------------------------------------------------------
|
|
2460
917
|
/**
|
|
2461
|
-
*
|
|
2462
|
-
*
|
|
2463
|
-
* 1. Selects departments (if not provided).
|
|
2464
|
-
* 2. Creates the meeting record in SQLite.
|
|
2465
|
-
* 3. Spawns leaders via the LeaderPool.
|
|
2466
|
-
* 4. Runs the MeetingRunner lifecycle (opening -> discussion -> synthesis -> minutes).
|
|
2467
|
-
* 5. Deactivates leaders after completion.
|
|
918
|
+
* Create a new meeting record and return meeting info + recommended leaders.
|
|
2468
919
|
*
|
|
2469
|
-
*
|
|
920
|
+
* Does NOT run the meeting. The calling agent (via skill markdown) is
|
|
921
|
+
* responsible for orchestrating the meeting phases using add-transcript
|
|
922
|
+
* and generate-minutes MCP tools.
|
|
2470
923
|
*/
|
|
2471
|
-
|
|
924
|
+
startMeeting(opts) {
|
|
2472
925
|
const { topic, agenda } = opts;
|
|
2473
926
|
const departments = opts.departments ?? this.selectLeaders(topic, agenda);
|
|
2474
|
-
logger.info(`
|
|
2475
|
-
let projectContext;
|
|
2476
|
-
try {
|
|
2477
|
-
const analysis = await analyzeProject(process.cwd());
|
|
2478
|
-
projectContext = formatProjectContext(analysis);
|
|
2479
|
-
logger.debug("Project analysis complete", { meetingId: topic });
|
|
2480
|
-
} catch (err) {
|
|
2481
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2482
|
-
logger.warn(`Project analysis failed (proceeding without context): ${msg}`);
|
|
2483
|
-
}
|
|
927
|
+
logger.info(`Creating meeting: "${topic}" with departments: [${departments.join(", ")}]`);
|
|
2484
928
|
const meetingId = uuidv46();
|
|
2485
|
-
|
|
929
|
+
createMeeting({
|
|
2486
930
|
id: meetingId,
|
|
2487
931
|
topic,
|
|
2488
932
|
agenda,
|
|
2489
|
-
participantIds:
|
|
2490
|
-
// Will be filled in after leader spawning
|
|
933
|
+
participantIds: departments.map((d) => `${d}-leader`),
|
|
2491
934
|
initiatedBy: this.orchestratorId,
|
|
2492
|
-
status: "
|
|
2493
|
-
phase: "
|
|
935
|
+
status: "pending",
|
|
936
|
+
phase: "orchestrator-phase",
|
|
2494
937
|
startedAt: Date.now(),
|
|
2495
938
|
previousMeetingId: opts.previousMeetingId ?? null
|
|
2496
939
|
});
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
participantIds: leaders.map((l) => l.id)
|
|
2504
|
-
});
|
|
2505
|
-
const runner = new MeetingRunner(meetingId, leaders, projectContext);
|
|
2506
|
-
try {
|
|
2507
|
-
await runner.run();
|
|
2508
|
-
} finally {
|
|
2509
|
-
for (const leader of leaders) {
|
|
2510
|
-
this.leaderPool.deactivateLeader(leader.id);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
return meetingId;
|
|
940
|
+
return {
|
|
941
|
+
meetingId,
|
|
942
|
+
departments,
|
|
943
|
+
agenda,
|
|
944
|
+
topic
|
|
945
|
+
};
|
|
2514
946
|
}
|
|
2515
947
|
// ---- chain meeting -------------------------------------------------------
|
|
2516
948
|
/**
|
|
2517
|
-
*
|
|
949
|
+
* Create a new meeting chained from a previous meeting.
|
|
2518
950
|
*
|
|
2519
951
|
* Loads minutes from the previous meeting and includes them as context for
|
|
2520
952
|
* the new meeting topic. The new meeting's `previousMeetingId` is set for
|
|
2521
953
|
* traceability.
|
|
954
|
+
*
|
|
955
|
+
* Does NOT run the meeting (same as startMeeting).
|
|
2522
956
|
*/
|
|
2523
|
-
|
|
957
|
+
chainMeeting(opts) {
|
|
2524
958
|
const previousMeeting = getMeeting(opts.previousMeetingId);
|
|
2525
959
|
if (!previousMeeting) {
|
|
2526
960
|
throw new Error(`Previous meeting not found: ${opts.previousMeetingId}`);
|
|
@@ -2590,18 +1024,11 @@ async function startMeetingHandler({
|
|
|
2590
1024
|
}) {
|
|
2591
1025
|
try {
|
|
2592
1026
|
const orchestrator = new Orchestrator();
|
|
2593
|
-
const
|
|
1027
|
+
const result = orchestrator.startMeeting({
|
|
2594
1028
|
topic,
|
|
2595
1029
|
agenda,
|
|
2596
1030
|
departments
|
|
2597
1031
|
});
|
|
2598
|
-
const result = {
|
|
2599
|
-
meetingId,
|
|
2600
|
-
status: "started",
|
|
2601
|
-
topic,
|
|
2602
|
-
agenda,
|
|
2603
|
-
departments: departments ?? orchestrator.selectLeaders(topic, agenda)
|
|
2604
|
-
};
|
|
2605
1032
|
return {
|
|
2606
1033
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2607
1034
|
};
|
|
@@ -3004,219 +1431,6 @@ async function compactMinutesHandler({
|
|
|
3004
1431
|
|
|
3005
1432
|
// src/tools/execute-tasks.ts
|
|
3006
1433
|
import { z as z5 } from "zod";
|
|
3007
|
-
|
|
3008
|
-
// src/orchestrator/worker-manager.ts
|
|
3009
|
-
async function executeWorkerAgent(worker) {
|
|
3010
|
-
const department = inferDepartment(worker);
|
|
3011
|
-
const workerRole = inferWorkerRole(worker);
|
|
3012
|
-
const agentConfig = createAgentConfig({
|
|
3013
|
-
tier: "worker",
|
|
3014
|
-
role: workerRole,
|
|
3015
|
-
department,
|
|
3016
|
-
task: worker.taskDescription,
|
|
3017
|
-
context: worker.inputContext ?? void 0
|
|
3018
|
-
});
|
|
3019
|
-
const systemPrompt = buildWorkerPrompt({
|
|
3020
|
-
workerType: workerRole,
|
|
3021
|
-
department,
|
|
3022
|
-
task: worker.taskDescription,
|
|
3023
|
-
context: worker.inputContext ?? void 0
|
|
3024
|
-
});
|
|
3025
|
-
const prompt = `Execute the following task:
|
|
3026
|
-
|
|
3027
|
-
Task: ${worker.taskDescription}
|
|
3028
|
-
` + (worker.inputContext ? `
|
|
3029
|
-
Context: ${worker.inputContext}
|
|
3030
|
-
` : "") + `
|
|
3031
|
-
Provide a clear, structured result.`;
|
|
3032
|
-
const invokeOpts = buildInvokeOptions(agentConfig, prompt, systemPrompt);
|
|
3033
|
-
invokeOpts.timeoutMs = 3e5;
|
|
3034
|
-
const result = await invokeClaude(invokeOpts);
|
|
3035
|
-
if (!result.success) {
|
|
3036
|
-
throw new Error(result.error ?? "Worker agent invocation failed");
|
|
3037
|
-
}
|
|
3038
|
-
return result.output;
|
|
3039
|
-
}
|
|
3040
|
-
function inferDepartment(worker) {
|
|
3041
|
-
switch (worker.taskType) {
|
|
3042
|
-
case "research":
|
|
3043
|
-
return "research";
|
|
3044
|
-
case "testing":
|
|
3045
|
-
return "qa";
|
|
3046
|
-
case "analysis":
|
|
3047
|
-
return "architecture";
|
|
3048
|
-
case "implementation":
|
|
3049
|
-
default:
|
|
3050
|
-
return "engineering";
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
function inferWorkerRole(worker) {
|
|
3054
|
-
const desc = worker.taskDescription.toLowerCase();
|
|
3055
|
-
if (desc.includes("schema") || desc.includes("data model")) return "schema-designer";
|
|
3056
|
-
if (desc.includes("api") || desc.includes("endpoint")) return "api-designer";
|
|
3057
|
-
if (desc.includes("dependency") || desc.includes("coupling")) return "dependency-analyzer";
|
|
3058
|
-
if (desc.includes("test")) return "test-writer";
|
|
3059
|
-
if (desc.includes("security") || desc.includes("audit")) return "security-auditor";
|
|
3060
|
-
if (desc.includes("performance") || desc.includes("benchmark")) return "perf-tester";
|
|
3061
|
-
if (desc.includes("research") || desc.includes("explore") || desc.includes("investigate")) return "code-explorer";
|
|
3062
|
-
if (desc.includes("document") || desc.includes("search")) return "doc-searcher";
|
|
3063
|
-
if (desc.includes("fix") || desc.includes("bug")) return "bug-fixer";
|
|
3064
|
-
if (desc.includes("refactor")) return "refactorer";
|
|
3065
|
-
return "feature-dev";
|
|
3066
|
-
}
|
|
3067
|
-
function inferTaskType(description) {
|
|
3068
|
-
const lower = description.toLowerCase();
|
|
3069
|
-
if (lower.includes("research") || lower.includes("explore") || lower.includes("search")) {
|
|
3070
|
-
return "research";
|
|
3071
|
-
}
|
|
3072
|
-
if (lower.includes("test") || lower.includes("audit") || lower.includes("benchmark")) {
|
|
3073
|
-
return "testing";
|
|
3074
|
-
}
|
|
3075
|
-
if (lower.includes("analys") || lower.includes("design") || lower.includes("schema")) {
|
|
3076
|
-
return "analysis";
|
|
3077
|
-
}
|
|
3078
|
-
return "implementation";
|
|
3079
|
-
}
|
|
3080
|
-
var WorkerManager = class {
|
|
3081
|
-
// ---- spawn workers ------------------------------------------------------
|
|
3082
|
-
/**
|
|
3083
|
-
* Spawn a batch of workers on behalf of a leader.
|
|
3084
|
-
*
|
|
3085
|
-
* Each task assignment is persisted to SQLite and an `agent_spawned` event is
|
|
3086
|
-
* emitted. The returned records are in `pending` status.
|
|
3087
|
-
*/
|
|
3088
|
-
async spawnWorkers(leaderId, meetingId, tasks) {
|
|
3089
|
-
const workers = [];
|
|
3090
|
-
for (const task of tasks) {
|
|
3091
|
-
const worker = createWorker({
|
|
3092
|
-
leaderId,
|
|
3093
|
-
meetingId,
|
|
3094
|
-
taskDescription: task.description,
|
|
3095
|
-
taskType: inferTaskType(task.description),
|
|
3096
|
-
inputContext: task.inputPaths.length > 0 ? task.inputPaths.join(", ") : null,
|
|
3097
|
-
outputResult: null,
|
|
3098
|
-
errorMessage: null,
|
|
3099
|
-
dependencies: task.dependencies
|
|
3100
|
-
});
|
|
3101
|
-
workers.push(worker);
|
|
3102
|
-
logger.info(`Spawned worker for leader ${leaderId}`, {
|
|
3103
|
-
agentId: worker.id,
|
|
3104
|
-
meetingId,
|
|
3105
|
-
department: "worker"
|
|
3106
|
-
});
|
|
3107
|
-
eventBus.emitAgentEvent({
|
|
3108
|
-
kind: "agent_spawned",
|
|
3109
|
-
agentId: worker.id,
|
|
3110
|
-
agentType: "worker",
|
|
3111
|
-
parentId: leaderId,
|
|
3112
|
-
label: task.description.slice(0, 60),
|
|
3113
|
-
department: "engineering"
|
|
3114
|
-
// Workers inherit; refined later if needed
|
|
3115
|
-
});
|
|
3116
|
-
}
|
|
3117
|
-
return workers;
|
|
3118
|
-
}
|
|
3119
|
-
// ---- execute workers ----------------------------------------------------
|
|
3120
|
-
/**
|
|
3121
|
-
* Execute a list of workers, respecting dependency ordering.
|
|
3122
|
-
*
|
|
3123
|
-
* - Workers with no unresolved dependencies run in parallel.
|
|
3124
|
-
* - Workers whose dependencies are all completed run next.
|
|
3125
|
-
* - Continues until all workers are done or a dependency cycle is detected.
|
|
3126
|
-
*/
|
|
3127
|
-
async executeWorkers(workers) {
|
|
3128
|
-
const completed = /* @__PURE__ */ new Set();
|
|
3129
|
-
const results = [];
|
|
3130
|
-
const pending = new Map(workers.map((w) => [w.id, w]));
|
|
3131
|
-
while (pending.size > 0) {
|
|
3132
|
-
const ready = [];
|
|
3133
|
-
for (const worker of pending.values()) {
|
|
3134
|
-
const depsReady = worker.dependencies.every((dep) => completed.has(dep));
|
|
3135
|
-
if (depsReady) {
|
|
3136
|
-
ready.push(worker);
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
if (ready.length === 0) {
|
|
3140
|
-
logger.warn("Dependency cycle or unsatisfiable deps detected; failing remaining workers");
|
|
3141
|
-
for (const worker of pending.values()) {
|
|
3142
|
-
const failed = updateWorker(worker.id, {
|
|
3143
|
-
status: "failed",
|
|
3144
|
-
errorMessage: "Unresolvable dependency",
|
|
3145
|
-
completedAt: Date.now()
|
|
3146
|
-
});
|
|
3147
|
-
results.push(failed ?? worker);
|
|
3148
|
-
eventBus.emitAgentEvent({
|
|
3149
|
-
kind: "task_completed",
|
|
3150
|
-
agentId: worker.id,
|
|
3151
|
-
result: "failure"
|
|
3152
|
-
});
|
|
3153
|
-
}
|
|
3154
|
-
break;
|
|
3155
|
-
}
|
|
3156
|
-
const batchResults = await Promise.allSettled(
|
|
3157
|
-
ready.map(async (worker) => {
|
|
3158
|
-
updateWorker(worker.id, { status: "running" });
|
|
3159
|
-
eventBus.emitAgentEvent({
|
|
3160
|
-
kind: "state_changed",
|
|
3161
|
-
agentId: worker.id,
|
|
3162
|
-
from: "idle",
|
|
3163
|
-
to: "working"
|
|
3164
|
-
});
|
|
3165
|
-
try {
|
|
3166
|
-
const output = await executeWorkerAgent(worker);
|
|
3167
|
-
const updated = updateWorker(worker.id, {
|
|
3168
|
-
status: "completed",
|
|
3169
|
-
outputResult: output,
|
|
3170
|
-
completedAt: Date.now(),
|
|
3171
|
-
costUsd: 0.01
|
|
3172
|
-
// Approximate cost; real cost tracked by CLI
|
|
3173
|
-
});
|
|
3174
|
-
eventBus.emitAgentEvent({
|
|
3175
|
-
kind: "task_completed",
|
|
3176
|
-
agentId: worker.id,
|
|
3177
|
-
result: "success"
|
|
3178
|
-
});
|
|
3179
|
-
return updated ?? worker;
|
|
3180
|
-
} catch (err) {
|
|
3181
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3182
|
-
const updated = updateWorker(worker.id, {
|
|
3183
|
-
status: "failed",
|
|
3184
|
-
errorMessage: errorMsg,
|
|
3185
|
-
completedAt: Date.now()
|
|
3186
|
-
});
|
|
3187
|
-
eventBus.emitAgentEvent({
|
|
3188
|
-
kind: "task_completed",
|
|
3189
|
-
agentId: worker.id,
|
|
3190
|
-
result: "failure"
|
|
3191
|
-
});
|
|
3192
|
-
return updated ?? worker;
|
|
3193
|
-
}
|
|
3194
|
-
})
|
|
3195
|
-
);
|
|
3196
|
-
for (let i = 0; i < ready.length; i++) {
|
|
3197
|
-
const worker = ready[i];
|
|
3198
|
-
pending.delete(worker.id);
|
|
3199
|
-
completed.add(worker.id);
|
|
3200
|
-
const settlement = batchResults[i];
|
|
3201
|
-
if (settlement.status === "fulfilled") {
|
|
3202
|
-
results.push(settlement.value);
|
|
3203
|
-
} else {
|
|
3204
|
-
results.push(worker);
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
return results;
|
|
3209
|
-
}
|
|
3210
|
-
// ---- status query -------------------------------------------------------
|
|
3211
|
-
/**
|
|
3212
|
-
* Get the current status of all workers under a given leader.
|
|
3213
|
-
*/
|
|
3214
|
-
getWorkerStatus(leaderId) {
|
|
3215
|
-
return listWorkersByLeader(leaderId);
|
|
3216
|
-
}
|
|
3217
|
-
};
|
|
3218
|
-
|
|
3219
|
-
// src/tools/execute-tasks.ts
|
|
3220
1434
|
var executeTasksSchema = {
|
|
3221
1435
|
meetingId: z5.string().describe("Meeting ID"),
|
|
3222
1436
|
taskIds: z5.array(z5.string()).optional().describe("Specific tasks to execute, or all")
|
|
@@ -3268,58 +1482,15 @@ async function executeTasksHandler({
|
|
|
3268
1482
|
}
|
|
3269
1483
|
tasksByDepartment.get(dept).push(task);
|
|
3270
1484
|
}
|
|
3271
|
-
const
|
|
3272
|
-
const executionResults = [];
|
|
1485
|
+
const departments = [];
|
|
3273
1486
|
for (const [department, tasks] of tasksByDepartment) {
|
|
3274
|
-
|
|
3275
|
-
workerId: task.id,
|
|
3276
|
-
description: `${task.title}: ${task.description}`,
|
|
3277
|
-
inputPaths: [],
|
|
3278
|
-
outputPath: "",
|
|
3279
|
-
dependencies: task.dependencies,
|
|
3280
|
-
status: "pending",
|
|
3281
|
-
result: null
|
|
3282
|
-
}));
|
|
3283
|
-
const leaderId = `${department}-leader-${meetingId}`;
|
|
3284
|
-
const workers = await workerManager.spawnWorkers(
|
|
3285
|
-
leaderId,
|
|
3286
|
-
meetingId,
|
|
3287
|
-
assignments
|
|
3288
|
-
);
|
|
3289
|
-
const completedWorkers = await workerManager.executeWorkers(workers);
|
|
3290
|
-
executionResults.push({
|
|
3291
|
-
department,
|
|
3292
|
-
taskCount: tasks.length,
|
|
3293
|
-
workers: completedWorkers.map((w) => ({
|
|
3294
|
-
id: w.id,
|
|
3295
|
-
status: w.status,
|
|
3296
|
-
taskDescription: w.taskDescription,
|
|
3297
|
-
outputResult: w.outputResult,
|
|
3298
|
-
errorMessage: w.errorMessage
|
|
3299
|
-
}))
|
|
3300
|
-
});
|
|
1487
|
+
departments.push({ department, tasks });
|
|
3301
1488
|
}
|
|
3302
|
-
const totalWorkers = executionResults.reduce(
|
|
3303
|
-
(acc, r) => acc + r.workers.length,
|
|
3304
|
-
0
|
|
3305
|
-
);
|
|
3306
|
-
const completedCount = executionResults.reduce(
|
|
3307
|
-
(acc, r) => acc + r.workers.filter((w) => w.status === "completed").length,
|
|
3308
|
-
0
|
|
3309
|
-
);
|
|
3310
|
-
const failedCount = executionResults.reduce(
|
|
3311
|
-
(acc, r) => acc + r.workers.filter((w) => w.status === "failed").length,
|
|
3312
|
-
0
|
|
3313
|
-
);
|
|
3314
1489
|
const result = {
|
|
3315
1490
|
meetingId,
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
completed: completedCount,
|
|
3320
|
-
failed: failedCount
|
|
3321
|
-
},
|
|
3322
|
-
departments: executionResults
|
|
1491
|
+
totalTasks: tasksToExecute.length,
|
|
1492
|
+
departments,
|
|
1493
|
+
instructions: "These tasks should be dispatched to implementer agents. The orchestrator agent will use Claude Code Agent tool to run each task."
|
|
3323
1494
|
};
|
|
3324
1495
|
return {
|
|
3325
1496
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -3765,19 +1936,19 @@ async function getTaskReportHandler({
|
|
|
3765
1936
|
import { z as z11 } from "zod";
|
|
3766
1937
|
|
|
3767
1938
|
// src/extension/capability-registry.ts
|
|
3768
|
-
import { readFileSync
|
|
3769
|
-
import { join as
|
|
1939
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
1940
|
+
import { join as join2 } from "path";
|
|
3770
1941
|
|
|
3771
1942
|
// src/utils/config.ts
|
|
3772
1943
|
import { mkdirSync } from "fs";
|
|
3773
1944
|
import { homedir } from "os";
|
|
3774
|
-
import { join
|
|
1945
|
+
import { join } from "path";
|
|
3775
1946
|
function buildConfig() {
|
|
3776
|
-
const DATA_DIR =
|
|
1947
|
+
const DATA_DIR = join(homedir(), ".open-coleslaw");
|
|
3777
1948
|
return {
|
|
3778
1949
|
DATA_DIR,
|
|
3779
|
-
DB_PATH:
|
|
3780
|
-
MINUTES_DIR:
|
|
1950
|
+
DB_PATH: join(DATA_DIR, "data.db"),
|
|
1951
|
+
MINUTES_DIR: join(DATA_DIR, "minutes"),
|
|
3781
1952
|
DASHBOARD_PORT: 35143
|
|
3782
1953
|
};
|
|
3783
1954
|
}
|
|
@@ -3900,7 +2071,7 @@ var CapabilityRegistry = class {
|
|
|
3900
2071
|
registryPath;
|
|
3901
2072
|
constructor() {
|
|
3902
2073
|
const { DATA_DIR } = getConfig();
|
|
3903
|
-
this.registryPath =
|
|
2074
|
+
this.registryPath = join2(DATA_DIR, "registry.json");
|
|
3904
2075
|
}
|
|
3905
2076
|
/**
|
|
3906
2077
|
* Load all capabilities: built-in (hardcoded) + custom (from registry.json).
|
|
@@ -3975,11 +2146,11 @@ ${lines.join("\n")}`);
|
|
|
3975
2146
|
// Private helpers
|
|
3976
2147
|
// -------------------------------------------------------------------------
|
|
3977
2148
|
readCustomEntries() {
|
|
3978
|
-
if (!
|
|
2149
|
+
if (!existsSync(this.registryPath)) {
|
|
3979
2150
|
return [];
|
|
3980
2151
|
}
|
|
3981
2152
|
try {
|
|
3982
|
-
const raw =
|
|
2153
|
+
const raw = readFileSync(this.registryPath, "utf-8");
|
|
3983
2154
|
const parsed = JSON.parse(raw);
|
|
3984
2155
|
if (!Array.isArray(parsed)) return [];
|
|
3985
2156
|
return parsed;
|
|
@@ -3994,7 +2165,7 @@ ${lines.join("\n")}`);
|
|
|
3994
2165
|
|
|
3995
2166
|
// src/extension/generator.ts
|
|
3996
2167
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3997
|
-
import { join as
|
|
2168
|
+
import { join as join3 } from "path";
|
|
3998
2169
|
function sanitizeName(name) {
|
|
3999
2170
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4000
2171
|
}
|
|
@@ -4011,7 +2182,7 @@ function header(request) {
|
|
|
4011
2182
|
}
|
|
4012
2183
|
function customDir(type) {
|
|
4013
2184
|
const { DATA_DIR } = getConfig();
|
|
4014
|
-
const dir =
|
|
2185
|
+
const dir = join3(DATA_DIR, `custom-${type}s`);
|
|
4015
2186
|
mkdirSync2(dir, { recursive: true });
|
|
4016
2187
|
return dir;
|
|
4017
2188
|
}
|
|
@@ -4232,7 +2403,7 @@ function generateCapability(request) {
|
|
|
4232
2403
|
ext = ".js";
|
|
4233
2404
|
}
|
|
4234
2405
|
const dir = customDir(request.type);
|
|
4235
|
-
const filePath =
|
|
2406
|
+
const filePath = join3(dir, `${safeName}${ext}`);
|
|
4236
2407
|
let code;
|
|
4237
2408
|
switch (request.type) {
|
|
4238
2409
|
case "hook":
|
|
@@ -4267,11 +2438,11 @@ function generateCapability(request) {
|
|
|
4267
2438
|
}
|
|
4268
2439
|
|
|
4269
2440
|
// src/extension/guide-updater.ts
|
|
4270
|
-
import { readFileSync as
|
|
4271
|
-
import { join as
|
|
2441
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2442
|
+
import { join as join4 } from "path";
|
|
4272
2443
|
async function updatePluginGuide(registry) {
|
|
4273
2444
|
const { DATA_DIR } = getConfig();
|
|
4274
|
-
const guidePath =
|
|
2445
|
+
const guidePath = join4(DATA_DIR, "plugin-guide.md");
|
|
4275
2446
|
const capSection = registry.formatForGuide();
|
|
4276
2447
|
const guide = [
|
|
4277
2448
|
"# Open-Coleslaw Plugin Guide",
|
|
@@ -4664,19 +2835,249 @@ async function chainMeetingHandler({
|
|
|
4664
2835
|
}) {
|
|
4665
2836
|
try {
|
|
4666
2837
|
const orchestrator = new Orchestrator();
|
|
4667
|
-
const
|
|
2838
|
+
const result = orchestrator.chainMeeting({
|
|
4668
2839
|
previousMeetingId,
|
|
4669
2840
|
topic,
|
|
4670
2841
|
agenda,
|
|
4671
2842
|
departments
|
|
4672
2843
|
});
|
|
2844
|
+
return {
|
|
2845
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
2846
|
+
};
|
|
2847
|
+
} catch (error) {
|
|
2848
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2849
|
+
return {
|
|
2850
|
+
content: [
|
|
2851
|
+
{ type: "text", text: JSON.stringify({ error: message }, null, 2) }
|
|
2852
|
+
],
|
|
2853
|
+
isError: true
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// src/tools/add-transcript.ts
|
|
2859
|
+
import { z as z14 } from "zod";
|
|
2860
|
+
|
|
2861
|
+
// src/orchestrator/meeting-runner.ts
|
|
2862
|
+
function insertTranscriptEntry(meetingId, speakerId, speakerRole, agendaItemIndex, roundNumber, content) {
|
|
2863
|
+
const db = getDb();
|
|
2864
|
+
const now = Date.now();
|
|
2865
|
+
const tokenCount = Math.ceil(content.length / 4);
|
|
2866
|
+
const result = db.prepare(
|
|
2867
|
+
`INSERT INTO transcript_entries
|
|
2868
|
+
(meeting_id, speaker_id, speaker_role, agenda_item_index, round_number, content, token_count, created_at)
|
|
2869
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2870
|
+
).run(meetingId, speakerId, speakerRole, agendaItemIndex, roundNumber, content, tokenCount, now);
|
|
2871
|
+
return {
|
|
2872
|
+
id: Number(result.lastInsertRowid),
|
|
2873
|
+
meetingId,
|
|
2874
|
+
speakerId,
|
|
2875
|
+
speakerRole,
|
|
2876
|
+
agendaItemIndex,
|
|
2877
|
+
roundNumber,
|
|
2878
|
+
content,
|
|
2879
|
+
tokenCount,
|
|
2880
|
+
createdAt: now
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function getTranscript(meetingId) {
|
|
2884
|
+
const db = getDb();
|
|
2885
|
+
const rows = db.prepare("SELECT * FROM transcript_entries WHERE meeting_id = ? ORDER BY created_at ASC").all(meetingId);
|
|
2886
|
+
return rows.map((r) => ({
|
|
2887
|
+
id: r.id,
|
|
2888
|
+
meetingId: r.meeting_id,
|
|
2889
|
+
speakerId: r.speaker_id,
|
|
2890
|
+
speakerRole: r.speaker_role,
|
|
2891
|
+
agendaItemIndex: r.agenda_item_index,
|
|
2892
|
+
roundNumber: r.round_number,
|
|
2893
|
+
content: r.content,
|
|
2894
|
+
tokenCount: r.token_count,
|
|
2895
|
+
createdAt: r.created_at
|
|
2896
|
+
}));
|
|
2897
|
+
}
|
|
2898
|
+
var MeetingRunner = class {
|
|
2899
|
+
meetingId;
|
|
2900
|
+
constructor(meetingId) {
|
|
2901
|
+
this.meetingId = meetingId;
|
|
2902
|
+
}
|
|
2903
|
+
// ---- Add transcript entry (called by MCP tool add-transcript) -----------
|
|
2904
|
+
/**
|
|
2905
|
+
* Add a transcript entry for this meeting.
|
|
2906
|
+
* Returns the created TranscriptEntry.
|
|
2907
|
+
*/
|
|
2908
|
+
addTranscript(speakerRole, agendaItemIndex, roundNumber, content) {
|
|
2909
|
+
const meeting = getMeeting(this.meetingId);
|
|
2910
|
+
if (!meeting) {
|
|
2911
|
+
throw new Error(`Meeting not found: ${this.meetingId}`);
|
|
2912
|
+
}
|
|
2913
|
+
const entry = insertTranscriptEntry(
|
|
2914
|
+
this.meetingId,
|
|
2915
|
+
speakerRole,
|
|
2916
|
+
speakerRole,
|
|
2917
|
+
agendaItemIndex,
|
|
2918
|
+
roundNumber,
|
|
2919
|
+
content
|
|
2920
|
+
);
|
|
2921
|
+
logger.debug(`Transcript added: ${speakerRole} (item ${agendaItemIndex}, round ${roundNumber})`, {
|
|
2922
|
+
meetingId: this.meetingId
|
|
2923
|
+
});
|
|
2924
|
+
return entry;
|
|
2925
|
+
}
|
|
2926
|
+
// ---- Generate minutes from all transcripts ------------------------------
|
|
2927
|
+
/**
|
|
2928
|
+
* Generate meeting minutes by formatting all stored transcript entries.
|
|
2929
|
+
* Returns the minutesId of the created minutes record.
|
|
2930
|
+
*/
|
|
2931
|
+
async generateMinutes() {
|
|
2932
|
+
const meeting = getMeeting(this.meetingId);
|
|
2933
|
+
if (!meeting) {
|
|
2934
|
+
throw new Error(`Meeting not found: ${this.meetingId}`);
|
|
2935
|
+
}
|
|
2936
|
+
updateMeeting(this.meetingId, {
|
|
2937
|
+
phase: "minutes-generation",
|
|
2938
|
+
status: "minutes-generation"
|
|
2939
|
+
});
|
|
2940
|
+
const transcript = getTranscript(this.meetingId);
|
|
2941
|
+
const sections = [];
|
|
2942
|
+
const speakerRoles = [...new Set(transcript.map((e) => e.speakerRole))];
|
|
2943
|
+
sections.push(`# Meeting Minutes`);
|
|
2944
|
+
sections.push(`## Topic: ${meeting.topic}`);
|
|
2945
|
+
sections.push(`## Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2946
|
+
sections.push(`## Participants: ${speakerRoles.join(", ")}`);
|
|
2947
|
+
sections.push("");
|
|
2948
|
+
sections.push(`## Agenda`);
|
|
2949
|
+
meeting.agenda.forEach((item, i) => {
|
|
2950
|
+
sections.push(`${i + 1}. ${item}`);
|
|
2951
|
+
});
|
|
2952
|
+
sections.push("");
|
|
2953
|
+
const openingEntries = transcript.filter((e) => e.agendaItemIndex === -1);
|
|
2954
|
+
if (openingEntries.length > 0) {
|
|
2955
|
+
sections.push(`## Opening Statements`);
|
|
2956
|
+
for (const entry of openingEntries) {
|
|
2957
|
+
sections.push(`### ${entry.speakerRole}`);
|
|
2958
|
+
sections.push(entry.content);
|
|
2959
|
+
sections.push("");
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
for (let i = 0; i < meeting.agenda.length; i++) {
|
|
2963
|
+
const itemEntries = transcript.filter((e) => e.agendaItemIndex === i);
|
|
2964
|
+
if (itemEntries.length > 0) {
|
|
2965
|
+
sections.push(`## Discussion: ${meeting.agenda[i]}`);
|
|
2966
|
+
for (const entry of itemEntries) {
|
|
2967
|
+
sections.push(`**${entry.speakerRole}** (round ${entry.roundNumber}):`);
|
|
2968
|
+
sections.push(entry.content);
|
|
2969
|
+
sections.push("");
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
const synthesisEntries = transcript.filter((e) => e.agendaItemIndex === -2);
|
|
2974
|
+
if (synthesisEntries.length > 0) {
|
|
2975
|
+
sections.push(`## Final Positions`);
|
|
2976
|
+
for (const entry of synthesisEntries) {
|
|
2977
|
+
sections.push(`### ${entry.speakerRole}`);
|
|
2978
|
+
sections.push(entry.content);
|
|
2979
|
+
sections.push("");
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
const content = sections.join("\n");
|
|
2983
|
+
const actionItems = speakerRoles.map((role, idx) => ({
|
|
2984
|
+
id: `action-${this.meetingId}-${idx}`,
|
|
2985
|
+
title: `${role} deliverables`,
|
|
2986
|
+
description: `Action items committed by ${role} during synthesis phase`,
|
|
2987
|
+
assignedDepartment: "engineering",
|
|
2988
|
+
// default; will be refined by compactor
|
|
2989
|
+
assignedRole: role,
|
|
2990
|
+
priority: "medium",
|
|
2991
|
+
dependencies: [],
|
|
2992
|
+
acceptanceCriteria: ["Deliverables completed as stated in final position"]
|
|
2993
|
+
}));
|
|
2994
|
+
const minutesRecord = createMinutes({
|
|
2995
|
+
meetingId: this.meetingId,
|
|
2996
|
+
format: "summary",
|
|
2997
|
+
content,
|
|
2998
|
+
actionItems
|
|
2999
|
+
});
|
|
3000
|
+
updateMeeting(this.meetingId, {
|
|
3001
|
+
status: "completed",
|
|
3002
|
+
completedAt: Date.now()
|
|
3003
|
+
});
|
|
3004
|
+
logger.info("Minutes generated", { meetingId: this.meetingId });
|
|
3005
|
+
return minutesRecord.id;
|
|
3006
|
+
}
|
|
3007
|
+
// ---- Completion status --------------------------------------------------
|
|
3008
|
+
/**
|
|
3009
|
+
* Check whether the meeting has been completed (minutes generated).
|
|
3010
|
+
*/
|
|
3011
|
+
isComplete() {
|
|
3012
|
+
const meeting = getMeeting(this.meetingId);
|
|
3013
|
+
if (!meeting) return false;
|
|
3014
|
+
return ["completed", "compacted", "reported"].includes(meeting.status);
|
|
3015
|
+
}
|
|
3016
|
+
// ---- Transcript access --------------------------------------------------
|
|
3017
|
+
/**
|
|
3018
|
+
* Get all transcript entries for this meeting.
|
|
3019
|
+
*/
|
|
3020
|
+
getTranscript() {
|
|
3021
|
+
return getTranscript(this.meetingId);
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
|
|
3025
|
+
// src/tools/add-transcript.ts
|
|
3026
|
+
var addTranscriptSchema = {
|
|
3027
|
+
meetingId: z14.string().describe("Meeting ID"),
|
|
3028
|
+
speakerRole: z14.string().describe('Role of the speaker (e.g. "Architecture Lead", "Engineering Lead")'),
|
|
3029
|
+
agendaItemIndex: z14.number().describe("Agenda item index (0-based). Use -1 for opening statements, -2 for synthesis"),
|
|
3030
|
+
roundNumber: z14.number().describe("Discussion round number (0 for opening/synthesis)"),
|
|
3031
|
+
content: z14.string().describe("The transcript content from the speaker")
|
|
3032
|
+
};
|
|
3033
|
+
async function addTranscriptHandler({
|
|
3034
|
+
meetingId,
|
|
3035
|
+
speakerRole,
|
|
3036
|
+
agendaItemIndex,
|
|
3037
|
+
roundNumber,
|
|
3038
|
+
content
|
|
3039
|
+
}) {
|
|
3040
|
+
try {
|
|
3041
|
+
const runner = new MeetingRunner(meetingId);
|
|
3042
|
+
const entry = runner.addTranscript(speakerRole, agendaItemIndex, roundNumber, content);
|
|
4673
3043
|
const result = {
|
|
3044
|
+
success: true,
|
|
3045
|
+
entryId: entry.id,
|
|
4674
3046
|
meetingId,
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
3047
|
+
speakerRole,
|
|
3048
|
+
agendaItemIndex,
|
|
3049
|
+
roundNumber
|
|
3050
|
+
};
|
|
3051
|
+
return {
|
|
3052
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3053
|
+
};
|
|
3054
|
+
} catch (error) {
|
|
3055
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3056
|
+
return {
|
|
3057
|
+
content: [
|
|
3058
|
+
{ type: "text", text: JSON.stringify({ error: message }, null, 2) }
|
|
3059
|
+
],
|
|
3060
|
+
isError: true
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
// src/tools/generate-minutes.ts
|
|
3066
|
+
import { z as z15 } from "zod";
|
|
3067
|
+
var generateMinutesSchema = {
|
|
3068
|
+
meetingId: z15.string().describe("Meeting ID to generate minutes for")
|
|
3069
|
+
};
|
|
3070
|
+
async function generateMinutesHandler({
|
|
3071
|
+
meetingId
|
|
3072
|
+
}) {
|
|
3073
|
+
try {
|
|
3074
|
+
const runner = new MeetingRunner(meetingId);
|
|
3075
|
+
const minutesId = await runner.generateMinutes();
|
|
3076
|
+
const minutes = getMinutesByMeeting(meetingId);
|
|
3077
|
+
const result = {
|
|
3078
|
+
minutesId,
|
|
3079
|
+
meetingId,
|
|
3080
|
+
content: minutes?.content ?? ""
|
|
4680
3081
|
};
|
|
4681
3082
|
return {
|
|
4682
3083
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -4700,7 +3101,7 @@ function createServer() {
|
|
|
4700
3101
|
});
|
|
4701
3102
|
server.tool(
|
|
4702
3103
|
"start-meeting",
|
|
4703
|
-
"
|
|
3104
|
+
"Create a new meeting record with topic and agenda. Returns meetingId and recommended departments. Does NOT run the meeting.",
|
|
4704
3105
|
startMeetingSchema,
|
|
4705
3106
|
startMeetingHandler
|
|
4706
3107
|
);
|
|
@@ -4724,7 +3125,7 @@ function createServer() {
|
|
|
4724
3125
|
);
|
|
4725
3126
|
server.tool(
|
|
4726
3127
|
"execute-tasks",
|
|
4727
|
-
"
|
|
3128
|
+
"Get the task list from compacted minutes for agent dispatch. Does NOT spawn workers.",
|
|
4728
3129
|
executeTasksSchema,
|
|
4729
3130
|
executeTasksHandler
|
|
4730
3131
|
);
|
|
@@ -4777,10 +3178,22 @@ function createServer() {
|
|
|
4777
3178
|
);
|
|
4778
3179
|
server.tool(
|
|
4779
3180
|
"chain-meeting",
|
|
4780
|
-
"
|
|
3181
|
+
"Create a new meeting chained from a previous meeting, using its minutes as context. Does NOT run the meeting.",
|
|
4781
3182
|
chainMeetingSchema,
|
|
4782
3183
|
chainMeetingHandler
|
|
4783
3184
|
);
|
|
3185
|
+
server.tool(
|
|
3186
|
+
"add-transcript",
|
|
3187
|
+
"Add a transcript entry to a meeting. Used to record speaker contributions during meeting phases.",
|
|
3188
|
+
addTranscriptSchema,
|
|
3189
|
+
addTranscriptHandler
|
|
3190
|
+
);
|
|
3191
|
+
server.tool(
|
|
3192
|
+
"generate-minutes",
|
|
3193
|
+
"Generate PRD minutes from all stored transcripts for a meeting. Marks the meeting as completed.",
|
|
3194
|
+
generateMinutesSchema,
|
|
3195
|
+
generateMinutesHandler
|
|
3196
|
+
);
|
|
4784
3197
|
return server;
|
|
4785
3198
|
}
|
|
4786
3199
|
|
|
@@ -6459,11 +4872,14 @@ function startDashboard(options) {
|
|
|
6459
4872
|
// src/index.ts
|
|
6460
4873
|
async function main() {
|
|
6461
4874
|
ensureDataDirs();
|
|
6462
|
-
const
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
4875
|
+
const isBareSubprocess = process.env["CLAUDE_CODE_SIMPLE"] === "1";
|
|
4876
|
+
if (!isBareSubprocess) {
|
|
4877
|
+
const sessionId = randomUUID();
|
|
4878
|
+
const projectPath = process.cwd();
|
|
4879
|
+
const projectName = projectPath.split("/").pop() ?? "unknown";
|
|
4880
|
+
startDashboard({ sessionId, projectPath, projectName }).catch(() => {
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
6467
4883
|
const server = createServer();
|
|
6468
4884
|
const transport = new StdioServerTransport();
|
|
6469
4885
|
await server.connect(transport);
|