opencode-teammate 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/.bunli/commands.gen.ts +87 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release.yml +140 -0
- package/.oxfmtrc.json +3 -0
- package/.oxlintrc.json +4 -0
- package/.zed/settings.json +76 -0
- package/README.md +15 -0
- package/bunli.config.ts +11 -0
- package/bunup.config.ts +31 -0
- package/package.json +36 -0
- package/src/adapters/assets/index.ts +1 -0
- package/src/adapters/assets/specifications.ts +70 -0
- package/src/adapters/beads/agents.ts +105 -0
- package/src/adapters/beads/config.ts +17 -0
- package/src/adapters/beads/index.ts +4 -0
- package/src/adapters/beads/issues.ts +156 -0
- package/src/adapters/beads/specifications.ts +55 -0
- package/src/adapters/environments/index.ts +43 -0
- package/src/adapters/environments/worktrees.ts +78 -0
- package/src/adapters/teammates/index.ts +15 -0
- package/src/assets/agent/planner.md +196 -0
- package/src/assets/command/brainstorm.md +60 -0
- package/src/assets/command/specify.md +135 -0
- package/src/assets/command/work.md +247 -0
- package/src/assets/index.ts +37 -0
- package/src/cli/commands/manifest.ts +6 -0
- package/src/cli/commands/spec/sync.ts +47 -0
- package/src/cli/commands/work.ts +110 -0
- package/src/cli/index.ts +11 -0
- package/src/plugin.ts +45 -0
- package/src/tools/i-am-done.ts +44 -0
- package/src/tools/i-am-stuck.ts +49 -0
- package/src/tools/index.ts +2 -0
- package/src/use-cases/index.ts +5 -0
- package/src/use-cases/inject-beads-issue.ts +97 -0
- package/src/use-cases/sync-specifications.ts +48 -0
- package/src/use-cases/sync-teammates.ts +35 -0
- package/src/use-cases/track-specs.ts +91 -0
- package/src/use-cases/work-on-issue.ts +110 -0
- package/src/utils/chain.ts +60 -0
- package/src/utils/frontmatter.spec.ts +491 -0
- package/src/utils/frontmatter.ts +317 -0
- package/src/utils/opencode.ts +102 -0
- package/src/utils/polling.ts +41 -0
- package/src/utils/projects.ts +35 -0
- package/src/utils/shell/client.spec.ts +106 -0
- package/src/utils/shell/client.ts +117 -0
- package/src/utils/shell/error.ts +29 -0
- package/src/utils/shell/index.ts +2 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specify
|
|
3
|
+
agent: plan
|
|
4
|
+
model: github-copilot/claude-sonnet-4.5
|
|
5
|
+
description: "Transform a thought into a specification document and implementation plan."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Specification Workflow
|
|
9
|
+
|
|
10
|
+
Transform brainstorm thoughts into well-defined specifications through collaborative dialogue.
|
|
11
|
+
|
|
12
|
+
**Input:** A set of thought file paths from the `specs/thoughts/` folder.
|
|
13
|
+
|
|
14
|
+
<thought_file>
|
|
15
|
+
$ARGUMENTS
|
|
16
|
+
</thought_file>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Phase 1: Context Gathering
|
|
21
|
+
|
|
22
|
+
- 1. **Load the Thought**: Read the thought files from `specs/thoughts/` folder
|
|
23
|
+
- 2. **Gather Related Context**: Delegate exploration tasks to subagents
|
|
24
|
+
- Spawn parallel exploration tasks
|
|
25
|
+
```
|
|
26
|
+
Task(subagent_type="explore", description="Explore specifications", prompt="Explore specification related to...")
|
|
27
|
+
# Any relevant exploration tasks...
|
|
28
|
+
```
|
|
29
|
+
- Identify potential conflicts or overlaps with existing specifications
|
|
30
|
+
- 3. **Present Initial Understanding**: Summarize what you understand from the thought (100-150 words)
|
|
31
|
+
- ```
|
|
32
|
+
Based on the thought document, here's my understanding:
|
|
33
|
+
|
|
34
|
+
**Core Idea**: [What the thought is about]
|
|
35
|
+
**Key Goals**: [What it aims to achieve]
|
|
36
|
+
**Open Questions**: [What needs clarification]
|
|
37
|
+
|
|
38
|
+
Is this understanding correct?
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Phase 2: Clarification Dialogue
|
|
44
|
+
|
|
45
|
+
### 2.1 Clarify with User
|
|
46
|
+
|
|
47
|
+
Ask questions **one at a time** to fully understand:
|
|
48
|
+
|
|
49
|
+
- **Purpose**: What problem does this solve?
|
|
50
|
+
- **Scope**: What's in vs. out?
|
|
51
|
+
- **Constraints**: Technical, time, or resource limitations?
|
|
52
|
+
- **Success**: How do we know it's done?
|
|
53
|
+
- **Users**: Who benefits from this?
|
|
54
|
+
|
|
55
|
+
**Guidelines:**
|
|
56
|
+
|
|
57
|
+
- Prefer multiple choice questions when possible
|
|
58
|
+
- React to answers by spawning @codebase-analyser when relevant
|
|
59
|
+
- Stop clarifying when you have enough to write the specification
|
|
60
|
+
- Keep the dialogue bidirectional - answer user questions too
|
|
61
|
+
- Maximum 5-7 clarifying questions before moving forward
|
|
62
|
+
|
|
63
|
+
### 2.2 Confirm Readiness
|
|
64
|
+
|
|
65
|
+
Before proceeding, confirm with the user:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
I believe I have enough information to write the specification. Here's the scope:
|
|
69
|
+
|
|
70
|
+
**What we're building**: [Summary]
|
|
71
|
+
**Key requirements**: [List 3-5 main requirements]
|
|
72
|
+
**Out of scope**: [What we're NOT doing]
|
|
73
|
+
|
|
74
|
+
Ready to proceed with the specification?
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**IMPORTANT**: Before continuing to Phase 3, ensure the user switched to Build mode. If not, ask the user to do so.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Phase 3: Write Specification
|
|
82
|
+
|
|
83
|
+
### 3.1 Delegate Writing
|
|
84
|
+
|
|
85
|
+
Ask @general to write the specification file for you:
|
|
86
|
+
|
|
87
|
+
- Provide the feature name (2-4 words, action-noun format)
|
|
88
|
+
- The subagent should use the `writing-specification` skill to create the document
|
|
89
|
+
- Write the specification to `specs/<feature-name>.md`
|
|
90
|
+
|
|
91
|
+
### 3.2 Present for Validation
|
|
92
|
+
|
|
93
|
+
Present the specification in sections (200-300 words each):
|
|
94
|
+
|
|
95
|
+
1. **Overview & User Scenarios** - Present first, get approval
|
|
96
|
+
2. **Functional Requirements** - Present second, get approval
|
|
97
|
+
3. **Success Criteria & Edge Cases** - Present last, get approval
|
|
98
|
+
|
|
99
|
+
After each section:
|
|
100
|
+
|
|
101
|
+
- Ask: "Does this capture [section topic] correctly? Should I adjust?"
|
|
102
|
+
- If adjustments are needed, similarly to 3.1 Delegate Writing, ask @general to make them. Once adjustments are made, revalidate the section.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Phase 4: Implementation Plan Reconciliation
|
|
107
|
+
|
|
108
|
+
Once the specification is validated and written, reconcile the implementation plan with the new or updated specification.
|
|
109
|
+
|
|
110
|
+
### 4.1 Delegate to Planner
|
|
111
|
+
|
|
112
|
+
Ask @mate.planner to reconcile the implementation plan with the specification:
|
|
113
|
+
|
|
114
|
+
- Provide context on the specification changes.
|
|
115
|
+
- Provide the Beads Epic ID and the specification file path.
|
|
116
|
+
|
|
117
|
+
### 4.2 Strategic Alignment (The "Ask")
|
|
118
|
+
|
|
119
|
+
Present the planner report to the user and review it with the user:
|
|
120
|
+
|
|
121
|
+
- Are the tasks appropriately sized?
|
|
122
|
+
- Are dependencies correct?
|
|
123
|
+
- Does the plan cover all requirements?
|
|
124
|
+
- If the user requests for changes, ask @mate.planner to make them.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Key Principles
|
|
129
|
+
|
|
130
|
+
- **One question at a time**: Don't overwhelm with multiple questions
|
|
131
|
+
- **Bidirectional dialogue**: Answer user questions, don't just ask
|
|
132
|
+
- **Validate incrementally**: Present sections, get approval, then write
|
|
133
|
+
- **No open questions in final spec**: Research or ask immediately, don't defer
|
|
134
|
+
- **Be skeptical**: Question vague requirements, identify issues early
|
|
135
|
+
- **YAGNI ruthlessly**: Remove unnecessary features from all specifications
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: work
|
|
3
|
+
agent: build
|
|
4
|
+
model: github-copilot/claude-sonnet-4.5
|
|
5
|
+
description: "Claim a ready task from beads and get it done."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Work
|
|
9
|
+
|
|
10
|
+
You are tasked with implementing an approved technical plan from beads issues. Your goal is to execute the task autonomously from start to finish, only pausing when you encounter significant mismatches between the plan and reality.
|
|
11
|
+
|
|
12
|
+
<user_prompt>
|
|
13
|
+
$ARGUMENTS
|
|
14
|
+
</user_prompt>
|
|
15
|
+
|
|
16
|
+
## Workflow Overview
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Phase 1: Select Task
|
|
21
|
+
|
|
22
|
+
Run `bd ready` to find tasks with no blockers.
|
|
23
|
+
|
|
24
|
+
**Selection Logic:**
|
|
25
|
+
|
|
26
|
+
- **User provided task ID:** Use it directly, validate with `bd show <task_id>`
|
|
27
|
+
- **Multiple tasks available:** Present list with priority/title, ask user to select
|
|
28
|
+
- **Single task available:** Auto-select and inform user
|
|
29
|
+
- **No tasks available:** Inform user: "No tasks, run `bd blocked` to see what's waiting" and exit.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Phase 2: Claim Task
|
|
34
|
+
|
|
35
|
+
Before updating the task, make sure you have claimed the related epic if any.
|
|
36
|
+
Run `bd update <task_id> --claim` to atomically claim the task.
|
|
37
|
+
|
|
38
|
+
**If claim fails:** Task is already claimed. Inform user and return to Phase 1.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## [When claimed task is an epic] Phase 3: View Task Details
|
|
43
|
+
|
|
44
|
+
Run `bd show <task_id>` and parse for:
|
|
45
|
+
|
|
46
|
+
- Files to create/modify
|
|
47
|
+
- Dependencies and blockers
|
|
48
|
+
- Acceptance criteria
|
|
49
|
+
|
|
50
|
+
**If task is an epic:**
|
|
51
|
+
|
|
52
|
+
- Verify that all child tasks are closed and jump to Phase 7: Validate Acceptance Criteria.
|
|
53
|
+
- If any child tasks are open, inform user and return to Phase 1.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## [When claimed task is a task] Phase 3: View Task Details
|
|
58
|
+
|
|
59
|
+
Run `bd show <task_id>` and parse for:
|
|
60
|
+
|
|
61
|
+
- Files to create/modify
|
|
62
|
+
- Dependencies and blockers
|
|
63
|
+
- Acceptance criteria
|
|
64
|
+
|
|
65
|
+
**If task is an epic:**
|
|
66
|
+
|
|
67
|
+
- Verify that all child tasks are closed and jump to Phase 7: Validate Acceptance Criteria.
|
|
68
|
+
- If any child tasks are open, inform user and return to Phase 1.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 4: Setup Environment
|
|
73
|
+
|
|
74
|
+
- **Create Feature Branch**
|
|
75
|
+
- Branch naming: Use task ID prefix + 3-5 words from title in kebab-case.
|
|
76
|
+
- Run environment setup commands
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Phase 5: Retrieve Context
|
|
81
|
+
|
|
82
|
+
### Deep Analysis
|
|
83
|
+
|
|
84
|
+
Use @explore to understand:
|
|
85
|
+
|
|
86
|
+
- Current architecture and patterns
|
|
87
|
+
- Where new code should fit
|
|
88
|
+
- Existing implementations that might conflict
|
|
89
|
+
- Testing patterns to follow
|
|
90
|
+
|
|
91
|
+
### Read Mentioned Files
|
|
92
|
+
|
|
93
|
+
**CRITICAL:** Read files COMPLETELY - never use limit/offset parameters.
|
|
94
|
+
|
|
95
|
+
Read every file mentioned in the task to understand its purpose, structure, and integration points.
|
|
96
|
+
|
|
97
|
+
### Create Todo List
|
|
98
|
+
|
|
99
|
+
Create an internal checklist tracking:
|
|
100
|
+
|
|
101
|
+
- Implementation items (files, functions, integrations)
|
|
102
|
+
- Verification items (tests, acceptance criteria)
|
|
103
|
+
- Cleanup items (logging, docs, debug code removal)
|
|
104
|
+
|
|
105
|
+
Update this list as you work - check off completed items, add discovered items.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Phase 6: Implement the Plan
|
|
110
|
+
|
|
111
|
+
### Implementation Philosophy
|
|
112
|
+
|
|
113
|
+
**Your job:**
|
|
114
|
+
|
|
115
|
+
- Follow the task's intent while adapting to reality
|
|
116
|
+
- Make atomic, logical commits as you progress
|
|
117
|
+
- Verify continuously (test as you go)
|
|
118
|
+
- Be autonomous - implement fully before asking questions
|
|
119
|
+
|
|
120
|
+
**You should NOT:**
|
|
121
|
+
|
|
122
|
+
- Simplify code just to avoid diagnostics
|
|
123
|
+
- Skip verification steps
|
|
124
|
+
- Wait until the end to commit
|
|
125
|
+
|
|
126
|
+
### Handle Mismatches
|
|
127
|
+
|
|
128
|
+
**Only pause and ask when:**
|
|
129
|
+
|
|
130
|
+
- The mismatch significantly impacts the implementation approach
|
|
131
|
+
- You cannot make a reasonable decision autonomously
|
|
132
|
+
- Following the task literally would break existing code
|
|
133
|
+
|
|
134
|
+
**Present clearly:**
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
🛑 Mismatch Detected
|
|
138
|
+
|
|
139
|
+
**Task Expected:** [what task says]
|
|
140
|
+
**Actual Situation:** [what you found]
|
|
141
|
+
**Why This Matters:** [impact analysis]
|
|
142
|
+
|
|
143
|
+
**Options:**
|
|
144
|
+
1. [Option A with trade-offs]
|
|
145
|
+
2. [Option B with trade-offs]
|
|
146
|
+
|
|
147
|
+
How should I proceed?
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Do NOT ask about:**
|
|
151
|
+
|
|
152
|
+
- Minor naming differences
|
|
153
|
+
- Import statement order
|
|
154
|
+
- Formatting preferences (use project style)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Phase 7: Validate Acceptance Criteria
|
|
159
|
+
|
|
160
|
+
Before marking complete, verify ALL acceptance criteria from the task.
|
|
161
|
+
|
|
162
|
+
Run quality gates (see AGENTS.md for standard commands):
|
|
163
|
+
|
|
164
|
+
- Tests pass
|
|
165
|
+
- Linting clean
|
|
166
|
+
- Formatting correct
|
|
167
|
+
- Type checking passes (if applicable)
|
|
168
|
+
|
|
169
|
+
If any fail, fix and re-verify until all pass.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Phase 8: Complete the Session
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bd close <task_id> --reason "Completed" # Close task
|
|
177
|
+
bd sync # Sync beads database
|
|
178
|
+
git status # Verify clean
|
|
179
|
+
git commit -m "chore: sync beads" # Commit beads issues changes (if relevant)
|
|
180
|
+
git push -u origin <branch-name> # Push branch
|
|
181
|
+
git status # Verify push succeeded
|
|
182
|
+
gh pr create --title "<Task Title>" \ # Submit the pull request
|
|
183
|
+
--body "<Why? and How? Changes Description>" \
|
|
184
|
+
--assignee @me \
|
|
185
|
+
--base main
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Summary
|
|
189
|
+
|
|
190
|
+
Present completion summary:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
✅ Task Completed: [task_id] - [Task Title]
|
|
194
|
+
|
|
195
|
+
**Branch:** <branch-name>
|
|
196
|
+
**Commits:** [N commits]
|
|
197
|
+
|
|
198
|
+
**Files Changed:**
|
|
199
|
+
- Created: [list]
|
|
200
|
+
- Modified: [list]
|
|
201
|
+
|
|
202
|
+
**Next Steps:**
|
|
203
|
+
- Create PR from <branch-name> → main
|
|
204
|
+
- Run `bd ready` for next task
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Key Principles
|
|
210
|
+
|
|
211
|
+
- **Autonomy First:** Only pause for significant mismatches
|
|
212
|
+
- **Atomic Commits:** Commit at logical checkpoints throughout implementation
|
|
213
|
+
- **Verify Continuously:** Test as you go, not just at end
|
|
214
|
+
- **Read Completely:** Never use limit/offset when reading files
|
|
215
|
+
- **Follow Intent:** Adapt plan to reality while honoring its purpose
|
|
216
|
+
- **Branch per Task:** One task = one branch = one focused change
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Troubleshooting
|
|
221
|
+
|
|
222
|
+
**Task is blocked:** Check `bd show <task_id>` for blockers. Work on blocker first or select different task.
|
|
223
|
+
|
|
224
|
+
**Branch already exists:** Check `git status`. If clean, delete with `git branch -D <branch>`. If dirty, stash or commit first.
|
|
225
|
+
|
|
226
|
+
**Tests failing:** Review failure output carefully. Fix root cause, don't simplify implementation.
|
|
227
|
+
|
|
228
|
+
**Files not found:** If create task, that's expected. If modify task, verify paths with find/grep or ask if paths changed.
|
|
229
|
+
|
|
230
|
+
**Acceptance criteria unclear:** Use best judgment based on task goal. Document assumptions in commit messages.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Session Close Protocol
|
|
235
|
+
|
|
236
|
+
**MANDATORY before saying "done":**
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
[ ] All tests passing (see AGENTS.md)
|
|
240
|
+
[ ] All linting passing (see AGENTS.md)
|
|
241
|
+
[ ] All changes committed (git status clean)
|
|
242
|
+
[ ] Task closed in beads (bd close <task_id>)
|
|
243
|
+
[ ] Branch pushed to remote (git push)
|
|
244
|
+
[ ] Summary provided to user
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Never skip these steps.** Work is not done until pushed and closed.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Glob, $ } from "bun";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { all, objectify, toResult } from "radashi";
|
|
4
|
+
|
|
5
|
+
const MARKDOWNS = new Glob("**/*.md");
|
|
6
|
+
|
|
7
|
+
export async function install(destination: string) {
|
|
8
|
+
const $$ = $.cwd(destination);
|
|
9
|
+
|
|
10
|
+
await $$`mkdir -p .opencode/command && mkdir -p .opencode/agent`;
|
|
11
|
+
|
|
12
|
+
const assets = await all(
|
|
13
|
+
objectify(
|
|
14
|
+
Array.from(MARKDOWNS.scanSync({ cwd: import.meta.dir })),
|
|
15
|
+
(fileName) => fileName,
|
|
16
|
+
async (fileName) => {
|
|
17
|
+
const parsed = path.parse(fileName);
|
|
18
|
+
const output = path.format({
|
|
19
|
+
...parsed,
|
|
20
|
+
base: `mate.${parsed.base}`,
|
|
21
|
+
name: `mate.${parsed.name}`,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const [error] = await toResult(
|
|
25
|
+
$$`cp ${import.meta.dir}/${fileName} ${destination}/.opencode/${output}`.quiet(),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (error)
|
|
29
|
+
console.error(`[opencode-teammate] Failed to install markdown '${fileName}'`, error);
|
|
30
|
+
|
|
31
|
+
return error === undefined;
|
|
32
|
+
},
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return assets;
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineCommand, option } from "@bunli/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
import * as use from "../../../use-cases";
|
|
6
|
+
import * as assets from "../../../adapters/assets";
|
|
7
|
+
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
name: "sync",
|
|
10
|
+
description: "Syncs the specifications with beads epics",
|
|
11
|
+
options: {
|
|
12
|
+
target: option(
|
|
13
|
+
z
|
|
14
|
+
.string()
|
|
15
|
+
.transform((t) => path.resolve(t))
|
|
16
|
+
.optional(),
|
|
17
|
+
{
|
|
18
|
+
description: "Specification file or directory to sync",
|
|
19
|
+
short: "t",
|
|
20
|
+
},
|
|
21
|
+
),
|
|
22
|
+
watch: option(z.boolean().default(false), {
|
|
23
|
+
description: "Watch target for changes",
|
|
24
|
+
short: "w",
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
handler: async ({ flags, cwd, colors, spinner }) => {
|
|
28
|
+
const project = "/Users/hugo/arion/d-gram";
|
|
29
|
+
|
|
30
|
+
const syncs = use.syncSpecifications({
|
|
31
|
+
project,
|
|
32
|
+
source: assets.specifications.use({
|
|
33
|
+
target: flags.target ?? cwd,
|
|
34
|
+
ignore: new Bun.Glob("thoughts/**/*.md"),
|
|
35
|
+
watch: flags.watch,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (flags.watch) spinner("Watching for changes...").start();
|
|
40
|
+
|
|
41
|
+
for await (const sync of syncs) {
|
|
42
|
+
const { asset, issue } = sync;
|
|
43
|
+
const filepath = path.relative(project, path.format(asset.filepath));
|
|
44
|
+
process.stdout.write(`\r\x1b[K${colors.bold(filepath)}: ${issue.id}\n`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { defineCommand, option } from "@bunli/core";
|
|
2
|
+
import { colors, type ColorFunction } from "@bunli/utils";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { chain, counting, listify, Semaphore } from "radashi";
|
|
5
|
+
|
|
6
|
+
import { poll, waitFor } from "../../utils/polling";
|
|
7
|
+
import { createClient } from "../../utils/opencode";
|
|
8
|
+
import * as projects from "../../utils/projects";
|
|
9
|
+
import * as beads from "../../adapters/beads";
|
|
10
|
+
import * as use from "../../use-cases";
|
|
11
|
+
|
|
12
|
+
// - through a tool `i_am_stuck(boolean)`, integrate agent.status = "stuck" with beads
|
|
13
|
+
// - when the task is stuck, the RALPH loop should:
|
|
14
|
+
// - not close
|
|
15
|
+
// - not resolve
|
|
16
|
+
// - request for human jump in the session
|
|
17
|
+
export default defineCommand({
|
|
18
|
+
name: "work",
|
|
19
|
+
alias: ["ralph"],
|
|
20
|
+
description: "Starts RALPH loop on opened tasks",
|
|
21
|
+
options: {
|
|
22
|
+
concurrency: option(z.coerce.number().min(1).default(1), {
|
|
23
|
+
description: "How many workers can be spawned at the same time",
|
|
24
|
+
short: "c",
|
|
25
|
+
}),
|
|
26
|
+
task: option(z.string().optional(), {
|
|
27
|
+
description:
|
|
28
|
+
"Task (including subtasks) to work on. When not provided, workers can be spawned for any ready tasks.",
|
|
29
|
+
short: "t",
|
|
30
|
+
}),
|
|
31
|
+
"wait-on-idle": option(z.boolean().default(false), {
|
|
32
|
+
description: "If no task is available, wait for one to become available.",
|
|
33
|
+
}),
|
|
34
|
+
"max-dispatches": option(z.coerce.number().min(1).default(Infinity), {
|
|
35
|
+
description: "Limit the number of tasks that can be dispatched to workers.",
|
|
36
|
+
short: "n",
|
|
37
|
+
}),
|
|
38
|
+
project: option(z.string().optional(), {
|
|
39
|
+
description:
|
|
40
|
+
"Project to work on. When not provided, workers can be spawned for any ready tasks.",
|
|
41
|
+
short: "p",
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
handler: async ({ flags, colors }) => {
|
|
45
|
+
const client = await createClient({
|
|
46
|
+
loopUpForDesktopServer: true,
|
|
47
|
+
});
|
|
48
|
+
const directory = await projects.use(client, flags.project);
|
|
49
|
+
const shell = Bun.$.cwd(directory);
|
|
50
|
+
const dispatched = new Set<string>();
|
|
51
|
+
|
|
52
|
+
const { "wait-on-idle": waitOnIdle, "max-dispatches": maxDispatches } = flags;
|
|
53
|
+
|
|
54
|
+
const tasks = poll(
|
|
55
|
+
() =>
|
|
56
|
+
beads.issues.list.ready(shell, {
|
|
57
|
+
parent: flags.task,
|
|
58
|
+
}),
|
|
59
|
+
{ interval: "1 second", exitOnEmpty: !waitOnIdle },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const semaphore = new Semaphore(flags.concurrency);
|
|
63
|
+
|
|
64
|
+
for await (const task of tasks) {
|
|
65
|
+
const permit = await semaphore.acquire();
|
|
66
|
+
|
|
67
|
+
dispatched.add(task.id);
|
|
68
|
+
use
|
|
69
|
+
.workOnIssue({
|
|
70
|
+
client,
|
|
71
|
+
directory,
|
|
72
|
+
issue: task,
|
|
73
|
+
})
|
|
74
|
+
.catch((error) => console.error(error))
|
|
75
|
+
.finally(() => permit.release());
|
|
76
|
+
|
|
77
|
+
if (dispatched.size >= maxDispatches) break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// wait for all works to complete
|
|
81
|
+
await waitFor(
|
|
82
|
+
async () => semaphore.queueLength === 0 && semaphore.maxCapacity === semaphore.capacity,
|
|
83
|
+
{ interval: "1 second" },
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const issues = await beads.issues.list(shell, {
|
|
87
|
+
all: true,
|
|
88
|
+
ids: Array.from(dispatched.values()),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const counts = counting(issues, (issue) => issue.status);
|
|
92
|
+
const recap = listify(counts, (status, count) => {
|
|
93
|
+
const style = recapStyles[status] ?? colors.reset;
|
|
94
|
+
return style(`${count} ${status}`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(
|
|
98
|
+
colors.gray(`Dispatched: ${colors.italic(Array.from(dispatched.values()).join(", "))}`),
|
|
99
|
+
);
|
|
100
|
+
console.log(`Tasks: ${recap.join(", ")}`);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const recapStyles = {
|
|
105
|
+
open: chain(colors.yellow, colors.italic),
|
|
106
|
+
in_progress: chain(colors.yellow, colors.italic),
|
|
107
|
+
blocked: chain(colors.red, colors.bold, colors.underline),
|
|
108
|
+
deferred: colors.red,
|
|
109
|
+
closed: chain(colors.green, colors.bold),
|
|
110
|
+
} as Record<string, ColorFunction>;
|
package/src/cli/index.ts
ADDED
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
import * as assets from "./assets";
|
|
4
|
+
import { register } from "./utils/opencode";
|
|
5
|
+
import { chain } from "./utils/chain";
|
|
6
|
+
import * as use from "./use-cases";
|
|
7
|
+
import * as tools from "./tools";
|
|
8
|
+
|
|
9
|
+
export const TeammatePlugin: Plugin = async (options) => {
|
|
10
|
+
const { directory } = options;
|
|
11
|
+
|
|
12
|
+
const client = await register(options);
|
|
13
|
+
await assets.install(directory);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
// event: async ({ event }) => {
|
|
17
|
+
// const [error] = await toResult(use.trackSpecs(options, event));
|
|
18
|
+
// if (error)
|
|
19
|
+
// await client.app.log({
|
|
20
|
+
// service: "opencode-teammate",
|
|
21
|
+
// level: "error",
|
|
22
|
+
// message:
|
|
23
|
+
// error instanceof ShellError
|
|
24
|
+
// ? error.stderr.toString()
|
|
25
|
+
// : error.message,
|
|
26
|
+
// });
|
|
27
|
+
// },
|
|
28
|
+
"command.execute.before": async (input, output) => {
|
|
29
|
+
await chain({ stopOnFatal: true }, () =>
|
|
30
|
+
use.injectBeadsIssue({
|
|
31
|
+
client,
|
|
32
|
+
directory,
|
|
33
|
+
input,
|
|
34
|
+
output,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
tool: {
|
|
39
|
+
i_am_stuck: tools.createIAmStuck(client),
|
|
40
|
+
i_am_done: tools.createIAmDone(client),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default TeammatePlugin;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
3
|
+
import { assert, dedent } from "radashi";
|
|
4
|
+
|
|
5
|
+
import * as beads from "../adapters/beads";
|
|
6
|
+
|
|
7
|
+
export const createIAmDone = (client: OpencodeClient) =>
|
|
8
|
+
tool({
|
|
9
|
+
description: dedent`
|
|
10
|
+
Inform your manager that you are done with the task. The manager will then review your work and handle the rest.
|
|
11
|
+
Use this tool when:
|
|
12
|
+
- All acceptance criteria are met and you have completed the task.
|
|
13
|
+
- Right before returning
|
|
14
|
+
`,
|
|
15
|
+
args: {},
|
|
16
|
+
execute: async (_args, ctx) => {
|
|
17
|
+
const session = await client.session.get({
|
|
18
|
+
sessionID: ctx.sessionID,
|
|
19
|
+
directory: ctx.directory,
|
|
20
|
+
});
|
|
21
|
+
assert(session.data, `Failed to fetch session: ${JSON.stringify(session.error)}`);
|
|
22
|
+
|
|
23
|
+
const message = await client.session.message({
|
|
24
|
+
sessionID: ctx.sessionID,
|
|
25
|
+
messageID: ctx.messageID,
|
|
26
|
+
directory: ctx.directory,
|
|
27
|
+
});
|
|
28
|
+
assert(message.data, `Failed to fetch session message: ${JSON.stringify(session.error)}`);
|
|
29
|
+
|
|
30
|
+
const { info } = message.data;
|
|
31
|
+
|
|
32
|
+
await beads.agents.done(Bun.$.cwd(session.data.directory), session.data.slug, {
|
|
33
|
+
context: {
|
|
34
|
+
agent: ctx.agent,
|
|
35
|
+
model: info.role === "user" ? info.model.modelID : info.modelID,
|
|
36
|
+
provider: info.role === "user" ? info.model.providerID : info.providerID,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return dedent`
|
|
41
|
+
You are marked as done. Your manager will review your work and come back to you if needed.
|
|
42
|
+
`;
|
|
43
|
+
},
|
|
44
|
+
});
|