speexor 0.1.0 → 0.2.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 (46) hide show
  1. package/API-REFERENCE.md +96 -1
  2. package/ARCHITECTURE.md +84 -33
  3. package/BENCHMARKS.md +52 -0
  4. package/CHANGELOG.md +35 -4
  5. package/CODE-OF-CONDUCT.md +83 -83
  6. package/CONTRIBUTING.md +98 -98
  7. package/FAQ.md +105 -105
  8. package/GLOSSARY.md +33 -0
  9. package/LICENSE.md +21 -21
  10. package/PUBLISH.md +77 -77
  11. package/README.md +222 -8
  12. package/REFACTOR-LOG.md +40 -40
  13. package/ROADMAP.md +37 -15
  14. package/SECURITY-DEFAULTS.md +118 -0
  15. package/SECURITY.md +79 -79
  16. package/SUMMARY.md +31 -8
  17. package/TESTING.md +140 -140
  18. package/dist/{agent-5D3BVWNK.js → agent-D4BRWEOZ.js} +4 -4
  19. package/dist/agent-D4BRWEOZ.js.map +1 -0
  20. package/dist/{chunk-2F66BZYJ.js → chunk-2DX54KIM.js} +2 -2
  21. package/dist/chunk-2DX54KIM.js.map +1 -0
  22. package/dist/{chunk-B7WLHC4W.js → chunk-7VZHDGRQ.js} +2 -2
  23. package/dist/chunk-7VZHDGRQ.js.map +1 -0
  24. package/dist/{chunk-SXALZEOJ.js → chunk-AOFWQZWY.js} +2 -2
  25. package/dist/chunk-AOFWQZWY.js.map +1 -0
  26. package/dist/cli/index.js +4 -4
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/core/index.js +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/index.js +1 -1
  32. package/docs/SETUP.md +94 -94
  33. package/docs/TROUBLESHOOTING.md +113 -113
  34. package/docs/adr/0001-record-architecture-decisions.md +44 -0
  35. package/docs/adr/0002-plugin-architecture.md +53 -0
  36. package/docs/adr/0003-recursive-task-decomposition.md +57 -0
  37. package/docs/adr/0004-local-first-security.md +58 -0
  38. package/docs/adr/0005-data-directory-layout.md +69 -0
  39. package/examples/basic.yaml +61 -61
  40. package/package.json +103 -102
  41. package/schema/config.schema.json +119 -119
  42. package/speexor.config.yaml.example +30 -30
  43. package/dist/agent-5D3BVWNK.js.map +0 -1
  44. package/dist/chunk-2F66BZYJ.js.map +0 -1
  45. package/dist/chunk-B7WLHC4W.js.map +0 -1
  46. package/dist/chunk-SXALZEOJ.js.map +0 -1
package/SECURITY.md CHANGED
@@ -1,79 +1,79 @@
1
- # Security Policy
2
-
3
- ## Supported Versions
4
-
5
- | Version | Supported |
6
- |---------|-----------|
7
- | 0.1.x | ✅ Active development |
8
-
9
- ## Reporting a Vulnerability
10
-
11
- Speexor handles API tokens (GitHub tokens, AI provider keys) and executes AI-generated code. Security is a top priority.
12
-
13
- **To report a vulnerability:**
14
- 1. **Do NOT** open a public GitHub issue
15
- 2. Email: opensource@superdevids.com (or use GitHub's private vulnerability reporting)
16
- 3. Include a detailed description, steps to reproduce, and potential impact
17
-
18
- You can expect:
19
- - **Acknowledgment** within 48 hours
20
- - **Initial assessment** within 5 business days
21
- - **Fix timeline** depending on severity
22
-
23
- ## Security Practices
24
-
25
- ### Credential Management
26
- - API tokens are stored in environment variables or the system keychain
27
- - Secrets are never logged in plaintext
28
- - GitHub tokens are reused from `gh` CLI when possible
29
- - `.speexor/` directory is git-ignored (add to `.gitignore`)
30
-
31
- ### Code Execution
32
- - AI agents generate and execute code in isolated git worktrees
33
- - Each agent runs in its own process/tmux session
34
- - Process runtime has resource limits (SIGTERM → SIGKILL after 5s)
35
- - Worktrees are cleaned up on session stop or package destroy
36
-
37
- ### Network Security
38
- - Dashboard server listens on localhost by default
39
- - CORS is enabled (configurable in production)
40
- - No telemetry or tracking
41
- - No cloud dependency — fully local-first
42
-
43
- ### Data Protection
44
- - Session state stored in `.speexor/state.json` (local only)
45
- - Logs stored in `.speexor/logs/` (local only)
46
- - No data sent to external servers except configured AI providers and GitHub API
47
- - Users control which API endpoints are called
48
-
49
- ### Supply Chain Security
50
- - All dependencies are pinned with exact versions
51
- - pnpm lockfile for deterministic installs
52
- - Regular dependency audits via `pnpm audit`
53
- - Minimal runtime dependencies (12 packages)
54
-
55
- ## Security Checklist for Deployments
56
-
57
- - [ ] AI provider API keys stored in environment variables (not config files)
58
- - [ ] GitHub tokens use minimal scopes (repo, issues, pull requests)
59
- - [ ] Dashboard port not exposed to public internet
60
- - [ ] `.speexor/` directory added to `.gitignore`
61
- - [ ] Regular dependency updates (`pnpm audit`)
62
- - [ ] Agent output reviewed before auto-merge (auto-merge disabled by default)
63
-
64
- ## Third-Party Security
65
-
66
- Speexor relies on:
67
- - **GitHub CLI (`gh`)** — user's existing authentication
68
- - **AI agent CLIs** — OpenCode, Claude Code, Aider, Codex — each with their own security models
69
- - **tmux** (Unix) / **Process** (Windows) — for runtime isolation
70
-
71
- Review the security documentation for each tool you use.
72
-
73
- ## Incident Response
74
-
75
- 1. **Detection** — Monitoring for unusual agent behavior or unauthorized access
76
- 2. **Containment** — `speexor stop <session>` to halt affected agents
77
- 3. **Analysis** — Review logs in `.speexor/logs/` and session state
78
- 4. **Recovery** — Rotate affected credentials, clean up worktrees
79
- 5. **Postmortem** — Document findings, update security practices
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | 0.1.x | ✅ Active development |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ Speexor handles API tokens (GitHub tokens, AI provider keys) and executes AI-generated code. Security is a top priority.
12
+
13
+ **To report a vulnerability:**
14
+ 1. **Do NOT** open a public GitHub issue
15
+ 2. Email: opensource@superdevids.com (or use GitHub's private vulnerability reporting)
16
+ 3. Include a detailed description, steps to reproduce, and potential impact
17
+
18
+ You can expect:
19
+ - **Acknowledgment** within 48 hours
20
+ - **Initial assessment** within 5 business days
21
+ - **Fix timeline** depending on severity
22
+
23
+ ## Security Practices
24
+
25
+ ### Credential Management
26
+ - API tokens are stored in environment variables or the system keychain
27
+ - Secrets are never logged in plaintext
28
+ - GitHub tokens are reused from `gh` CLI when possible
29
+ - `.speexor/` directory is git-ignored (add to `.gitignore`)
30
+
31
+ ### Code Execution
32
+ - AI agents generate and execute code in isolated git worktrees
33
+ - Each agent runs in its own process/tmux session
34
+ - Process runtime has resource limits (SIGTERM → SIGKILL after 5s)
35
+ - Worktrees are cleaned up on session stop or package destroy
36
+
37
+ ### Network Security
38
+ - Dashboard server listens on localhost by default
39
+ - CORS is enabled (configurable in production)
40
+ - No telemetry or tracking
41
+ - No cloud dependency — fully local-first
42
+
43
+ ### Data Protection
44
+ - Session state stored in `.speexor/state.json` (local only)
45
+ - Logs stored in `.speexor/logs/` (local only)
46
+ - No data sent to external servers except configured AI providers and GitHub API
47
+ - Users control which API endpoints are called
48
+
49
+ ### Supply Chain Security
50
+ - All dependencies are pinned with exact versions
51
+ - pnpm lockfile for deterministic installs
52
+ - Regular dependency audits via `pnpm audit`
53
+ - Minimal runtime dependencies (12 packages)
54
+
55
+ ## Security Checklist for Deployments
56
+
57
+ - [ ] AI provider API keys stored in environment variables (not config files)
58
+ - [ ] GitHub tokens use minimal scopes (repo, issues, pull requests)
59
+ - [ ] Dashboard port not exposed to public internet
60
+ - [ ] `.speexor/` directory added to `.gitignore`
61
+ - [ ] Regular dependency updates (`pnpm audit`)
62
+ - [ ] Agent output reviewed before auto-merge (auto-merge disabled by default)
63
+
64
+ ## Third-Party Security
65
+
66
+ Speexor relies on:
67
+ - **GitHub CLI (`gh`)** — user's existing authentication
68
+ - **AI agent CLIs** — OpenCode, Claude Code, Aider, Codex — each with their own security models
69
+ - **tmux** (Unix) / **Process** (Windows) — for runtime isolation
70
+
71
+ Review the security documentation for each tool you use.
72
+
73
+ ## Incident Response
74
+
75
+ 1. **Detection** — Monitoring for unusual agent behavior or unauthorized access
76
+ 2. **Containment** — `speexor stop <session>` to halt affected agents
77
+ 3. **Analysis** — Review logs in `.speexor/logs/` and session state
78
+ 4. **Recovery** — Rotate affected credentials, clean up worktrees
79
+ 5. **Postmortem** — Document findings, update security practices
package/SUMMARY.md CHANGED
@@ -3,19 +3,20 @@
3
3
  > Agent Orchestrator for multi-AI coding agent orchestration across repositories.
4
4
 
5
5
  ## Project Status
6
- - **Version:** 0.1.0 (pre-release)
6
+ - **Version:** 0.2.0 (pre-release)
7
7
  - **License:** MIT
8
8
  - **Language:** TypeScript (ESM, ES2022)
9
9
  - **Node.js:** >= 18.0.0
10
+ - **Test files:** 19 files, ~320 tests
10
11
 
11
12
  ## What It Does
12
13
  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
 
14
15
  ## 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
16
+ - **Source files:** 60+ TypeScript files (~5,200+ lines)
17
+ - **Plugins:** 13+ implementations across 7 plugin slots
18
+ - **CLI commands:** 14 (6 original + 8 new)
19
+ - **AI adapters:** OpenCode, Claude Code, Aider, Codex (all with exponential retry)
19
20
  - **Runtime backends:** tmux (Unix), Process (Windows)
20
21
 
21
22
  ## Architecture Highlights
@@ -23,6 +24,13 @@ Speexor is an orchestration layer that spawns and manages multiple AI coding age
23
24
  - Agent-agnostic: no vendor lock to a single AI provider
24
25
  - Git-provider agnostic: GitHub as first implementation, interface open for GitLab/Gitea
25
26
  - Dashboard built-in: no external dependencies for monitoring
27
+ - Recursive Task Decomposition (DAG-based planner)
28
+ - Governance Engine (two-axis approval model)
29
+ - Cost Tracking (per-provider/project/node with budget guard)
30
+ - Extension Manager + Plugin SDK
31
+ - Secrets Vault (OS-keychain backend)
32
+ - Decision Quality Evaluation & Calibration
33
+ - Interactive Dashboard v2 (Task Tree, Fleet, Approvals, Cost)
26
34
 
27
35
  ## Milestones
28
36
 
@@ -31,16 +39,31 @@ Speexor is an orchestration layer that spawns and manages multiple AI coding age
31
39
  | M0 — Foundation | Monorepo, core types, CLI skeleton, plugin contracts | ✅ Done |
32
40
  | M1 — Single Agent E2E | OpenCode adapter + tmux + worktree, E2E flow | ✅ Done |
33
41
  | M2 — GitHub Integration | Tracker + SCM + reaction engine | ✅ Done |
34
- | M3 — Multi-Agent | Claude Code, Aider, Codex adapters | ✅ Done |
42
+ | M3 — Multi-Agent Adapters | Claude Code, Aider, Codex adapters | ✅ Done |
35
43
  | 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 |
44
+ | M5 — Cost Tracking & Budget Guard | Multi-provider routing config | Done |
45
+ | M6 — Polish & Testing | Documentation, examples, open-source readiness | Done |
46
+ | M7 — Task Graph Core & Decomposition | DAG planner, recursive decomposition | ✅ Done |
47
+ | M8 — Parallel Scheduler | Resource-aware parallel execution | ✅ Done |
48
+ | M9 — Governance & Approval | Two-axis approval model, risk policy | ✅ Done |
49
+ | M10 — Extension Manager & SDK | Marketplace, manifest, permissions | ✅ Done |
50
+ | M11 — Security & Secrets Vault | OS-keychain credential storage | ✅ Done |
51
+ | M12 — Interactive Dashboard v2 | Task Tree, Fleet, Approvals, Cost panels | ✅ Done |
52
+ | M22a — Critical Fixes (v5) | Edge case hardening, crash fixes | ✅ Done |
53
+ | M22b — Major Fixes (v5) | Retry logic, race condition fixes | ✅ Done |
54
+ | M22c — Documentation Consolidation | Cross-reference, glossary, API ref | ✅ Done |
55
+ | M13 — Hardening & Cost Guard | Budget enforcement, guardrails | 🔄 In Progress |
38
56
 
39
57
  ## Quick Links
40
58
  - [README](./README.md)
41
59
  - [Architecture](./ARCHITECTURE.md)
42
60
  - [Changelog](./CHANGELOG.md)
43
61
  - [Roadmap](./ROADMAP.md)
62
+ - [Glossary](./GLOSSARY.md)
63
+ - [API Reference](./API-REFERENCE.md)
64
+ - [Benchmarks](./BENCHMARKS.md)
65
+ - [Architecture Decisions](./docs/adr/)
66
+ - [Security Defaults](./SECURITY-DEFAULTS.md)
44
67
  - [Contributing](./CONTRIBUTING.md)
45
68
  - [Security](./SECURITY.md)
46
69
  - [Testing](./TESTING.md)
package/TESTING.md CHANGED
@@ -1,140 +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
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
@@ -1,5 +1,5 @@
1
- import { loadConfig, SpeexorLifecycle } from './chunk-2F66BZYJ.js';
2
- import { loadAllPlugins } from './chunk-B7WLHC4W.js';
1
+ import { loadConfig, SpeexorLifecycle } from './chunk-2DX54KIM.js';
2
+ import { loadAllPlugins } from './chunk-7VZHDGRQ.js';
3
3
  import Debug from 'debug';
4
4
 
5
5
  var debug = Debug("speexor:agent");
@@ -33,5 +33,5 @@ async function agentSpawnCommand(options) {
33
33
  }
34
34
 
35
35
  export { agentSpawnCommand };
36
- //# sourceMappingURL=agent-5D3BVWNK.js.map
37
- //# sourceMappingURL=agent-5D3BVWNK.js.map
36
+ //# sourceMappingURL=agent-D4BRWEOZ.js.map
37
+ //# sourceMappingURL=agent-D4BRWEOZ.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-D4BRWEOZ.js","sourcesContent":["import { loadConfig } from '../core/config.js'\r\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\r\nimport { loadAllPlugins } from '../plugins/index.js'\r\nimport type { AgentTask, AgentProvider } from '../core/types.js'\r\nimport Debug from 'debug'\r\n\r\nconst debug = Debug('speexor:agent')\r\n\r\ninterface SpawnOptions {\r\n task: string\r\n agent: string\r\n}\r\n\r\nexport async function agentSpawnCommand(options: SpawnOptions) {\r\n const config = loadConfig()\r\n const lifecycle = new SpeexorLifecycle(config)\r\n await lifecycle.initialize()\r\n\r\n const plugins = loadAllPlugins()\r\n for (const plugin of plugins) {\r\n lifecycle.registerPlugin(plugin)\r\n }\r\n\r\n const project = config.projects[0]\r\n if (!project) {\r\n throw new Error('No project configured')\r\n }\r\n\r\n const task: AgentTask = {\r\n id: options.task,\r\n title: options.task,\r\n description: `Task ${options.task}`,\r\n repository: project.repository,\r\n branch: `speexor/${options.task}`,\r\n provider: (options.agent as AgentProvider) || project.provider.primary,\r\n }\r\n\r\n debug(`Spawning agent for task ${task.id} using ${task.provider}`)\r\n\r\n const session = await lifecycle.spawnAgent(task)\r\n console.log(`\\n ✅ Agent spawned: ${session.id}`)\r\n console.log(` 🤖 Provider: ${session.provider}`)\r\n console.log(` 📋 Task: ${task.id}\\n`)\r\n}\r\n"]}
@@ -208,5 +208,5 @@ var SpeexorLifecycle = class {
208
208
  };
209
209
 
210
210
  export { DEFAULT_REACTION_RULES, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig };
211
- //# sourceMappingURL=chunk-2F66BZYJ.js.map
212
- //# sourceMappingURL=chunk-2F66BZYJ.js.map
211
+ //# sourceMappingURL=chunk-2DX54KIM.js.map
212
+ //# sourceMappingURL=chunk-2DX54KIM.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-2DX54KIM.js","sourcesContent":["import { EventEmitter } from 'eventemitter3'\r\nimport type { EventBus } from './types.js'\r\n\r\nexport function createEventBus(): EventBus {\r\n const emitter = new EventEmitter()\r\n\r\n return {\r\n emit(event, data) {\r\n emitter.emit(event, data)\r\n },\r\n on(event, handler) {\r\n emitter.on(event, handler)\r\n },\r\n off(event, handler) {\r\n emitter.off(event, handler)\r\n },\r\n once(event, handler) {\r\n emitter.once(event, handler)\r\n },\r\n }\r\n}\r\n","import { readFileSync, existsSync } from 'node:fs'\r\nimport { join } from 'node:path'\r\nimport { parse } from 'yaml'\r\nimport { z } from 'zod'\r\nimport type { SpeexorConfig, ReactionConfig } from './types.js'\r\n\r\nconst reactionRuleSchema = z.object({\r\n auto: z.boolean(),\r\n action: z.enum(['fix', 'notify', 'escalate', 'skip']),\r\n retries: z.number().int().min(0).max(10),\r\n escalateAfter: z.number().int().min(1).max(1440),\r\n})\r\n\r\nconst providerRoutingSchema = z.object({\r\n primary: z.enum(['opencode', 'claude-code', 'aider', 'codex']),\r\n fallback: z.array(z.enum(['opencode', 'claude-code', 'aider', 'codex'])).optional(),\r\n concurrentLimit: z.number().int().min(1).max(20).optional(),\r\n costLimit: z.number().int().optional(),\r\n})\r\n\r\nconst projectSchema = z.object({\r\n name: z.string().min(1),\r\n repository: z.string().min(1),\r\n path: z.string().optional(),\r\n branch: z.string().optional(),\r\n provider: providerRoutingSchema,\r\n reactions: z\r\n .object({\r\n 'ci-failed': reactionRuleSchema.optional(),\r\n 'changes-requested': reactionRuleSchema.optional(),\r\n 'approved-and-green': reactionRuleSchema.optional(),\r\n })\r\n .optional(),\r\n plugins: z\r\n .object({\r\n tracker: z.string().optional(),\r\n scm: z.string().optional(),\r\n runtime: z.string().optional(),\r\n notifier: z.string().optional(),\r\n })\r\n .optional(),\r\n})\r\n\r\nconst configSchema = z.object({\r\n version: z.literal('1'),\r\n projects: z.array(projectSchema).min(1),\r\n})\r\n\r\nexport const DEFAULT_REACTION_RULES: ReactionConfig = {\r\n 'ci-failed': { auto: true, action: 'fix', retries: 3, escalateAfter: 30 },\r\n 'changes-requested': { auto: true, action: 'fix', retries: 2, escalateAfter: 60 },\r\n 'approved-and-green': { auto: false, action: 'notify', retries: 0, escalateAfter: 0 },\r\n}\r\n\r\nexport function loadConfig(cwd?: string): SpeexorConfig {\r\n const dir = cwd ?? process.cwd()\r\n\r\n const candidates = [\r\n join(dir, 'speexor.config.yaml'),\r\n join(dir, 'speexor.config.yml'),\r\n join(dir, '.speexor.yaml'),\r\n join(dir, '.speexor.yml'),\r\n join(dir, 'speexor.yaml'),\r\n ]\r\n\r\n let configPath: string | undefined\r\n for (const candidate of candidates) {\r\n if (existsSync(candidate)) {\r\n configPath = candidate\r\n break\r\n }\r\n }\r\n\r\n if (!configPath) {\r\n throw new Error(`speexor.config.yaml not found in ${dir}. ` + 'Run `speexor start <repo>` to initialize.')\r\n }\r\n\r\n const raw = readFileSync(configPath, 'utf-8')\r\n const parsed = parse(raw)\r\n\r\n return validateConfig(parsed)\r\n}\r\n\r\nexport function validateConfig(raw: unknown): SpeexorConfig {\r\n const result = configSchema.safeParse(raw)\r\n if (!result.success) {\r\n const issues = result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n')\r\n throw new Error(`Invalid speexor config:\\n${issues}`)\r\n }\r\n\r\n for (const project of result.data.projects) {\r\n if (!project.reactions) {\r\n ;(project as { reactions: ReactionConfig }).reactions = { ...DEFAULT_REACTION_RULES }\r\n } else {\r\n project.reactions = {\r\n 'ci-failed': project.reactions['ci-failed'] ?? DEFAULT_REACTION_RULES['ci-failed'],\r\n 'changes-requested': project.reactions['changes-requested'] ?? DEFAULT_REACTION_RULES['changes-requested'],\r\n 'approved-and-green': project.reactions['approved-and-green'] ?? DEFAULT_REACTION_RULES['approved-and-green'],\r\n }\r\n }\r\n }\r\n\r\n return result.data as SpeexorConfig\r\n}\r\n\r\nexport function generateDefaultConfig(repoUrl: string, projectName?: string): SpeexorConfig {\r\n const name = projectName ?? repoUrl.split('/').pop()?.replace('.git', '') ?? 'my-project'\r\n return {\r\n version: '1',\r\n projects: [\r\n {\r\n name,\r\n repository: repoUrl,\r\n provider: {\r\n primary: 'opencode',\r\n },\r\n reactions: { ...DEFAULT_REACTION_RULES },\r\n },\r\n ],\r\n }\r\n}\r\n","import type {\r\n SpeexorConfig,\r\n AgentPlugin,\r\n RuntimePlugin,\r\n WorkspacePlugin,\r\n AgentTask,\r\n AgentSession,\r\n SessionStatus,\r\n EventBus,\r\n PluginModule,\r\n PluginSlot,\r\n} from './types.js'\r\nimport { createEventBus } from './event-bus.js'\r\nimport Debug from 'debug'\r\n\r\nconst debug = Debug('speexor:lifecycle')\r\n\r\nexport class SpeexorLifecycle {\r\n private config: SpeexorConfig\r\n public eventBus: EventBus\r\n private plugins: Map<PluginSlot, PluginModule[]> = new Map()\r\n private sessions: Map<string, AgentSession> = new Map()\r\n private status: SessionStatus = 'initializing'\r\n\r\n constructor(config: SpeexorConfig) {\r\n this.config = config\r\n this.eventBus = createEventBus()\r\n }\r\n\r\n async initialize(): Promise<void> {\r\n debug('Initializing Speexor lifecycle')\r\n this.status = 'active'\r\n this.eventBus.emit('lifecycle:initialized', { timestamp: new Date() })\r\n }\r\n\r\n registerPlugin(plugin: PluginModule): void {\r\n const existing = this.plugins.get(plugin.type) ?? []\r\n existing.push(plugin)\r\n this.plugins.set(plugin.type, existing)\r\n debug(`Registered plugin: ${plugin.name} (${plugin.type})`)\r\n }\r\n\r\n getPlugins<T extends PluginModule>(slot: PluginSlot): T[] {\r\n return (this.plugins.get(slot) ?? []) as T[]\r\n }\r\n\r\n getFirstPlugin<T extends PluginModule>(slot: PluginSlot): T | undefined {\r\n return this.getPlugins<T>(slot)[0]\r\n }\r\n\r\n async spawnAgent(task: AgentTask): Promise<AgentSession> {\r\n const agentPlugin = this.getFirstPlugin<AgentPlugin>('agent')\r\n if (!agentPlugin) throw new Error('No agent plugin registered')\r\n\r\n const workspacePlugin = this.getFirstPlugin<WorkspacePlugin>('workspace')\r\n if (!workspacePlugin) throw new Error('No workspace plugin registered')\r\n\r\n const runtimePlugin = this.getFirstPlugin<RuntimePlugin>('runtime')\r\n if (!runtimePlugin) throw new Error('No runtime plugin registered')\r\n\r\n const worktree = await workspacePlugin.createWorktree(task)\r\n this.eventBus.emit('worktree:created', { taskId: task.id, path: worktree.path })\r\n\r\n const runtimeSession = await runtimePlugin.createSession(worktree.path)\r\n\r\n const session = await agentPlugin.spawn(task, runtimeSession)\r\n this.sessions.set(session.id, session)\r\n\r\n this.eventBus.emit('session:created', { session, task })\r\n debug(`Agent spawned: ${session.id} for task ${task.id}`)\r\n\r\n return session\r\n }\r\n\r\n async stopSession(sessionId: string): Promise<void> {\r\n const session = this.sessions.get(sessionId)\r\n if (!session) throw new Error(`Session ${sessionId} not found`)\r\n\r\n const agentPlugin = this.getFirstPlugin<AgentPlugin>('agent')\r\n if (agentPlugin) {\r\n await agentPlugin.kill(sessionId)\r\n }\r\n\r\n this.sessions.delete(sessionId)\r\n this.eventBus.emit('session:completed', { sessionId })\r\n debug(`Session stopped: ${sessionId}`)\r\n }\r\n\r\n getSession(sessionId: string): AgentSession | undefined {\r\n return this.sessions.get(sessionId)\r\n }\r\n\r\n listSessions(): AgentSession[] {\r\n return Array.from(this.sessions.values())\r\n }\r\n\r\n async destroy(): Promise<void> {\r\n this.status = 'completed'\r\n\r\n for (const [id] of this.sessions) {\r\n await this.stopSession(id).catch(() => {})\r\n }\r\n\r\n const slots: PluginSlot[] = ['terminal', 'notifier', 'scm', 'tracker', 'workspace', 'runtime', 'agent']\r\n for (const slot of slots) {\r\n const plugins = this.plugins.get(slot) ?? []\r\n for (const plugin of plugins.reverse()) {\r\n await plugin.destroy().catch(() => {})\r\n }\r\n }\r\n\r\n this.eventBus.emit('lifecycle:destroyed', { timestamp: new Date() })\r\n debug('Lifecycle destroyed')\r\n }\r\n\r\n getConfig(): SpeexorConfig {\r\n return this.config\r\n }\r\n\r\n getStatus(): SessionStatus {\r\n return this.status\r\n }\r\n}\r\n"]}
@@ -662,5 +662,5 @@ function loadPluginByType(type) {
662
662
  }
663
663
 
664
664
  export { loadAllPlugins, loadPluginByType };
665
- //# sourceMappingURL=chunk-B7WLHC4W.js.map
666
- //# sourceMappingURL=chunk-B7WLHC4W.js.map
665
+ //# sourceMappingURL=chunk-7VZHDGRQ.js.map
666
+ //# sourceMappingURL=chunk-7VZHDGRQ.js.map