@jackchen_me/open-multi-agent 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +8 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  4. package/.github/pull_request_template.md +0 -14
  5. package/.github/workflows/ci.yml +0 -23
  6. package/CLAUDE.md +0 -80
  7. package/CODE_OF_CONDUCT.md +0 -48
  8. package/CONTRIBUTING.md +0 -72
  9. package/DECISIONS.md +0 -43
  10. package/README_zh.md +0 -277
  11. package/SECURITY.md +0 -17
  12. package/examples/01-single-agent.ts +0 -131
  13. package/examples/02-team-collaboration.ts +0 -167
  14. package/examples/03-task-pipeline.ts +0 -201
  15. package/examples/04-multi-model-team.ts +0 -261
  16. package/examples/05-copilot-test.ts +0 -49
  17. package/examples/06-local-model.ts +0 -200
  18. package/examples/07-fan-out-aggregate.ts +0 -209
  19. package/examples/08-gemma4-local.ts +0 -192
  20. package/examples/09-structured-output.ts +0 -73
  21. package/examples/10-task-retry.ts +0 -132
  22. package/examples/11-trace-observability.ts +0 -133
  23. package/examples/12-grok.ts +0 -154
  24. package/examples/13-gemini.ts +0 -48
  25. package/src/agent/agent.ts +0 -622
  26. package/src/agent/loop-detector.ts +0 -137
  27. package/src/agent/pool.ts +0 -285
  28. package/src/agent/runner.ts +0 -542
  29. package/src/agent/structured-output.ts +0 -126
  30. package/src/index.ts +0 -182
  31. package/src/llm/adapter.ts +0 -98
  32. package/src/llm/anthropic.ts +0 -389
  33. package/src/llm/copilot.ts +0 -552
  34. package/src/llm/gemini.ts +0 -378
  35. package/src/llm/grok.ts +0 -29
  36. package/src/llm/openai-common.ts +0 -294
  37. package/src/llm/openai.ts +0 -292
  38. package/src/memory/shared.ts +0 -181
  39. package/src/memory/store.ts +0 -124
  40. package/src/orchestrator/orchestrator.ts +0 -1071
  41. package/src/orchestrator/scheduler.ts +0 -352
  42. package/src/task/queue.ts +0 -464
  43. package/src/task/task.ts +0 -239
  44. package/src/team/messaging.ts +0 -232
  45. package/src/team/team.ts +0 -334
  46. package/src/tool/built-in/bash.ts +0 -187
  47. package/src/tool/built-in/file-edit.ts +0 -154
  48. package/src/tool/built-in/file-read.ts +0 -105
  49. package/src/tool/built-in/file-write.ts +0 -81
  50. package/src/tool/built-in/grep.ts +0 -362
  51. package/src/tool/built-in/index.ts +0 -50
  52. package/src/tool/executor.ts +0 -178
  53. package/src/tool/framework.ts +0 -557
  54. package/src/tool/text-tool-extractor.ts +0 -219
  55. package/src/types.ts +0 -542
  56. package/src/utils/semaphore.ts +0 -89
  57. package/src/utils/trace.ts +0 -34
  58. package/tests/agent-hooks.test.ts +0 -473
  59. package/tests/agent-pool.test.ts +0 -212
  60. package/tests/approval.test.ts +0 -464
  61. package/tests/built-in-tools.test.ts +0 -393
  62. package/tests/gemini-adapter.test.ts +0 -97
  63. package/tests/grok-adapter.test.ts +0 -74
  64. package/tests/llm-adapters.test.ts +0 -357
  65. package/tests/loop-detection.test.ts +0 -456
  66. package/tests/openai-fallback.test.ts +0 -159
  67. package/tests/orchestrator.test.ts +0 -281
  68. package/tests/scheduler.test.ts +0 -221
  69. package/tests/semaphore.test.ts +0 -57
  70. package/tests/shared-memory.test.ts +0 -122
  71. package/tests/structured-output.test.ts +0 -331
  72. package/tests/task-queue.test.ts +0 -244
  73. package/tests/task-retry.test.ts +0 -368
  74. package/tests/task-utils.test.ts +0 -155
  75. package/tests/team-messaging.test.ts +0 -329
  76. package/tests/text-tool-extractor.test.ts +0 -170
  77. package/tests/tool-executor.test.ts +0 -193
  78. package/tests/trace.test.ts +0 -453
  79. package/tsconfig.json +0 -25
  80. package/vitest.config.ts +0 -9
@@ -1,131 +0,0 @@
1
- /**
2
- * Example 01 — Single Agent
3
- *
4
- * The simplest possible usage: one agent with bash and file tools, running
5
- * a coding task. Then shows streaming output using the Agent class directly.
6
- *
7
- * Run:
8
- * npx tsx examples/01-single-agent.ts
9
- *
10
- * Prerequisites:
11
- * ANTHROPIC_API_KEY env var must be set.
12
- */
13
-
14
- import { OpenMultiAgent, Agent, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../src/index.js'
15
- import type { OrchestratorEvent } from '../src/types.js'
16
-
17
- // ---------------------------------------------------------------------------
18
- // Part 1: Single agent via OpenMultiAgent (simplest path)
19
- // ---------------------------------------------------------------------------
20
-
21
- const orchestrator = new OpenMultiAgent({
22
- defaultModel: 'claude-sonnet-4-6',
23
- onProgress: (event: OrchestratorEvent) => {
24
- if (event.type === 'agent_start') {
25
- console.log(`[start] agent=${event.agent}`)
26
- } else if (event.type === 'agent_complete') {
27
- console.log(`[complete] agent=${event.agent}`)
28
- }
29
- },
30
- })
31
-
32
- console.log('Part 1: runAgent() — single one-shot task\n')
33
-
34
- const result = await orchestrator.runAgent(
35
- {
36
- name: 'coder',
37
- model: 'claude-sonnet-4-6',
38
- systemPrompt: `You are a focused TypeScript developer.
39
- When asked to implement something, write clean, minimal code with no extra commentary.
40
- Use the bash tool to run commands and the file tools to read/write files.`,
41
- tools: ['bash', 'file_read', 'file_write'],
42
- maxTurns: 8,
43
- },
44
- `Create a small TypeScript utility function in /tmp/greet.ts that:
45
- 1. Exports a function named greet(name: string): string
46
- 2. Returns "Hello, <name>!"
47
- 3. Adds a brief usage comment at the top of the file.
48
- Then add a default call greet("World") at the bottom and run the file with: npx tsx /tmp/greet.ts`,
49
- )
50
-
51
- if (result.success) {
52
- console.log('\nAgent output:')
53
- console.log('─'.repeat(60))
54
- console.log(result.output)
55
- console.log('─'.repeat(60))
56
- } else {
57
- console.error('Agent failed:', result.output)
58
- process.exit(1)
59
- }
60
-
61
- console.log('\nToken usage:')
62
- console.log(` input: ${result.tokenUsage.input_tokens}`)
63
- console.log(` output: ${result.tokenUsage.output_tokens}`)
64
- console.log(` tool calls made: ${result.toolCalls.length}`)
65
-
66
- // ---------------------------------------------------------------------------
67
- // Part 2: Streaming via Agent directly
68
- //
69
- // OpenMultiAgent.runAgent() is a convenient wrapper. When you need streaming, use
70
- // the Agent class directly with an injected ToolRegistry + ToolExecutor.
71
- // ---------------------------------------------------------------------------
72
-
73
- console.log('\n\nPart 2: Agent.stream() — incremental text output\n')
74
-
75
- // Build a registry with all built-in tools registered
76
- const registry = new ToolRegistry()
77
- registerBuiltInTools(registry)
78
- const executor = new ToolExecutor(registry)
79
-
80
- const streamingAgent = new Agent(
81
- {
82
- name: 'explainer',
83
- model: 'claude-sonnet-4-6',
84
- systemPrompt: 'You are a concise technical writer. Keep explanations brief.',
85
- maxTurns: 3,
86
- },
87
- registry,
88
- executor,
89
- )
90
-
91
- process.stdout.write('Streaming: ')
92
-
93
- for await (const event of streamingAgent.stream(
94
- 'In two sentences, explain what a TypeScript generic constraint is.',
95
- )) {
96
- if (event.type === 'text' && typeof event.data === 'string') {
97
- process.stdout.write(event.data)
98
- } else if (event.type === 'done') {
99
- process.stdout.write('\n')
100
- } else if (event.type === 'error') {
101
- console.error('\nStream error:', event.data)
102
- }
103
- }
104
-
105
- // ---------------------------------------------------------------------------
106
- // Part 3: Multi-turn conversation via Agent.prompt()
107
- // ---------------------------------------------------------------------------
108
-
109
- console.log('\nPart 3: Agent.prompt() — multi-turn conversation\n')
110
-
111
- const conversationAgent = new Agent(
112
- {
113
- name: 'tutor',
114
- model: 'claude-sonnet-4-6',
115
- systemPrompt: 'You are a TypeScript tutor. Give short, direct answers.',
116
- maxTurns: 2,
117
- },
118
- new ToolRegistry(), // no tools needed for this conversation
119
- new ToolExecutor(new ToolRegistry()),
120
- )
121
-
122
- const turn1 = await conversationAgent.prompt('What is a type guard in TypeScript?')
123
- console.log('Turn 1:', turn1.output.slice(0, 200))
124
-
125
- const turn2 = await conversationAgent.prompt('Give me one concrete code example of what you just described.')
126
- console.log('\nTurn 2:', turn2.output.slice(0, 300))
127
-
128
- // History is retained between prompt() calls
129
- console.log(`\nConversation history length: ${conversationAgent.getHistory().length} messages`)
130
-
131
- console.log('\nDone.')
@@ -1,167 +0,0 @@
1
- /**
2
- * Example 02 — Multi-Agent Team Collaboration
3
- *
4
- * Three specialised agents (architect, developer, reviewer) collaborate on a
5
- * shared goal. The OpenMultiAgent orchestrator breaks the goal into tasks, assigns
6
- * them to the right agents, and collects the results.
7
- *
8
- * Run:
9
- * npx tsx examples/02-team-collaboration.ts
10
- *
11
- * Prerequisites:
12
- * ANTHROPIC_API_KEY env var must be set.
13
- */
14
-
15
- import { OpenMultiAgent } from '../src/index.js'
16
- import type { AgentConfig, OrchestratorEvent } from '../src/types.js'
17
-
18
- // ---------------------------------------------------------------------------
19
- // Agent definitions
20
- // ---------------------------------------------------------------------------
21
-
22
- const architect: AgentConfig = {
23
- name: 'architect',
24
- model: 'claude-sonnet-4-6',
25
- provider: 'anthropic',
26
- systemPrompt: `You are a software architect with deep experience in Node.js and REST API design.
27
- Your job is to design clear, production-quality API contracts and file/directory structures.
28
- Output concise plans in markdown — no unnecessary prose.`,
29
- tools: ['bash', 'file_write'],
30
- maxTurns: 5,
31
- temperature: 0.2,
32
- }
33
-
34
- const developer: AgentConfig = {
35
- name: 'developer',
36
- model: 'claude-sonnet-4-6',
37
- provider: 'anthropic',
38
- systemPrompt: `You are a TypeScript/Node.js developer. You implement what the architect specifies.
39
- Write clean, runnable code with proper error handling. Use the tools to write files and run tests.`,
40
- tools: ['bash', 'file_read', 'file_write', 'file_edit'],
41
- maxTurns: 12,
42
- temperature: 0.1,
43
- }
44
-
45
- const reviewer: AgentConfig = {
46
- name: 'reviewer',
47
- model: 'claude-sonnet-4-6',
48
- provider: 'anthropic',
49
- systemPrompt: `You are a senior code reviewer. Review code for correctness, security, and clarity.
50
- Provide a structured review with: LGTM items, suggestions, and any blocking issues.
51
- Read files using the tools before reviewing.`,
52
- tools: ['bash', 'file_read', 'grep'],
53
- maxTurns: 5,
54
- temperature: 0.3,
55
- }
56
-
57
- // ---------------------------------------------------------------------------
58
- // Progress tracking
59
- // ---------------------------------------------------------------------------
60
-
61
- const startTimes = new Map<string, number>()
62
-
63
- function handleProgress(event: OrchestratorEvent): void {
64
- const ts = new Date().toISOString().slice(11, 23) // HH:MM:SS.mmm
65
-
66
- switch (event.type) {
67
- case 'agent_start':
68
- startTimes.set(event.agent ?? '', Date.now())
69
- console.log(`[${ts}] AGENT START → ${event.agent}`)
70
- break
71
-
72
- case 'agent_complete': {
73
- const elapsed = Date.now() - (startTimes.get(event.agent ?? '') ?? Date.now())
74
- console.log(`[${ts}] AGENT DONE ← ${event.agent} (${elapsed}ms)`)
75
- break
76
- }
77
-
78
- case 'task_start':
79
- console.log(`[${ts}] TASK START ↓ ${event.task}`)
80
- break
81
-
82
- case 'task_complete':
83
- console.log(`[${ts}] TASK DONE ↑ ${event.task}`)
84
- break
85
-
86
- case 'message':
87
- console.log(`[${ts}] MESSAGE • ${event.agent} → (team)`)
88
- break
89
-
90
- case 'error':
91
- console.error(`[${ts}] ERROR ✗ agent=${event.agent} task=${event.task}`)
92
- if (event.data instanceof Error) {
93
- console.error(` ${event.data.message}`)
94
- }
95
- break
96
- }
97
- }
98
-
99
- // ---------------------------------------------------------------------------
100
- // Orchestrate
101
- // ---------------------------------------------------------------------------
102
-
103
- const orchestrator = new OpenMultiAgent({
104
- defaultModel: 'claude-sonnet-4-6',
105
- maxConcurrency: 1, // run agents sequentially so output is readable
106
- onProgress: handleProgress,
107
- })
108
-
109
- const team = orchestrator.createTeam('api-team', {
110
- name: 'api-team',
111
- agents: [architect, developer, reviewer],
112
- sharedMemory: true,
113
- maxConcurrency: 1,
114
- })
115
-
116
- console.log(`Team "${team.name}" created with agents: ${team.getAgents().map(a => a.name).join(', ')}`)
117
- console.log('\nStarting team run...\n')
118
- console.log('='.repeat(60))
119
-
120
- const goal = `Create a minimal Express.js REST API in /tmp/express-api/ with:
121
- - GET /health → { status: "ok" }
122
- - GET /users → returns a hardcoded array of 2 user objects
123
- - POST /users → accepts { name, email } body, logs it, returns 201
124
- - Proper error handling middleware
125
- - The server should listen on port 3001
126
- - Include a package.json with the required dependencies`
127
-
128
- const result = await orchestrator.runTeam(team, goal)
129
-
130
- console.log('\n' + '='.repeat(60))
131
-
132
- // ---------------------------------------------------------------------------
133
- // Results
134
- // ---------------------------------------------------------------------------
135
-
136
- console.log('\nTeam run complete.')
137
- console.log(`Success: ${result.success}`)
138
- console.log(`Total tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
139
-
140
- console.log('\nPer-agent results:')
141
- for (const [agentName, agentResult] of result.agentResults) {
142
- const status = agentResult.success ? 'OK' : 'FAILED'
143
- const tools = agentResult.toolCalls.length
144
- console.log(` ${agentName.padEnd(12)} [${status}] tool_calls=${tools}`)
145
- if (!agentResult.success) {
146
- console.log(` Error: ${agentResult.output.slice(0, 120)}`)
147
- }
148
- }
149
-
150
- // Print the developer's final output (the actual code) as a sample
151
- const developerResult = result.agentResults.get('developer')
152
- if (developerResult?.success) {
153
- console.log('\nDeveloper output (last 600 chars):')
154
- console.log('─'.repeat(60))
155
- const out = developerResult.output
156
- console.log(out.length > 600 ? '...' + out.slice(-600) : out)
157
- console.log('─'.repeat(60))
158
- }
159
-
160
- // Print the reviewer's findings
161
- const reviewerResult = result.agentResults.get('reviewer')
162
- if (reviewerResult?.success) {
163
- console.log('\nReviewer output:')
164
- console.log('─'.repeat(60))
165
- console.log(reviewerResult.output)
166
- console.log('─'.repeat(60))
167
- }
@@ -1,201 +0,0 @@
1
- /**
2
- * Example 03 — Explicit Task Pipeline with Dependencies
3
- *
4
- * Demonstrates how to define tasks with explicit dependency chains
5
- * (design → implement → test → review) using runTasks(). The TaskQueue
6
- * automatically blocks downstream tasks until their dependencies complete.
7
- *
8
- * Run:
9
- * npx tsx examples/03-task-pipeline.ts
10
- *
11
- * Prerequisites:
12
- * ANTHROPIC_API_KEY env var must be set.
13
- */
14
-
15
- import { OpenMultiAgent } from '../src/index.js'
16
- import type { AgentConfig, OrchestratorEvent, Task } from '../src/types.js'
17
-
18
- // ---------------------------------------------------------------------------
19
- // Agents
20
- // ---------------------------------------------------------------------------
21
-
22
- const designer: AgentConfig = {
23
- name: 'designer',
24
- model: 'claude-sonnet-4-6',
25
- systemPrompt: `You are a software designer. Your output is always a concise technical spec
26
- in markdown. Focus on interfaces, data shapes, and file structure. Be brief.`,
27
- tools: ['file_write'],
28
- maxTurns: 4,
29
- }
30
-
31
- const implementer: AgentConfig = {
32
- name: 'implementer',
33
- model: 'claude-sonnet-4-6',
34
- systemPrompt: `You are a TypeScript developer. Read the design spec written by the designer,
35
- then implement it. Write all files to /tmp/pipeline-output/. Use the tools.`,
36
- tools: ['bash', 'file_read', 'file_write'],
37
- maxTurns: 10,
38
- }
39
-
40
- const tester: AgentConfig = {
41
- name: 'tester',
42
- model: 'claude-sonnet-4-6',
43
- systemPrompt: `You are a QA engineer. Read the implemented files and run them to verify correctness.
44
- Report: what passed, what failed, and any bugs found.`,
45
- tools: ['bash', 'file_read', 'grep'],
46
- maxTurns: 6,
47
- }
48
-
49
- const reviewer: AgentConfig = {
50
- name: 'reviewer',
51
- model: 'claude-sonnet-4-6',
52
- systemPrompt: `You are a code reviewer. Read all files and produce a brief structured review.
53
- Sections: Summary, Strengths, Issues (if any), Verdict (SHIP / NEEDS WORK).`,
54
- tools: ['file_read', 'grep'],
55
- maxTurns: 4,
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Progress handler — shows dependency blocking/unblocking
60
- // ---------------------------------------------------------------------------
61
-
62
- const taskTimes = new Map<string, number>()
63
-
64
- function handleProgress(event: OrchestratorEvent): void {
65
- const ts = new Date().toISOString().slice(11, 23)
66
-
67
- switch (event.type) {
68
- case 'task_start': {
69
- taskTimes.set(event.task ?? '', Date.now())
70
- const task = event.data as Task | undefined
71
- console.log(`[${ts}] TASK READY "${task?.title ?? event.task}" (assignee: ${task?.assignee ?? 'any'})`)
72
- break
73
- }
74
- case 'task_complete': {
75
- const elapsed = Date.now() - (taskTimes.get(event.task ?? '') ?? Date.now())
76
- const task = event.data as Task | undefined
77
- console.log(`[${ts}] TASK DONE "${task?.title ?? event.task}" in ${elapsed}ms`)
78
- break
79
- }
80
- case 'agent_start':
81
- console.log(`[${ts}] AGENT START ${event.agent}`)
82
- break
83
- case 'agent_complete':
84
- console.log(`[${ts}] AGENT DONE ${event.agent}`)
85
- break
86
- case 'error': {
87
- const task = event.data as Task | undefined
88
- console.error(`[${ts}] ERROR ${event.agent ?? ''} task="${task?.title ?? event.task}"`)
89
- break
90
- }
91
- }
92
- }
93
-
94
- // ---------------------------------------------------------------------------
95
- // Build the pipeline
96
- // ---------------------------------------------------------------------------
97
-
98
- const orchestrator = new OpenMultiAgent({
99
- defaultModel: 'claude-sonnet-4-6',
100
- maxConcurrency: 2, // allow test + review to potentially run in parallel later
101
- onProgress: handleProgress,
102
- })
103
-
104
- const team = orchestrator.createTeam('pipeline-team', {
105
- name: 'pipeline-team',
106
- agents: [designer, implementer, tester, reviewer],
107
- sharedMemory: true,
108
- })
109
-
110
- // Task IDs — use stable strings so dependsOn can reference them
111
- // (IDs will be generated by the framework; we capture the returned Task objects)
112
- const SPEC_FILE = '/tmp/pipeline-output/design-spec.md'
113
-
114
- const tasks: Array<{
115
- title: string
116
- description: string
117
- assignee?: string
118
- dependsOn?: string[]
119
- }> = [
120
- {
121
- title: 'Design: URL shortener data model',
122
- description: `Design a minimal in-memory URL shortener service.
123
- Write a markdown spec to ${SPEC_FILE} covering:
124
- - TypeScript interfaces for Url and ShortenRequest
125
- - The shortening algorithm (hash approach is fine)
126
- - API contract: POST /shorten, GET /:code
127
- Keep the spec under 30 lines.`,
128
- assignee: 'designer',
129
- // no dependencies — this is the root task
130
- },
131
- {
132
- title: 'Implement: URL shortener',
133
- description: `Read the design spec at ${SPEC_FILE}.
134
- Implement the URL shortener in /tmp/pipeline-output/src/:
135
- - shortener.ts: core logic (shorten, resolve functions)
136
- - server.ts: tiny HTTP server using Node's built-in http module (no Express)
137
- - POST /shorten body: { url: string } → { code: string, short: string }
138
- - GET /:code → redirect (301) or 404
139
- - index.ts: entry point that starts the server on port 3002
140
- No external dependencies beyond Node built-ins.`,
141
- assignee: 'implementer',
142
- dependsOn: ['Design: URL shortener data model'],
143
- },
144
- {
145
- title: 'Test: URL shortener',
146
- description: `Run the URL shortener implementation:
147
- 1. Start the server: node /tmp/pipeline-output/src/index.ts (or tsx)
148
- 2. POST a URL to shorten it using curl
149
- 3. Verify the GET redirect works
150
- 4. Report what passed and what (if anything) failed.
151
- Kill the server after testing.`,
152
- assignee: 'tester',
153
- dependsOn: ['Implement: URL shortener'],
154
- },
155
- {
156
- title: 'Review: URL shortener',
157
- description: `Read all .ts files in /tmp/pipeline-output/src/ and the design spec.
158
- Produce a structured code review with sections:
159
- - Summary (2 sentences)
160
- - Strengths (bullet list)
161
- - Issues (bullet list, or "None" if clean)
162
- - Verdict: SHIP or NEEDS WORK`,
163
- assignee: 'reviewer',
164
- dependsOn: ['Implement: URL shortener'], // runs in parallel with Test after Implement completes
165
- },
166
- ]
167
-
168
- // ---------------------------------------------------------------------------
169
- // Run
170
- // ---------------------------------------------------------------------------
171
-
172
- console.log('Starting 4-stage task pipeline...\n')
173
- console.log('Pipeline: design → implement → test + review (parallel)')
174
- console.log('='.repeat(60))
175
-
176
- const result = await orchestrator.runTasks(team, tasks)
177
-
178
- // ---------------------------------------------------------------------------
179
- // Summary
180
- // ---------------------------------------------------------------------------
181
-
182
- console.log('\n' + '='.repeat(60))
183
- console.log('Pipeline complete.\n')
184
- console.log(`Overall success: ${result.success}`)
185
- console.log(`Tokens — input: ${result.totalTokenUsage.input_tokens}, output: ${result.totalTokenUsage.output_tokens}`)
186
-
187
- console.log('\nPer-agent summary:')
188
- for (const [name, r] of result.agentResults) {
189
- const icon = r.success ? 'OK ' : 'FAIL'
190
- const toolCount = r.toolCalls.map(c => c.toolName).join(', ')
191
- console.log(` [${icon}] ${name.padEnd(14)} tools used: ${toolCount || '(none)'}`)
192
- }
193
-
194
- // Print the reviewer's verdict
195
- const review = result.agentResults.get('reviewer')
196
- if (review?.success) {
197
- console.log('\nCode review:')
198
- console.log('─'.repeat(60))
199
- console.log(review.output)
200
- console.log('─'.repeat(60))
201
- }