speexor 0.1.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/API-REFERENCE.md +201 -0
- package/ARCHITECTURE.md +548 -0
- package/CHANGELOG.md +52 -0
- package/CODE-OF-CONDUCT.md +83 -0
- package/CONTRIBUTING.md +98 -0
- package/FAQ.md +105 -0
- package/LICENSE.md +21 -0
- package/PUBLISH.md +77 -0
- package/README.md +179 -0
- package/REFACTOR-LOG.md +40 -0
- package/ROADMAP.md +78 -0
- package/SECURITY.md +79 -0
- package/SUMMARY.md +46 -0
- package/TESTING.md +140 -0
- package/dist/agent-5D3BVWNK.js +37 -0
- package/dist/agent-5D3BVWNK.js.map +1 -0
- package/dist/chunk-2F66BZYJ.js +212 -0
- package/dist/chunk-2F66BZYJ.js.map +1 -0
- package/dist/chunk-5NA2TFPG.js +3 -0
- package/dist/chunk-5NA2TFPG.js.map +1 -0
- package/dist/chunk-B7WLHC4W.js +666 -0
- package/dist/chunk-B7WLHC4W.js.map +1 -0
- package/dist/chunk-SXALZEOJ.js +345 -0
- package/dist/chunk-SXALZEOJ.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +287 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +31 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/types-0q_okI2g.d.ts +205 -0
- package/docs/PRD01.md +264 -0
- package/docs/PRD02.md +299 -0
- package/docs/PRD03.md +0 -0
- package/docs/PRD04.md +349 -0
- package/docs/PRD05.md +312 -0
- package/docs/SETUP.md +94 -0
- package/docs/TROUBLESHOOTING.md +113 -0
- package/examples/basic.yaml +61 -0
- package/package.json +102 -0
- package/schema/config.schema.json +119 -0
- package/speexor.config.yaml.example +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reaction/engine.ts","../src/store/session.ts"],"names":["debug","Debug"],"mappings":"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","file":"index.js","sourcesContent":["import type { SpeexorLifecycle } from '../core/lifecycle.js'\nimport type { ReactionRule, TrackerEvent, ProjectConfig, AgentTask } from '../core/types.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:reaction')\n\ninterface ReactionHandler {\n eventType: string\n rule: ReactionRule\n project: ProjectConfig\n handle(event: TrackerEvent): Promise<void>\n}\n\nexport class ReactionEngine {\n private lifecycle: SpeexorLifecycle\n private handlers: ReactionHandler[] = []\n private retryCounts = new Map<string, number>()\n\n constructor(lifecycle: SpeexorLifecycle) {\n this.lifecycle = lifecycle\n\n // Listen to lifecycle events\n lifecycle.eventBus.on('reaction:triggered', (data: unknown) => {\n const { eventType, taskId } = data as { eventType: string; taskId: string }\n debug(`Reaction triggered: ${eventType} for task ${taskId}`)\n })\n }\n\n configure(projects: ProjectConfig[]): void {\n this.handlers = []\n\n for (const project of projects) {\n if (!project.reactions) continue\n\n for (const [eventType, rule] of Object.entries(project.reactions)) {\n if (!rule) continue\n\n this.handlers.push({\n eventType,\n rule,\n project,\n handle: async (event: TrackerEvent) => {\n await this.executeReaction(event, rule, project)\n },\n })\n }\n }\n\n debug(`Configured ${this.handlers.length} reaction handler(s)`)\n }\n\n async processEvent(event: TrackerEvent): Promise<void> {\n debug(`Processing event: ${event.type} for issue ${event.issueId}`)\n\n const matchingHandlers = this.handlers.filter((h) => h.eventType === event.type)\n\n for (const handler of matchingHandlers) {\n const key = `${event.issueId}:${handler.eventType}`\n const retries = this.retryCounts.get(key) ?? 0\n\n if (retries >= handler.rule.retries) {\n debug(`Max retries reached for ${key} (${retries}/${handler.rule.retries})`)\n if (handler.rule.action === 'escalate') {\n this.escalate(event, handler.project)\n }\n continue\n }\n\n try {\n await handler.handle(event)\n this.retryCounts.set(key, retries + 1)\n } catch (error) {\n debug(`Reaction failed for ${key}: ${error}`)\n }\n }\n }\n\n private async executeReaction(event: TrackerEvent, rule: ReactionRule, project: ProjectConfig): Promise<void> {\n if (!rule.auto) {\n debug(`Rule for ${event.type} is not auto — notifying instead`)\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'notify',\n })\n return\n }\n\n switch (rule.action) {\n case 'fix':\n await this.autoFix(event, project)\n break\n case 'notify':\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'notify',\n })\n break\n case 'escalate':\n this.escalate(event, project)\n break\n case 'skip':\n debug(`Skipping reaction for ${event.type}`)\n break\n }\n }\n\n private async autoFix(event: TrackerEvent, project: ProjectConfig): Promise<void> {\n const task: AgentTask = {\n id: `fix-${event.issueId}-${Date.now()}`,\n title: `Auto-fix: ${event.type} for issue ${event.issueId}`,\n description: `Automated fix triggered by event: ${event.type}\\nIssue: ${event.issueId}\\nData: ${JSON.stringify(event.data)}`,\n repository: project.repository,\n branch: `speexor/fix-${event.issueId}`,\n provider: project.provider.primary,\n }\n\n debug(`Auto-fix spawning agent for task ${task.id}`)\n\n try {\n await this.lifecycle.spawnAgent(task)\n } catch (error) {\n debug(`Auto-fix spawn failed: ${error}`)\n }\n }\n\n private escalate(event: TrackerEvent, _project: ProjectConfig): void {\n debug(`Escalating: ${event.type} for issue ${event.issueId}`)\n\n this.lifecycle.eventBus.emit('reaction:triggered', {\n eventType: event.type,\n taskId: event.issueId,\n action: 'escalate',\n message: `Max retries exceeded for ${event.type} on issue ${event.issueId}`,\n })\n }\n\n getRetryCount(key: string): number {\n return this.retryCounts.get(key) ?? 0\n }\n\n resetRetryCount(key: string): void {\n this.retryCounts.delete(key)\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { SpeexorState, AgentSession, WorktreeSession, ProjectConfig } from '../core/types.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:store')\n\nexport class SessionStore {\n private statePath: string\n private state: SpeexorState\n\n constructor(baseDir?: string) {\n const dir = baseDir ?? join(process.cwd(), '.speexor')\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n\n this.statePath = join(dir, 'state.json')\n this.state = this.load()\n }\n\n private load(): SpeexorState {\n try {\n if (existsSync(this.statePath)) {\n const raw = readFileSync(this.statePath, 'utf-8')\n const data = JSON.parse(raw)\n return {\n sessions: data.sessions ?? [],\n worktrees: data.worktrees ?? [],\n runtimes: data.runtimes ?? [],\n projects: data.projects ?? [],\n }\n }\n } catch (error) {\n debug(`Failed to load state: ${error}`)\n }\n\n return { sessions: [], worktrees: [], runtimes: [], projects: [] }\n }\n\n private save(): void {\n try {\n writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), 'utf-8')\n } catch (error) {\n debug(`Failed to save state: ${error}`)\n }\n }\n\n getState(): SpeexorState {\n return { ...this.state }\n }\n\n addSession(session: AgentSession): void {\n this.state.sessions.push(session)\n this.save()\n }\n\n updateSession(sessionId: string, updates: Partial<AgentSession>): void {\n const idx = this.state.sessions.findIndex((s) => s.id === sessionId)\n if (idx !== -1) {\n const existing = this.state.sessions[idx]\n if (existing) {\n if (updates.id !== undefined) existing.id = updates.id\n if (updates.taskId !== undefined) existing.taskId = updates.taskId\n if (updates.provider !== undefined) existing.provider = updates.provider\n if (updates.status !== undefined) existing.status = updates.status\n if (updates.startedAt !== undefined) existing.startedAt = updates.startedAt\n if (updates.runtimeSessionId !== undefined) existing.runtimeSessionId = updates.runtimeSessionId\n this.save()\n }\n }\n }\n\n removeSession(sessionId: string): void {\n this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId)\n this.save()\n }\n\n addWorktree(worktree: WorktreeSession): void {\n this.state.worktrees.push(worktree)\n this.save()\n }\n\n removeWorktree(sessionId: string): void {\n this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId)\n this.save()\n }\n\n setProjects(projects: ProjectConfig[]): void {\n this.state.projects = projects\n this.save()\n }\n\n clear(): void {\n this.state = { sessions: [], worktrees: [], runtimes: [], projects: [] }\n this.save()\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
type AgentStatus = 'idle' | 'running' | 'stuck' | 'needs-review' | 'done' | 'error';
|
|
2
|
+
type AgentProvider = 'opencode' | 'claude-code' | 'aider' | 'codex';
|
|
3
|
+
type RuntimeType = 'tmux' | 'process';
|
|
4
|
+
type SessionStatus = 'initializing' | 'active' | 'paused' | 'completed' | 'failed' | 'cancelled';
|
|
5
|
+
interface PluginModule {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
type: PluginSlot;
|
|
9
|
+
initialize(context: PluginContext): Promise<void>;
|
|
10
|
+
destroy(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
type PluginSlot = 'agent' | 'runtime' | 'workspace' | 'tracker' | 'scm' | 'notifier' | 'terminal';
|
|
13
|
+
interface PluginContext {
|
|
14
|
+
config: SpeexorConfig;
|
|
15
|
+
eventBus: EventBus;
|
|
16
|
+
logger: (msg: string) => void;
|
|
17
|
+
}
|
|
18
|
+
interface AgentPlugin extends PluginModule {
|
|
19
|
+
type: 'agent';
|
|
20
|
+
spawn(task: AgentTask, runtime: RuntimeSession): Promise<AgentSession>;
|
|
21
|
+
sendInput(sessionId: string, input: string): Promise<void>;
|
|
22
|
+
getStatus(sessionId: string): Promise<AgentStatus>;
|
|
23
|
+
kill(sessionId: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface AgentTask {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
description: string;
|
|
29
|
+
repository: string;
|
|
30
|
+
branch: string;
|
|
31
|
+
provider?: AgentProvider;
|
|
32
|
+
model?: string;
|
|
33
|
+
}
|
|
34
|
+
interface AgentSession {
|
|
35
|
+
id: string;
|
|
36
|
+
taskId: string;
|
|
37
|
+
provider: AgentProvider;
|
|
38
|
+
status: AgentStatus;
|
|
39
|
+
startedAt: Date;
|
|
40
|
+
runtimeSessionId: string;
|
|
41
|
+
}
|
|
42
|
+
interface RuntimePlugin extends PluginModule {
|
|
43
|
+
type: 'runtime';
|
|
44
|
+
createSession(worktreePath: string): Promise<RuntimeSession>;
|
|
45
|
+
destroySession(sessionId: string): Promise<void>;
|
|
46
|
+
sendInput(sessionId: string, input: string): Promise<void>;
|
|
47
|
+
getOutput(sessionId: string): Promise<string>;
|
|
48
|
+
getLiveStream(sessionId: string): AsyncIterable<string>;
|
|
49
|
+
getStatus(sessionId: string): Promise<'running' | 'stopped' | 'error'>;
|
|
50
|
+
}
|
|
51
|
+
interface RuntimeSession {
|
|
52
|
+
id: string;
|
|
53
|
+
type: RuntimeType;
|
|
54
|
+
worktreePath: string;
|
|
55
|
+
pid?: number;
|
|
56
|
+
createdAt: Date;
|
|
57
|
+
}
|
|
58
|
+
interface WorkspacePlugin extends PluginModule {
|
|
59
|
+
type: 'workspace';
|
|
60
|
+
createWorktree(task: AgentTask): Promise<WorktreeSession>;
|
|
61
|
+
removeWorktree(sessionId: string): Promise<void>;
|
|
62
|
+
getWorktreePath(sessionId: string): string;
|
|
63
|
+
listActive(): Promise<WorktreeSession[]>;
|
|
64
|
+
cleanupStale(): Promise<string[]>;
|
|
65
|
+
}
|
|
66
|
+
interface WorktreeSession {
|
|
67
|
+
id: string;
|
|
68
|
+
taskId: string;
|
|
69
|
+
repository: string;
|
|
70
|
+
branch: string;
|
|
71
|
+
path: string;
|
|
72
|
+
createdAt: Date;
|
|
73
|
+
}
|
|
74
|
+
interface TrackerPlugin extends PluginModule {
|
|
75
|
+
type: 'tracker';
|
|
76
|
+
fetchIssues(filter?: TrackerFilter): Promise<TrackerIssue[]>;
|
|
77
|
+
getIssue(id: string): Promise<TrackerIssue | null>;
|
|
78
|
+
onEvent(handler: (event: TrackerEvent) => void): void;
|
|
79
|
+
}
|
|
80
|
+
interface TrackerFilter {
|
|
81
|
+
state?: 'open' | 'closed' | 'all';
|
|
82
|
+
labels?: string[];
|
|
83
|
+
since?: Date;
|
|
84
|
+
limit?: number;
|
|
85
|
+
}
|
|
86
|
+
interface TrackerIssue {
|
|
87
|
+
id: string;
|
|
88
|
+
title: string;
|
|
89
|
+
description: string;
|
|
90
|
+
state: 'open' | 'closed';
|
|
91
|
+
labels: string[];
|
|
92
|
+
url: string;
|
|
93
|
+
createdAt: Date;
|
|
94
|
+
updatedAt: Date;
|
|
95
|
+
}
|
|
96
|
+
type TrackerEventType = 'issue-opened' | 'issue-closed' | 'ci-failed' | 'ci-passed' | 'changes-requested' | 'approved';
|
|
97
|
+
interface TrackerEvent {
|
|
98
|
+
type: TrackerEventType;
|
|
99
|
+
issueId: string;
|
|
100
|
+
timestamp: Date;
|
|
101
|
+
data: Record<string, unknown>;
|
|
102
|
+
}
|
|
103
|
+
interface SCMPlugin extends PluginModule {
|
|
104
|
+
type: 'scm';
|
|
105
|
+
createBranch(baseBranch: string, newBranch: string): Promise<void>;
|
|
106
|
+
commitAndPush(branch: string, message: string): Promise<string>;
|
|
107
|
+
createPullRequest(title: string, description: string, head: string, base: string): Promise<PRInfo>;
|
|
108
|
+
getPRStatus(prId: string): Promise<PRStatus>;
|
|
109
|
+
getPRComments(prId: string): Promise<PRComment[]>;
|
|
110
|
+
getCIRuns(prId: string): Promise<CIRun[]>;
|
|
111
|
+
mergePR(prId: string, method?: 'merge' | 'squash' | 'rebase'): Promise<void>;
|
|
112
|
+
onEvent(handler: (event: TrackerEvent) => void): void;
|
|
113
|
+
}
|
|
114
|
+
interface PRInfo {
|
|
115
|
+
id: string;
|
|
116
|
+
url: string;
|
|
117
|
+
title: string;
|
|
118
|
+
state: 'open' | 'closed' | 'merged';
|
|
119
|
+
headBranch: string;
|
|
120
|
+
baseBranch: string;
|
|
121
|
+
createdAt: Date;
|
|
122
|
+
}
|
|
123
|
+
interface PRStatus {
|
|
124
|
+
id: string;
|
|
125
|
+
state: 'open' | 'closed' | 'merged';
|
|
126
|
+
mergeable: boolean;
|
|
127
|
+
ciStatus: 'pending' | 'passing' | 'failing' | 'unknown';
|
|
128
|
+
reviewStatus: 'approved' | 'changes-requested' | 'pending' | 'none';
|
|
129
|
+
}
|
|
130
|
+
interface PRComment {
|
|
131
|
+
id: string;
|
|
132
|
+
author: string;
|
|
133
|
+
body: string;
|
|
134
|
+
file?: string;
|
|
135
|
+
line?: number;
|
|
136
|
+
createdAt: Date;
|
|
137
|
+
}
|
|
138
|
+
interface CIRun {
|
|
139
|
+
id: string;
|
|
140
|
+
name: string;
|
|
141
|
+
status: 'queued' | 'in_progress' | 'completed';
|
|
142
|
+
conclusion: 'success' | 'failure' | 'cancelled' | 'skipped' | null;
|
|
143
|
+
url: string;
|
|
144
|
+
}
|
|
145
|
+
interface NotifierPlugin extends PluginModule {
|
|
146
|
+
type: 'notifier';
|
|
147
|
+
notify(level: 'info' | 'warn' | 'error' | 'success', title: string, message: string): Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
interface TerminalPlugin extends PluginModule {
|
|
150
|
+
type: 'terminal';
|
|
151
|
+
attach(sessionId: string): Promise<void>;
|
|
152
|
+
detach(sessionId: string): Promise<void>;
|
|
153
|
+
write(sessionId: string, data: string): Promise<void>;
|
|
154
|
+
onData(sessionId: string, handler: (data: string) => void): void;
|
|
155
|
+
}
|
|
156
|
+
interface EventBus {
|
|
157
|
+
emit(event: string, data: unknown): void;
|
|
158
|
+
on(event: string, handler: (data: unknown) => void): void;
|
|
159
|
+
off(event: string, handler: (data: unknown) => void): void;
|
|
160
|
+
once(event: string, handler: (data: unknown) => void): void;
|
|
161
|
+
}
|
|
162
|
+
interface ReactionConfig {
|
|
163
|
+
'ci-failed': ReactionRule;
|
|
164
|
+
'changes-requested': ReactionRule;
|
|
165
|
+
'approved-and-green': ReactionRule;
|
|
166
|
+
}
|
|
167
|
+
interface ReactionRule {
|
|
168
|
+
auto: boolean;
|
|
169
|
+
action: 'fix' | 'notify' | 'escalate' | 'skip';
|
|
170
|
+
retries: number;
|
|
171
|
+
escalateAfter: number;
|
|
172
|
+
}
|
|
173
|
+
interface ProviderRouting {
|
|
174
|
+
primary: AgentProvider;
|
|
175
|
+
fallback?: AgentProvider[];
|
|
176
|
+
concurrentLimit?: number;
|
|
177
|
+
costLimit?: number;
|
|
178
|
+
}
|
|
179
|
+
interface SpeexorConfig {
|
|
180
|
+
version: '1';
|
|
181
|
+
projects: ProjectConfig[];
|
|
182
|
+
}
|
|
183
|
+
interface ProjectConfig {
|
|
184
|
+
name: string;
|
|
185
|
+
repository: string;
|
|
186
|
+
path?: string;
|
|
187
|
+
branch?: string;
|
|
188
|
+
provider: ProviderRouting;
|
|
189
|
+
reactions?: Partial<ReactionConfig>;
|
|
190
|
+
plugins?: {
|
|
191
|
+
tracker?: string;
|
|
192
|
+
scm?: string;
|
|
193
|
+
runtime?: string;
|
|
194
|
+
notifier?: string;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
interface SpeexorState {
|
|
198
|
+
sessions: AgentSession[];
|
|
199
|
+
worktrees: WorktreeSession[];
|
|
200
|
+
runtimes: RuntimeSession[];
|
|
201
|
+
projects: ProjectConfig[];
|
|
202
|
+
}
|
|
203
|
+
type SessionEventType = 'session:created' | 'session:status-changed' | 'session:completed' | 'session:failed' | 'worktree:created' | 'worktree:removed' | 'reaction:triggered' | 'reaction:completed' | 'error';
|
|
204
|
+
|
|
205
|
+
export type { AgentSession as A, CIRun as C, EventBus as E, NotifierPlugin as N, PluginModule as P, RuntimeSession as R, SpeexorState as S, TrackerEvent as T, WorktreeSession as W, ProjectConfig as a, AgentPlugin as b, AgentProvider as c, AgentStatus as d, AgentTask as e, PRComment as f, PRInfo as g, PRStatus as h, PluginContext as i, PluginSlot as j, ProviderRouting as k, ReactionConfig as l, ReactionRule as m, RuntimePlugin as n, RuntimeType as o, SCMPlugin as p, SessionEventType as q, SessionStatus as r, SpeexorConfig as s, TerminalPlugin as t, TrackerEventType as u, TrackerFilter as v, TrackerIssue as w, TrackerPlugin as x, WorkspacePlugin as y };
|
package/docs/PRD01.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# PRD: Agent Orchestrator (Codename: "Speexor")
|
|
2
|
+
|
|
3
|
+
**Versi:** 0.1 — Draft
|
|
4
|
+
**Penulis:** Aditya (dengan bantuan Claude)
|
|
5
|
+
**Tanggal:** 30 Juni 2026
|
|
6
|
+
**Status:** Draft untuk validasi internal sebelum mulai development
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Ringkasan Eksekutif
|
|
11
|
+
|
|
12
|
+
Speexor adalah orchestration layer untuk menjalankan banyak AI coding agent secara paralel di atas satu atau lebih repository. Setiap agent berjalan terisolasi di git worktree-nya sendiri, menangani satu task/issue, dan secara otonom memperbaiki CI yang gagal, merespons review comment, lalu membuka PR. Developer (kamu) hanya turun tangan saat dibutuhkan judgment manusia: approve PR, resolve konflik desain, atau keputusan arsitektur.
|
|
13
|
+
|
|
14
|
+
Produk ini terinspirasi dari `agent-orchestrator` (AO) milik AgentWrapper, tapi dibangun ulang agar:
|
|
15
|
+
|
|
16
|
+
- Native mendukung workflow vibe coding kamu yang sudah berjalan di OpenCode CLI multi-provider (Zen, DeepSeek, GLM, Groq, Google AI Studio).
|
|
17
|
+
- Agent-agnostic dari hari pertama: OpenCode, Claude Code, Aider, Codex semua first-class.
|
|
18
|
+
- Git-provider agnostic lewat plugin interface, dengan GitHub sebagai implementasi pertama yang dikerjakan penuh.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Latar Belakang & Problem Statement
|
|
23
|
+
|
|
24
|
+
Kamu saat ini mengelola banyak project aktif sekaligus (BrainClash, Kata Netizen, Smart Vision Glasses, HRIS, EVote Transparan) dengan workflow vibe coding manual: buka terminal, jalankan OpenCode, tunggu, cek hasil, ulangi untuk project lain. Masalah yang muncul:
|
|
25
|
+
|
|
26
|
+
1. **Context switching tinggi** — pindah antar project butuh setup ulang context/agent setiap kali.
|
|
27
|
+
2. **Tidak ada paralelisme aman** — menjalankan dua agent di branch berbeda pada repo yang sama berisiko konflik file/state.
|
|
28
|
+
3. **Feedback loop manual** — kalau CI gagal atau ada review comment, kamu yang harus baca log, copy-paste ke agent, dan menunggu lagi.
|
|
29
|
+
4. **Tidak ada visibilitas terpusat** — status semua agent (jalan, stuck, selesai, butuh review) tidak terlihat dalam satu tempat.
|
|
30
|
+
5. **Vendor lock per provider** — workflow saat ini terikat ke cara kamu invoke OpenCode CLI secara manual, sulit di-generalize ke Claude Code/Aider/Codex.
|
|
31
|
+
|
|
32
|
+
Tanpa orchestration layer, scaling dari "1 agent, 1 terminal" ke "5 agent paralel di 3 repo" jadi tidak realistis untuk dikelola manual.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 3. Tujuan Produk (Goals)
|
|
37
|
+
|
|
38
|
+
| # | Goal | Metrik Keberhasilan |
|
|
39
|
+
| --- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
40
|
+
| G1 | Spawn & kelola banyak agent paralel per repo dengan isolasi penuh | N agent berjalan simultan di N worktree tanpa file/state collision |
|
|
41
|
+
| G2 | Otomatisasi feedback loop (CI fail, review comment) tanpa intervensi manual | ≥80% CI-fail case auto-routed ke agent tanpa kamu baca log manual |
|
|
42
|
+
| G3 | Dashboard terpusat untuk monitor semua project & agent | Status seluruh fleet terlihat dalam 1 layar, update real-time |
|
|
43
|
+
| G4 | Plugin architecture agnostic (agent, tracker, runtime, SCM, notifier) | Menambah agent/tracker baru tanpa ubah core, cukup implement interface |
|
|
44
|
+
| G5 | Setup cepat, sesuai workflow OpenCode kamu | `speexor start` di repo lokal → dashboard jalan < 1 menit |
|
|
45
|
+
|
|
46
|
+
### Non-Goals (Eksplisit di luar scope v1)
|
|
47
|
+
|
|
48
|
+
- Bukan pengganti CI/CD pipeline (tetap pakai GitHub Actions/CI yang sudah ada, Speexor hanya merespons hasilnya).
|
|
49
|
+
- Bukan code review tool penuh (tidak menggantikan human review untuk merge decision akhir; auto-merge dimatikan secara default).
|
|
50
|
+
- Tidak menyediakan hosting model AI sendiri; semua agent backend tetap pakai CLI/API masing-masing (OpenCode, Claude Code, dst).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 4. Target Pengguna
|
|
55
|
+
|
|
56
|
+
**Primary:** Kamu sendiri (solo developer/indie hacker yang vibe coding lintas banyak project).
|
|
57
|
+
**Secondary (potensial open-source):** Developer Indonesia di komunitas WPU atau pengguna OpenCode CLI lain yang punya kebutuhan serupa — multi-project, multi-provider, ingin orchestration tanpa vendor lock ke satu agent CLI.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 5. Studi Komparatif Singkat: AO vs Speexor
|
|
62
|
+
|
|
63
|
+
| Aspek | AO (AgentWrapper) | Speexor (rencana) |
|
|
64
|
+
| ---------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| Bahasa | TypeScript (monorepo pnpm) | TypeScript (monorepo pnpm, sejalan stack kamu) |
|
|
66
|
+
| Default agent | claude-code | Tidak ada default tunggal — pilih saat `speexor start`, OpenCode jadi first-tier karena workflow kamu |
|
|
67
|
+
| Runtime | tmux / process (ConPTY) / docker | tmux (Linux/macOS) + process fallback; Docker masuk v2 |
|
|
68
|
+
| Tracker | github, linear, gitlab | github (v1 penuh), interface terbuka untuk gitlab/gitea/bitbucket di v2 |
|
|
69
|
+
| Dashboard | Web, port 3000 | Web, port custom, fokus dashboard fleet + multi-provider cost view (karena kamu peduli cost OpenCode Go vs DeepSeek vs GLM) |
|
|
70
|
+
| Reactions config | YAML | YAML, sama filosofi, ditambah field provider routing |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 6. Arsitektur Plugin (7 Slot)
|
|
75
|
+
|
|
76
|
+
Mengikuti pola AO, core hanya pegang lifecycle & event bus. Semua kapabilitas eksternal lewat plugin yang implement TypeScript interface.
|
|
77
|
+
|
|
78
|
+
| Slot | Tanggung Jawab | Implementasi v1 | Implementasi v2+ |
|
|
79
|
+
| ------------- | -------------------------------------------- | -------------------------------------------- | ----------------------------------------------------- |
|
|
80
|
+
| **Runtime** | Tempat proses agent jalan (terminal session) | tmux (macOS/Linux), process/ConPTY (Windows) | docker |
|
|
81
|
+
| **Agent** | Adapter ke CLI/agent AI | opencode, claude-code, aider, codex | cursor, kimicode |
|
|
82
|
+
| **Workspace** | Strategi isolasi kode | git worktree | clone |
|
|
83
|
+
| **Tracker** | Sumber task/issue | github issues | linear, gitlab issues |
|
|
84
|
+
| **SCM** | Operasi git/PR | github (gh CLI/API) | gitlab |
|
|
85
|
+
| **Notifier** | Channel notifikasi | desktop, terminal log | slack, discord, telegram (relevan buat komunitas WPU) |
|
|
86
|
+
| **Terminal** | Tampilan live session di dashboard | web (xterm.js) | — |
|
|
87
|
+
|
|
88
|
+
**Kontrak inti:** setiap plugin mengekspor `PluginModule` yang implement interface dari `packages/core/src/types.ts` — agent baru = 1 file adapter, tidak menyentuh core sama sekali.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 7. Cara Kerja End-to-End
|
|
93
|
+
|
|
94
|
+
1. **`speexor start <repo>`** — clone/deteksi repo, generate `speexor.config.yaml` default, jalankan dashboard.
|
|
95
|
+
2. **Orchestrator agent** (agent "meta" yang juga AI) membaca issue/task dari Tracker plugin, lalu spawn **worker agent** baru per task.
|
|
96
|
+
3. Setiap worker agent dapat:
|
|
97
|
+
- Worktree git baru (`git worktree add`) dengan branch terisolasi.
|
|
98
|
+
- Sesi runtime sendiri (tmux pane / process).
|
|
99
|
+
- Provider/model yang dikonfigurasi per project (mendukung multi-provider routing seperti workflow kamu: OpenCode Go primer, DeepSeek fallback).
|
|
100
|
+
4. Worker agent kerja otonom: baca kode, tulis kode, jalankan test lokal, commit, push, buka PR via SCM plugin.
|
|
101
|
+
5. **Reaction engine** mendengarkan event dari Tracker/SCM:
|
|
102
|
+
- `ci-failed` → kirim log ke agent terkait untuk diperbaiki (dengan retry limit).
|
|
103
|
+
- `changes-requested` → forward comment reviewer ke agent.
|
|
104
|
+
- `approved-and-green` → notifikasi ke kamu (auto-merge default `false`).
|
|
105
|
+
6. **Dashboard** menampilkan semua project, status setiap agent (running/stuck/needs-review/done), log live, dan ringkasan cost/usage tiap provider.
|
|
106
|
+
7. Kamu review PR final dan merge manual (atau aktifkan auto-merge per project jika sudah percaya).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 8. Functional Requirements
|
|
111
|
+
|
|
112
|
+
### 8.1 CLI
|
|
113
|
+
|
|
114
|
+
- FR-1: `speexor start [repo-url|path]` — inisialisasi project + jalankan dashboard.
|
|
115
|
+
- FR-2: `speexor agent spawn --task <id> --agent <opencode|claude-code|aider|codex>` — manual spawn agent untuk task tertentu.
|
|
116
|
+
- FR-3: `speexor list` — daftar semua project & status agent aktif.
|
|
117
|
+
- FR-4: `speexor stop <session-id>` — hentikan agent tertentu dengan aman (cleanup worktree).
|
|
118
|
+
- FR-5: `speexor config-help` — tampilkan schema config lengkap.
|
|
119
|
+
- FR-6: `speexor logs <session-id>` — tail log sesi agent.
|
|
120
|
+
|
|
121
|
+
### 8.2 Konfigurasi (`speexor.config.yaml`)
|
|
122
|
+
|
|
123
|
+
- FR-7: Mendukung multi-project dalam satu config (seperti AO).
|
|
124
|
+
- FR-8: Mendukung definisi **provider routing per agent** (mis. OpenCode pakai Zen primary, DeepSeek fallback) — ini fitur pembeda dari AO yang tidak ada konsep multi-provider cost routing.
|
|
125
|
+
- FR-9: Reactions block (`ci-failed`, `changes-requested`, `approved-and-green`) dengan `auto`, `action`, `retries`, `escalateAfter`.
|
|
126
|
+
- FR-10: Validasi schema via JSON Schema (`$schema` reference, autocomplete di editor).
|
|
127
|
+
|
|
128
|
+
### 8.3 Plugin: Agent Adapter
|
|
129
|
+
|
|
130
|
+
- FR-11: Interface `AgentPlugin` minimal punya: `spawn()`, `sendInput()`, `getStatus()`, `kill()`.
|
|
131
|
+
- FR-12: Adapter OpenCode CLI (v1, prioritas tertinggi karena workflow eksisting kamu).
|
|
132
|
+
- FR-13: Adapter Claude Code, Aider, Codex (v1, paralel development).
|
|
133
|
+
- FR-14: Adapter ditulis sebagai package terpisah (`@speexor/agent-opencode`, dst) agar bisa diinstall independen.
|
|
134
|
+
|
|
135
|
+
### 8.4 Workspace & Runtime
|
|
136
|
+
|
|
137
|
+
- FR-15: Setiap agent task → 1 `git worktree` + 1 branch baru otomatis dengan naming convention (`speexor/<task-id>`).
|
|
138
|
+
- FR-16: Cleanup otomatis worktree saat PR merged/closed atau session di-stop.
|
|
139
|
+
- FR-17: Runtime tmux session per agent, dengan fallback `process`/ConPTY untuk Windows.
|
|
140
|
+
|
|
141
|
+
### 8.5 Tracker & SCM (GitHub-first)
|
|
142
|
+
|
|
143
|
+
- FR-18: Plugin GitHub: baca issues, buka PR, baca review comments, baca status CI via GitHub Actions API.
|
|
144
|
+
- FR-19: Interface `TrackerPlugin` & `SCMPlugin` didesain generik dari awal (meski hanya GitHub yang diimplementasi penuh di v1) supaya GitLab/Gitea/Bitbucket bisa ditambah tanpa ubah core.
|
|
145
|
+
- FR-20: Autentikasi via `gh` CLI (reuse kredensial yang sudah ada) atau GitHub App token.
|
|
146
|
+
|
|
147
|
+
### 8.6 Dashboard Web
|
|
148
|
+
|
|
149
|
+
- FR-21: Live view semua project & agent (status: idle/running/stuck/needs-review/done/error).
|
|
150
|
+
- FR-22: Live terminal stream per agent session (xterm.js + websocket).
|
|
151
|
+
- FR-23: Panel cost/usage tracking per provider per project (mengikuti kebutuhan kamu mengevaluasi cost OpenCode Go vs DeepSeek vs GLM).
|
|
152
|
+
- FR-24: Halaman konfigurasi reaction & project tanpa edit YAML manual (opsional, nice-to-have v1.5).
|
|
153
|
+
|
|
154
|
+
### 8.7 Notifier
|
|
155
|
+
|
|
156
|
+
- FR-25: Desktop notification (native) saat `approved-and-green` atau agent stuck >X menit.
|
|
157
|
+
- FR-26: Notifier plugin interface terbuka untuk Slack/Discord/Telegram di v2.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 9. Non-Functional Requirements
|
|
162
|
+
|
|
163
|
+
| Kategori | Requirement |
|
|
164
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| **Isolasi** | Tidak boleh ada dua agent menulis ke worktree yang sama secara bersamaan. |
|
|
166
|
+
| **Keamanan** | Token GitHub/API key disimpan terenkripsi lokal (mis. OS keychain), tidak pernah di-log plaintext. |
|
|
167
|
+
| **Observability** | Semua aksi agent (commit, push, command CI fix) tercatat di log per session, queryable lewat `speexor logs`. |
|
|
168
|
+
| **Resilience** | Crash satu agent tidak boleh mematikan orchestrator atau agent lain. |
|
|
169
|
+
| **Performa** | Dashboard tetap responsif dengan ≥10 agent aktif paralel. |
|
|
170
|
+
| **Portabilitas** | Berjalan di Linux, macOS, dan Windows (mengikuti precedent AO: tmux di Unix, ConPTY di Windows). |
|
|
171
|
+
| **Extensibility** | Plugin baru (agent/tracker/SCM baru) tidak boleh butuh ubah kode core/package lain. |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 10. Tech Stack Usulan
|
|
176
|
+
|
|
177
|
+
Selaras dengan stack kamu yang sudah jalan:
|
|
178
|
+
|
|
179
|
+
- **Bahasa:** TypeScript, monorepo dengan `pnpm` + `pnpm-workspace.yaml`.
|
|
180
|
+
- **CLI framework:** `commander` atau `yargs`.
|
|
181
|
+
- **Dashboard backend:** NestJS (sesuai stack kamu) sebagai API + WebSocket gateway untuk live terminal & status.
|
|
182
|
+
- **Dashboard frontend:** Next.js (sesuai stack kamu).
|
|
183
|
+
- **Git operasi:** `simple-git` atau shell-out ke `git worktree` langsung.
|
|
184
|
+
- **Runtime tmux control:** `node-pty` untuk spawn process/pty.
|
|
185
|
+
- **Skema config:** JSON Schema + `ajv` untuk validasi `speexor.config.yaml`.
|
|
186
|
+
- **Storage state lokal:** SQLite (via Prisma, konsisten dengan stack kamu) untuk session/agent state, bukan hanya in-memory.
|
|
187
|
+
- **Testing:** Vitest/Jest + integration test untuk plugin contract.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 11. Arsitektur Repo (Monorepo Layout Usulan)
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
speexor/
|
|
195
|
+
├── packages/
|
|
196
|
+
│ ├── core/ # lifecycle, event bus, plugin contracts
|
|
197
|
+
│ ├── cli/ # speexor CLI
|
|
198
|
+
│ ├── dashboard-api/ # NestJS backend
|
|
199
|
+
│ ├── dashboard-web/ # Next.js frontend
|
|
200
|
+
│ ├── agent-opencode/ # adapter plugin
|
|
201
|
+
│ ├── agent-claude-code/
|
|
202
|
+
│ ├── agent-aider/
|
|
203
|
+
│ ├── agent-codex/
|
|
204
|
+
│ ├── tracker-github/
|
|
205
|
+
│ ├── scm-github/
|
|
206
|
+
│ ├── runtime-tmux/
|
|
207
|
+
│ ├── runtime-process/
|
|
208
|
+
│ └── notifier-desktop/
|
|
209
|
+
├── schema/
|
|
210
|
+
│ └── config.schema.json
|
|
211
|
+
├── examples/
|
|
212
|
+
└── speexor.config.yaml.example
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 12. Roadmap & Milestone
|
|
218
|
+
|
|
219
|
+
| Fase | Scope | Estimasi |
|
|
220
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
|
|
221
|
+
| **M0 — Foundation** | Setup monorepo, core lifecycle, plugin contract types, CLI skeleton | 1-2 minggu |
|
|
222
|
+
| **M1 — Single Agent E2E** | Adapter OpenCode + runtime tmux + workspace worktree, jalan end-to-end untuk 1 agent 1 task lokal (tanpa tracker dulu) | 2 minggu |
|
|
223
|
+
| **M2 — GitHub Integration** | Plugin tracker-github + scm-github, reaction engine (`ci-failed`, `changes-requested`) | 2 minggu |
|
|
224
|
+
| **M3 — Multi-Agent Adapter** | Tambah Claude Code, Aider, Codex adapter | 1-2 minggu |
|
|
225
|
+
| **M4 — Dashboard MVP** | Dashboard web: list project, status agent, live terminal stream | 2-3 minggu |
|
|
226
|
+
| **M5 — Cost/Provider Panel** | Multi-provider routing config + cost tracking panel | 1 minggu |
|
|
227
|
+
| **M6 — Polish & Docs** | SETUP.md, troubleshooting, contoh config, open-source readiness (jika mau publish) | 1 minggu |
|
|
228
|
+
|
|
229
|
+
Total estimasi solo dev (paralel pakai vibe coding sendiri): **~10-13 minggu** kerja efektif.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 13. Risiko & Mitigasi
|
|
234
|
+
|
|
235
|
+
| Risiko | Dampak | Mitigasi |
|
|
236
|
+
| --------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------- |
|
|
237
|
+
| Agent menulis file di worktree salah / corrupt state | Tinggi | Worktree isolasi ketat + cleanup otomatis + lock file per session |
|
|
238
|
+
| Biaya API multi-provider membludak karena banyak agent paralel | Sedang | Panel cost tracking + limit concurrent agent per provider di config |
|
|
239
|
+
| Adapter OpenCode CLI berubah breaking (CLI flag/API berubah) | Sedang | Pin versi OpenCode CLI yang didukung + adapter test contract |
|
|
240
|
+
| Kompleksitas plugin architecture bikin over-engineering untuk solo use-case | Sedang | Mulai dari M1 (single agent, tanpa dashboard) supaya value cepat terasa sebelum lanjut ke fitur besar |
|
|
241
|
+
| Auto-merge salah konfigurasi → PR jelek ke-merge otomatis | Tinggi | Default `auto: false` untuk `approved-and-green`, butuh opt-in eksplisit |
|
|
242
|
+
| Windows ConPTY kompatibilitas | Rendah-Sedang | Fallback ke `process` runtime, dokumentasi jelas di TROUBLESHOOTING.md |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 14. Metrik Sukses Pasca-Launch (untuk diri sendiri)
|
|
247
|
+
|
|
248
|
+
- Bisa menjalankan ≥3 agent paralel di ≥2 project berbeda (mis. BrainClash + Kata Netizen) tanpa konflik.
|
|
249
|
+
- Waktu dari "buka issue" sampai "PR siap review" turun signifikan dibanding workflow manual saat ini.
|
|
250
|
+
- Tidak perlu lagi copy-paste log CI manual ke agent — reaction engine handle otomatis.
|
|
251
|
+
- Dashboard jadi satu-satunya tempat kamu cek progres semua project aktif.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 15. Open Questions (untuk didiskusikan sebelum mulai coding)
|
|
256
|
+
|
|
257
|
+
1. Apakah `speexor` akan jadi tool privat untuk dipakai sendiri dulu, atau langsung disiapkan untuk open-source/publish ke npm seperti AO?
|
|
258
|
+
2. Apakah orchestrator agent (meta-agent yang spawn worker) juga akan pakai OpenCode, atau agent terpisah/lebih ringan untuk task planning saja?
|
|
259
|
+
3. Apakah perlu integrasi langsung ke skill/agent system yang sudah kamu bangun di CEO Orchestrator prompt system (67+ agents, 261+ skills)? Ini bisa jadi diferensiasi besar dibanding AO.
|
|
260
|
+
4. Berapa banyak project yang realistis dijalankan paralel di mesin development kamu (resource constraint lokal)?
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
_Dokumen ini adalah draft brief — siap dipecah jadi task-task implementasi per milestone begitu scope di atas dikonfirmasi._
|