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.
Files changed (48) hide show
  1. package/API-REFERENCE.md +201 -0
  2. package/ARCHITECTURE.md +548 -0
  3. package/CHANGELOG.md +52 -0
  4. package/CODE-OF-CONDUCT.md +83 -0
  5. package/CONTRIBUTING.md +98 -0
  6. package/FAQ.md +105 -0
  7. package/LICENSE.md +21 -0
  8. package/PUBLISH.md +77 -0
  9. package/README.md +179 -0
  10. package/REFACTOR-LOG.md +40 -0
  11. package/ROADMAP.md +78 -0
  12. package/SECURITY.md +79 -0
  13. package/SUMMARY.md +46 -0
  14. package/TESTING.md +140 -0
  15. package/dist/agent-5D3BVWNK.js +37 -0
  16. package/dist/agent-5D3BVWNK.js.map +1 -0
  17. package/dist/chunk-2F66BZYJ.js +212 -0
  18. package/dist/chunk-2F66BZYJ.js.map +1 -0
  19. package/dist/chunk-5NA2TFPG.js +3 -0
  20. package/dist/chunk-5NA2TFPG.js.map +1 -0
  21. package/dist/chunk-B7WLHC4W.js +666 -0
  22. package/dist/chunk-B7WLHC4W.js.map +1 -0
  23. package/dist/chunk-SXALZEOJ.js +345 -0
  24. package/dist/chunk-SXALZEOJ.js.map +1 -0
  25. package/dist/cli/index.d.ts +1 -0
  26. package/dist/cli/index.js +287 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/core/index.d.ts +31 -0
  29. package/dist/core/index.js +4 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/index.d.ts +75 -0
  32. package/dist/index.js +205 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/plugins/index.d.ts +6 -0
  35. package/dist/plugins/index.js +3 -0
  36. package/dist/plugins/index.js.map +1 -0
  37. package/dist/types-0q_okI2g.d.ts +205 -0
  38. package/docs/PRD01.md +264 -0
  39. package/docs/PRD02.md +299 -0
  40. package/docs/PRD03.md +0 -0
  41. package/docs/PRD04.md +349 -0
  42. package/docs/PRD05.md +312 -0
  43. package/docs/SETUP.md +94 -0
  44. package/docs/TROUBLESHOOTING.md +113 -0
  45. package/examples/basic.yaml +61 -0
  46. package/package.json +102 -0
  47. package/schema/config.schema.json +119 -0
  48. package/speexor.config.yaml.example +30 -0
package/SUMMARY.md ADDED
@@ -0,0 +1,46 @@
1
+ # Speexor — Project Summary
2
+
3
+ > Agent Orchestrator for multi-AI coding agent orchestration across repositories.
4
+
5
+ ## Project Status
6
+ - **Version:** 0.1.0 (pre-release)
7
+ - **License:** MIT
8
+ - **Language:** TypeScript (ESM, ES2022)
9
+ - **Node.js:** >= 18.0.0
10
+
11
+ ## What It Does
12
+ Speexor is an orchestration layer that spawns and manages multiple AI coding agents in parallel across one or more git repositories. Each agent runs in an isolated git worktree with its own runtime session, handles one task autonomously, and can automatically respond to CI failures and PR review comments.
13
+
14
+ ## Scope
15
+ - **Source files:** 31 TypeScript files (~2,430 lines)
16
+ - **Plugins:** 10 implementations across 6 plugin slots
17
+ - **CLI commands:** 6
18
+ - **AI adapters:** OpenCode, Claude Code, Aider, Codex
19
+ - **Runtime backends:** tmux (Unix), Process (Windows)
20
+
21
+ ## Architecture Highlights
22
+ - Plugin-based: all capabilities are extensions of 7 defined slots
23
+ - Agent-agnostic: no vendor lock to a single AI provider
24
+ - Git-provider agnostic: GitHub as first implementation, interface open for GitLab/Gitea
25
+ - Dashboard built-in: no external dependencies for monitoring
26
+
27
+ ## Milestones
28
+
29
+ | Milestone | Description | Status |
30
+ |-----------|-------------|--------|
31
+ | M0 — Foundation | Monorepo, core types, CLI skeleton, plugin contracts | ✅ Done |
32
+ | M1 — Single Agent E2E | OpenCode adapter + tmux + worktree, E2E flow | ✅ Done |
33
+ | M2 — GitHub Integration | Tracker + SCM + reaction engine | ✅ Done |
34
+ | M3 — Multi-Agent | Claude Code, Aider, Codex adapters | ✅ Done |
35
+ | M4 — Dashboard MVP | REST API + HTML dashboard | ✅ Done |
36
+ | M5 — Cost/Provider | Multi-provider routing config | 🔄 In Progress |
37
+ | M6 — Polish & Docs | Documentation, examples, open-source readiness | 🔄 In Progress |
38
+
39
+ ## Quick Links
40
+ - [README](./README.md)
41
+ - [Architecture](./ARCHITECTURE.md)
42
+ - [Changelog](./CHANGELOG.md)
43
+ - [Roadmap](./ROADMAP.md)
44
+ - [Contributing](./CONTRIBUTING.md)
45
+ - [Security](./SECURITY.md)
46
+ - [Testing](./TESTING.md)
package/TESTING.md ADDED
@@ -0,0 +1,140 @@
1
+ # Testing Guide
2
+
3
+ > Testing strategy and guidelines for Speexor — Agent Orchestrator.
4
+
5
+ ## Test Framework
6
+
7
+ Speexor uses [Vitest](https://vitest.dev/) as the test framework with `@vitest/coverage-v8` for coverage reporting.
8
+
9
+ ```bash
10
+ # Run all tests
11
+ pnpm test
12
+
13
+ # Watch mode
14
+ pnpm test:watch
15
+
16
+ # With coverage
17
+ pnpm test:coverage
18
+ ```
19
+
20
+ ## Test Categories
21
+
22
+ ### Unit Tests
23
+ Test individual modules in isolation:
24
+ - **Core types** — Verify interface contracts and type guards
25
+ - **Config validation** — Test Zod schemas with valid/invalid YAML
26
+ - **Event bus** — Test emit/on/off/once behavior
27
+ - **Plugin contracts** — Verify plugins implement required interfaces
28
+
29
+ ### Integration Tests
30
+ Test module interactions:
31
+ - **Lifecycle + plugins** — Verify plugin registration, agent spawn flow
32
+ - **Config + lifecycle** — Verify config loading → lifecycle initialization
33
+ - **Dashboard state** — Verify state mutations reflect correctly
34
+ - **Session store** — Verify persistence round-trip
35
+
36
+ ### Plugin Tests
37
+ Each plugin should be tested against its interface contract:
38
+ - **Agent plugins** — Test spawn/sendInput/getStatus/kill lifecycle
39
+ - **Runtime plugins** — Test createSession/destroySession flow
40
+ - **Workspace plugin** — Test worktree lifecycle with mock git repo
41
+ - **Tracker/SCM** — Test API calls (mock gh CLI)
42
+ - **Notifier** — Test notification dispatch
43
+
44
+ ### E2E Tests (Future)
45
+ Full end-to-end tests with real agent CLIs:
46
+ - Requires installed agent CLI (opencode, claude-code, etc.)
47
+ - Requires GitHub CLI authentication
48
+ - Creates real worktrees and branches (clean up after)
49
+
50
+ ## Coverage Requirements
51
+
52
+ | Category | Target |
53
+ |----------|--------|
54
+ | Core types & config | ≥90% |
55
+ | CLI commands | ≥80% |
56
+ | Plugin loader | ≥90% |
57
+ | Agent adapters | ≥75% |
58
+ | Runtime adapters | ≥80% |
59
+ | Dashboard | ≥70% |
60
+ | Reaction engine | ≥85% |
61
+ | Session store | ≥90% |
62
+
63
+ ## Test Structure
64
+
65
+ ```
66
+ tests/
67
+ ├── unit/
68
+ │ ├── core/
69
+ │ │ ├── config.test.ts
70
+ │ │ ├── event-bus.test.ts
71
+ │ │ └── lifecycle.test.ts
72
+ │ ├── plugins/
73
+ │ │ ├── opencode.test.ts
74
+ │ │ ├── tmux.test.ts
75
+ │ │ └── git-worktree.test.ts
76
+ │ └── cli/
77
+ │ └── commands.test.ts
78
+ ├── integration/
79
+ │ ├── lifecycle-plugins.test.ts
80
+ │ └── config-lifecycle.test.ts
81
+ └── fixtures/
82
+ ├── valid-config.yaml
83
+ ├── invalid-config.yaml
84
+ └── mock-gh-responses/
85
+ ```
86
+
87
+ ## Writing Tests
88
+
89
+ ### Vitest Setup
90
+ ```typescript
91
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
92
+ ```
93
+
94
+ ### Example: Testing Config Validation
95
+ ```typescript
96
+ import { validateConfig } from '../src/core/config.js'
97
+
98
+ describe('Config Validation', () => {
99
+ it('accepts valid minimal config', () => {
100
+ const config = {
101
+ version: '1',
102
+ projects: [
103
+ {
104
+ name: 'test',
105
+ repository: 'https://github.com/user/repo',
106
+ provider: { primary: 'opencode' },
107
+ },
108
+ ],
109
+ }
110
+ expect(() => validateConfig(config)).not.toThrow()
111
+ })
112
+
113
+ it('rejects config without version', () => {
114
+ expect(() => validateConfig({ projects: [] })).toThrow()
115
+ })
116
+ })
117
+ ```
118
+
119
+ ### Example: Testing Plugin Contract
120
+ ```typescript
121
+ import { OpenCodeAgent } from '../src/plugins/agent/opencode.js'
122
+ import { createEventBus } from '../src/core/event-bus.js'
123
+
124
+ describe('OpenCodeAgent', () => {
125
+ it('implements AgentPlugin interface', () => {
126
+ const agent = new OpenCodeAgent()
127
+ expect(agent.name).toBe('opencode-agent')
128
+ expect(agent.type).toBe('agent')
129
+ expect(agent.spawn).toBeInstanceOf(Function)
130
+ expect(agent.kill).toBeInstanceOf(Function)
131
+ })
132
+ })
133
+ ```
134
+
135
+ ## Mocking Guidelines
136
+
137
+ - **gh CLI** — Use `vi.mock('node:child_process')` to mock execSync
138
+ - **tmux** — Mock process execution, don't require tmux installation
139
+ - **File system** — Use `vi.mock('node:fs')` for config/store tests
140
+ - **Event bus** — Use real EventBus (not mocked) for integration tests
@@ -0,0 +1,37 @@
1
+ import { loadConfig, SpeexorLifecycle } from './chunk-2F66BZYJ.js';
2
+ import { loadAllPlugins } from './chunk-B7WLHC4W.js';
3
+ import Debug from 'debug';
4
+
5
+ var debug = Debug("speexor:agent");
6
+ async function agentSpawnCommand(options) {
7
+ const config = loadConfig();
8
+ const lifecycle = new SpeexorLifecycle(config);
9
+ await lifecycle.initialize();
10
+ const plugins = loadAllPlugins();
11
+ for (const plugin of plugins) {
12
+ lifecycle.registerPlugin(plugin);
13
+ }
14
+ const project = config.projects[0];
15
+ if (!project) {
16
+ throw new Error("No project configured");
17
+ }
18
+ const task = {
19
+ id: options.task,
20
+ title: options.task,
21
+ description: `Task ${options.task}`,
22
+ repository: project.repository,
23
+ branch: `speexor/${options.task}`,
24
+ provider: options.agent || project.provider.primary
25
+ };
26
+ debug(`Spawning agent for task ${task.id} using ${task.provider}`);
27
+ const session = await lifecycle.spawnAgent(task);
28
+ console.log(`
29
+ \u2705 Agent spawned: ${session.id}`);
30
+ console.log(` \u{1F916} Provider: ${session.provider}`);
31
+ console.log(` \u{1F4CB} Task: ${task.id}
32
+ `);
33
+ }
34
+
35
+ export { agentSpawnCommand };
36
+ //# sourceMappingURL=agent-5D3BVWNK.js.map
37
+ //# sourceMappingURL=agent-5D3BVWNK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/agent.ts"],"names":[],"mappings":";;;;AAMA,IAAM,KAAA,GAAQ,MAAM,eAAe,CAAA;AAOnC,eAAsB,kBAAkB,OAAA,EAAuB;AAC7D,EAAA,MAAM,SAAS,UAAA,EAAW;AAC1B,EAAA,MAAM,SAAA,GAAY,IAAI,gBAAA,CAAiB,MAAM,CAAA;AAC7C,EAAA,MAAM,UAAU,UAAA,EAAW;AAE3B,EAAA,MAAM,UAAU,cAAA,EAAe;AAC/B,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,SAAA,CAAU,eAAe,MAAM,CAAA;AAAA,EACjC;AAEA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA;AACjC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,uBAAuB,CAAA;AAAA,EACzC;AAEA,EAAA,MAAM,IAAA,GAAkB;AAAA,IACtB,IAAI,OAAA,CAAQ,IAAA;AAAA,IACZ,OAAO,OAAA,CAAQ,IAAA;AAAA,IACf,WAAA,EAAa,CAAA,KAAA,EAAQ,OAAA,CAAQ,IAAI,CAAA,CAAA;AAAA,IACjC,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,MAAA,EAAQ,CAAA,QAAA,EAAW,OAAA,CAAQ,IAAI,CAAA,CAAA;AAAA,IAC/B,QAAA,EAAW,OAAA,CAAQ,KAAA,IAA2B,OAAA,CAAQ,QAAA,CAAS;AAAA,GACjE;AAEA,EAAA,KAAA,CAAM,2BAA2B,IAAA,CAAK,EAAE,CAAA,OAAA,EAAU,IAAA,CAAK,QAAQ,CAAA,CAAE,CAAA;AAEjE,EAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,UAAA,CAAW,IAAI,CAAA;AAC/C,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,wBAAA,EAAwB,OAAA,CAAQ,EAAE,CAAA,CAAE,CAAA;AAChD,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sBAAA,EAAkB,OAAA,CAAQ,QAAQ,CAAA,CAAE,CAAA;AAChD,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,kBAAA,EAAc,IAAA,CAAK,EAAE;AAAA,CAAI,CAAA;AACvC","file":"agent-5D3BVWNK.js","sourcesContent":["import { loadConfig } from '../core/config.js'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport { loadAllPlugins } from '../plugins/index.js'\nimport type { AgentTask, AgentProvider } from '../core/types.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:agent')\n\ninterface SpawnOptions {\n task: string\n agent: string\n}\n\nexport async function agentSpawnCommand(options: SpawnOptions) {\n const config = loadConfig()\n const lifecycle = new SpeexorLifecycle(config)\n await lifecycle.initialize()\n\n const plugins = loadAllPlugins()\n for (const plugin of plugins) {\n lifecycle.registerPlugin(plugin)\n }\n\n const project = config.projects[0]\n if (!project) {\n throw new Error('No project configured')\n }\n\n const task: AgentTask = {\n id: options.task,\n title: options.task,\n description: `Task ${options.task}`,\n repository: project.repository,\n branch: `speexor/${options.task}`,\n provider: (options.agent as AgentProvider) || project.provider.primary,\n }\n\n debug(`Spawning agent for task ${task.id} using ${task.provider}`)\n\n const session = await lifecycle.spawnAgent(task)\n console.log(`\\n ✅ Agent spawned: ${session.id}`)\n console.log(` 🤖 Provider: ${session.provider}`)\n console.log(` 📋 Task: ${task.id}\\n`)\n}\n"]}
@@ -0,0 +1,212 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { parse } from 'yaml';
5
+ import { z } from 'zod';
6
+ import Debug from 'debug';
7
+
8
+ // src/core/event-bus.ts
9
+ function createEventBus() {
10
+ const emitter = new EventEmitter();
11
+ return {
12
+ emit(event, data) {
13
+ emitter.emit(event, data);
14
+ },
15
+ on(event, handler) {
16
+ emitter.on(event, handler);
17
+ },
18
+ off(event, handler) {
19
+ emitter.off(event, handler);
20
+ },
21
+ once(event, handler) {
22
+ emitter.once(event, handler);
23
+ }
24
+ };
25
+ }
26
+ var reactionRuleSchema = z.object({
27
+ auto: z.boolean(),
28
+ action: z.enum(["fix", "notify", "escalate", "skip"]),
29
+ retries: z.number().int().min(0).max(10),
30
+ escalateAfter: z.number().int().min(1).max(1440)
31
+ });
32
+ var providerRoutingSchema = z.object({
33
+ primary: z.enum(["opencode", "claude-code", "aider", "codex"]),
34
+ fallback: z.array(z.enum(["opencode", "claude-code", "aider", "codex"])).optional(),
35
+ concurrentLimit: z.number().int().min(1).max(20).optional(),
36
+ costLimit: z.number().int().optional()
37
+ });
38
+ var projectSchema = z.object({
39
+ name: z.string().min(1),
40
+ repository: z.string().min(1),
41
+ path: z.string().optional(),
42
+ branch: z.string().optional(),
43
+ provider: providerRoutingSchema,
44
+ reactions: z.object({
45
+ "ci-failed": reactionRuleSchema.optional(),
46
+ "changes-requested": reactionRuleSchema.optional(),
47
+ "approved-and-green": reactionRuleSchema.optional()
48
+ }).optional(),
49
+ plugins: z.object({
50
+ tracker: z.string().optional(),
51
+ scm: z.string().optional(),
52
+ runtime: z.string().optional(),
53
+ notifier: z.string().optional()
54
+ }).optional()
55
+ });
56
+ var configSchema = z.object({
57
+ version: z.literal("1"),
58
+ projects: z.array(projectSchema).min(1)
59
+ });
60
+ var DEFAULT_REACTION_RULES = {
61
+ "ci-failed": { auto: true, action: "fix", retries: 3, escalateAfter: 30 },
62
+ "changes-requested": { auto: true, action: "fix", retries: 2, escalateAfter: 60 },
63
+ "approved-and-green": { auto: false, action: "notify", retries: 0, escalateAfter: 0 }
64
+ };
65
+ function loadConfig(cwd) {
66
+ const dir = cwd ?? process.cwd();
67
+ const candidates = [
68
+ join(dir, "speexor.config.yaml"),
69
+ join(dir, "speexor.config.yml"),
70
+ join(dir, ".speexor.yaml"),
71
+ join(dir, ".speexor.yml"),
72
+ join(dir, "speexor.yaml")
73
+ ];
74
+ let configPath;
75
+ for (const candidate of candidates) {
76
+ if (existsSync(candidate)) {
77
+ configPath = candidate;
78
+ break;
79
+ }
80
+ }
81
+ if (!configPath) {
82
+ throw new Error(`speexor.config.yaml not found in ${dir}. Run \`speexor start <repo>\` to initialize.`);
83
+ }
84
+ const raw = readFileSync(configPath, "utf-8");
85
+ const parsed = parse(raw);
86
+ return validateConfig(parsed);
87
+ }
88
+ function validateConfig(raw) {
89
+ const result = configSchema.safeParse(raw);
90
+ if (!result.success) {
91
+ const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
92
+ throw new Error(`Invalid speexor config:
93
+ ${issues}`);
94
+ }
95
+ for (const project of result.data.projects) {
96
+ if (!project.reactions) {
97
+ project.reactions = { ...DEFAULT_REACTION_RULES };
98
+ } else {
99
+ project.reactions = {
100
+ "ci-failed": project.reactions["ci-failed"] ?? DEFAULT_REACTION_RULES["ci-failed"],
101
+ "changes-requested": project.reactions["changes-requested"] ?? DEFAULT_REACTION_RULES["changes-requested"],
102
+ "approved-and-green": project.reactions["approved-and-green"] ?? DEFAULT_REACTION_RULES["approved-and-green"]
103
+ };
104
+ }
105
+ }
106
+ return result.data;
107
+ }
108
+ function generateDefaultConfig(repoUrl, projectName) {
109
+ const name = projectName ?? repoUrl.split("/").pop()?.replace(".git", "") ?? "my-project";
110
+ return {
111
+ version: "1",
112
+ projects: [
113
+ {
114
+ name,
115
+ repository: repoUrl,
116
+ provider: {
117
+ primary: "opencode"
118
+ },
119
+ reactions: { ...DEFAULT_REACTION_RULES }
120
+ }
121
+ ]
122
+ };
123
+ }
124
+ var debug = Debug("speexor:lifecycle");
125
+ var SpeexorLifecycle = class {
126
+ config;
127
+ eventBus;
128
+ plugins = /* @__PURE__ */ new Map();
129
+ sessions = /* @__PURE__ */ new Map();
130
+ status = "initializing";
131
+ constructor(config) {
132
+ this.config = config;
133
+ this.eventBus = createEventBus();
134
+ }
135
+ async initialize() {
136
+ debug("Initializing Speexor lifecycle");
137
+ this.status = "active";
138
+ this.eventBus.emit("lifecycle:initialized", { timestamp: /* @__PURE__ */ new Date() });
139
+ }
140
+ registerPlugin(plugin) {
141
+ const existing = this.plugins.get(plugin.type) ?? [];
142
+ existing.push(plugin);
143
+ this.plugins.set(plugin.type, existing);
144
+ debug(`Registered plugin: ${plugin.name} (${plugin.type})`);
145
+ }
146
+ getPlugins(slot) {
147
+ return this.plugins.get(slot) ?? [];
148
+ }
149
+ getFirstPlugin(slot) {
150
+ return this.getPlugins(slot)[0];
151
+ }
152
+ async spawnAgent(task) {
153
+ const agentPlugin = this.getFirstPlugin("agent");
154
+ if (!agentPlugin) throw new Error("No agent plugin registered");
155
+ const workspacePlugin = this.getFirstPlugin("workspace");
156
+ if (!workspacePlugin) throw new Error("No workspace plugin registered");
157
+ const runtimePlugin = this.getFirstPlugin("runtime");
158
+ if (!runtimePlugin) throw new Error("No runtime plugin registered");
159
+ const worktree = await workspacePlugin.createWorktree(task);
160
+ this.eventBus.emit("worktree:created", { taskId: task.id, path: worktree.path });
161
+ const runtimeSession = await runtimePlugin.createSession(worktree.path);
162
+ const session = await agentPlugin.spawn(task, runtimeSession);
163
+ this.sessions.set(session.id, session);
164
+ this.eventBus.emit("session:created", { session, task });
165
+ debug(`Agent spawned: ${session.id} for task ${task.id}`);
166
+ return session;
167
+ }
168
+ async stopSession(sessionId) {
169
+ const session = this.sessions.get(sessionId);
170
+ if (!session) throw new Error(`Session ${sessionId} not found`);
171
+ const agentPlugin = this.getFirstPlugin("agent");
172
+ if (agentPlugin) {
173
+ await agentPlugin.kill(sessionId);
174
+ }
175
+ this.sessions.delete(sessionId);
176
+ this.eventBus.emit("session:completed", { sessionId });
177
+ debug(`Session stopped: ${sessionId}`);
178
+ }
179
+ getSession(sessionId) {
180
+ return this.sessions.get(sessionId);
181
+ }
182
+ listSessions() {
183
+ return Array.from(this.sessions.values());
184
+ }
185
+ async destroy() {
186
+ this.status = "completed";
187
+ for (const [id] of this.sessions) {
188
+ await this.stopSession(id).catch(() => {
189
+ });
190
+ }
191
+ const slots = ["terminal", "notifier", "scm", "tracker", "workspace", "runtime", "agent"];
192
+ for (const slot of slots) {
193
+ const plugins = this.plugins.get(slot) ?? [];
194
+ for (const plugin of plugins.reverse()) {
195
+ await plugin.destroy().catch(() => {
196
+ });
197
+ }
198
+ }
199
+ this.eventBus.emit("lifecycle:destroyed", { timestamp: /* @__PURE__ */ new Date() });
200
+ debug("Lifecycle destroyed");
201
+ }
202
+ getConfig() {
203
+ return this.config;
204
+ }
205
+ getStatus() {
206
+ return this.status;
207
+ }
208
+ };
209
+
210
+ export { DEFAULT_REACTION_RULES, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig };
211
+ //# sourceMappingURL=chunk-2F66BZYJ.js.map
212
+ //# sourceMappingURL=chunk-2F66BZYJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/event-bus.ts","../src/core/config.ts","../src/core/lifecycle.ts"],"names":[],"mappings":"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","file":"chunk-2F66BZYJ.js","sourcesContent":["import { EventEmitter } from 'eventemitter3'\nimport type { EventBus } from './types.js'\n\nexport function createEventBus(): EventBus {\n const emitter = new EventEmitter()\n\n return {\n emit(event, data) {\n emitter.emit(event, data)\n },\n on(event, handler) {\n emitter.on(event, handler)\n },\n off(event, handler) {\n emitter.off(event, handler)\n },\n once(event, handler) {\n emitter.once(event, handler)\n },\n }\n}\n","import { readFileSync, existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { parse } from 'yaml'\nimport { z } from 'zod'\nimport type { SpeexorConfig, ReactionConfig } from './types.js'\n\nconst reactionRuleSchema = z.object({\n auto: z.boolean(),\n action: z.enum(['fix', 'notify', 'escalate', 'skip']),\n retries: z.number().int().min(0).max(10),\n escalateAfter: z.number().int().min(1).max(1440),\n})\n\nconst providerRoutingSchema = z.object({\n primary: z.enum(['opencode', 'claude-code', 'aider', 'codex']),\n fallback: z.array(z.enum(['opencode', 'claude-code', 'aider', 'codex'])).optional(),\n concurrentLimit: z.number().int().min(1).max(20).optional(),\n costLimit: z.number().int().optional(),\n})\n\nconst projectSchema = z.object({\n name: z.string().min(1),\n repository: z.string().min(1),\n path: z.string().optional(),\n branch: z.string().optional(),\n provider: providerRoutingSchema,\n reactions: z\n .object({\n 'ci-failed': reactionRuleSchema.optional(),\n 'changes-requested': reactionRuleSchema.optional(),\n 'approved-and-green': reactionRuleSchema.optional(),\n })\n .optional(),\n plugins: z\n .object({\n tracker: z.string().optional(),\n scm: z.string().optional(),\n runtime: z.string().optional(),\n notifier: z.string().optional(),\n })\n .optional(),\n})\n\nconst configSchema = z.object({\n version: z.literal('1'),\n projects: z.array(projectSchema).min(1),\n})\n\nexport const DEFAULT_REACTION_RULES: ReactionConfig = {\n 'ci-failed': { auto: true, action: 'fix', retries: 3, escalateAfter: 30 },\n 'changes-requested': { auto: true, action: 'fix', retries: 2, escalateAfter: 60 },\n 'approved-and-green': { auto: false, action: 'notify', retries: 0, escalateAfter: 0 },\n}\n\nexport function loadConfig(cwd?: string): SpeexorConfig {\n const dir = cwd ?? process.cwd()\n\n const candidates = [\n join(dir, 'speexor.config.yaml'),\n join(dir, 'speexor.config.yml'),\n join(dir, '.speexor.yaml'),\n join(dir, '.speexor.yml'),\n join(dir, 'speexor.yaml'),\n ]\n\n let configPath: string | undefined\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n configPath = candidate\n break\n }\n }\n\n if (!configPath) {\n throw new Error(`speexor.config.yaml not found in ${dir}. ` + 'Run `speexor start <repo>` to initialize.')\n }\n\n const raw = readFileSync(configPath, 'utf-8')\n const parsed = parse(raw)\n\n return validateConfig(parsed)\n}\n\nexport function validateConfig(raw: unknown): SpeexorConfig {\n const result = configSchema.safeParse(raw)\n if (!result.success) {\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`Invalid speexor config:\\n${issues}`)\n }\n\n for (const project of result.data.projects) {\n if (!project.reactions) {\n ;(project as { reactions: ReactionConfig }).reactions = { ...DEFAULT_REACTION_RULES }\n } else {\n project.reactions = {\n 'ci-failed': project.reactions['ci-failed'] ?? DEFAULT_REACTION_RULES['ci-failed'],\n 'changes-requested': project.reactions['changes-requested'] ?? DEFAULT_REACTION_RULES['changes-requested'],\n 'approved-and-green': project.reactions['approved-and-green'] ?? DEFAULT_REACTION_RULES['approved-and-green'],\n }\n }\n }\n\n return result.data as SpeexorConfig\n}\n\nexport function generateDefaultConfig(repoUrl: string, projectName?: string): SpeexorConfig {\n const name = projectName ?? repoUrl.split('/').pop()?.replace('.git', '') ?? 'my-project'\n return {\n version: '1',\n projects: [\n {\n name,\n repository: repoUrl,\n provider: {\n primary: 'opencode',\n },\n reactions: { ...DEFAULT_REACTION_RULES },\n },\n ],\n }\n}\n","import type {\n SpeexorConfig,\n AgentPlugin,\n RuntimePlugin,\n WorkspacePlugin,\n AgentTask,\n AgentSession,\n SessionStatus,\n EventBus,\n PluginModule,\n PluginSlot,\n} from './types.js'\nimport { createEventBus } from './event-bus.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:lifecycle')\n\nexport class SpeexorLifecycle {\n private config: SpeexorConfig\n public eventBus: EventBus\n private plugins: Map<PluginSlot, PluginModule[]> = new Map()\n private sessions: Map<string, AgentSession> = new Map()\n private status: SessionStatus = 'initializing'\n\n constructor(config: SpeexorConfig) {\n this.config = config\n this.eventBus = createEventBus()\n }\n\n async initialize(): Promise<void> {\n debug('Initializing Speexor lifecycle')\n this.status = 'active'\n this.eventBus.emit('lifecycle:initialized', { timestamp: new Date() })\n }\n\n registerPlugin(plugin: PluginModule): void {\n const existing = this.plugins.get(plugin.type) ?? []\n existing.push(plugin)\n this.plugins.set(plugin.type, existing)\n debug(`Registered plugin: ${plugin.name} (${plugin.type})`)\n }\n\n getPlugins<T extends PluginModule>(slot: PluginSlot): T[] {\n return (this.plugins.get(slot) ?? []) as T[]\n }\n\n getFirstPlugin<T extends PluginModule>(slot: PluginSlot): T | undefined {\n return this.getPlugins<T>(slot)[0]\n }\n\n async spawnAgent(task: AgentTask): Promise<AgentSession> {\n const agentPlugin = this.getFirstPlugin<AgentPlugin>('agent')\n if (!agentPlugin) throw new Error('No agent plugin registered')\n\n const workspacePlugin = this.getFirstPlugin<WorkspacePlugin>('workspace')\n if (!workspacePlugin) throw new Error('No workspace plugin registered')\n\n const runtimePlugin = this.getFirstPlugin<RuntimePlugin>('runtime')\n if (!runtimePlugin) throw new Error('No runtime plugin registered')\n\n const worktree = await workspacePlugin.createWorktree(task)\n this.eventBus.emit('worktree:created', { taskId: task.id, path: worktree.path })\n\n const runtimeSession = await runtimePlugin.createSession(worktree.path)\n\n const session = await agentPlugin.spawn(task, runtimeSession)\n this.sessions.set(session.id, session)\n\n this.eventBus.emit('session:created', { session, task })\n debug(`Agent spawned: ${session.id} for task ${task.id}`)\n\n return session\n }\n\n async stopSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId)\n if (!session) throw new Error(`Session ${sessionId} not found`)\n\n const agentPlugin = this.getFirstPlugin<AgentPlugin>('agent')\n if (agentPlugin) {\n await agentPlugin.kill(sessionId)\n }\n\n this.sessions.delete(sessionId)\n this.eventBus.emit('session:completed', { sessionId })\n debug(`Session stopped: ${sessionId}`)\n }\n\n getSession(sessionId: string): AgentSession | undefined {\n return this.sessions.get(sessionId)\n }\n\n listSessions(): AgentSession[] {\n return Array.from(this.sessions.values())\n }\n\n async destroy(): Promise<void> {\n this.status = 'completed'\n\n for (const [id] of this.sessions) {\n await this.stopSession(id).catch(() => {})\n }\n\n const slots: PluginSlot[] = ['terminal', 'notifier', 'scm', 'tracker', 'workspace', 'runtime', 'agent']\n for (const slot of slots) {\n const plugins = this.plugins.get(slot) ?? []\n for (const plugin of plugins.reverse()) {\n await plugin.destroy().catch(() => {})\n }\n }\n\n this.eventBus.emit('lifecycle:destroyed', { timestamp: new Date() })\n debug('Lifecycle destroyed')\n }\n\n getConfig(): SpeexorConfig {\n return this.config\n }\n\n getStatus(): SessionStatus {\n return this.status\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=chunk-5NA2TFPG.js.map
3
+ //# sourceMappingURL=chunk-5NA2TFPG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-5NA2TFPG.js"}