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.
Files changed (50) hide show
  1. package/.bunli/commands.gen.ts +87 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/.github/workflows/release.yml +140 -0
  4. package/.oxfmtrc.json +3 -0
  5. package/.oxlintrc.json +4 -0
  6. package/.zed/settings.json +76 -0
  7. package/README.md +15 -0
  8. package/bunli.config.ts +11 -0
  9. package/bunup.config.ts +31 -0
  10. package/package.json +36 -0
  11. package/src/adapters/assets/index.ts +1 -0
  12. package/src/adapters/assets/specifications.ts +70 -0
  13. package/src/adapters/beads/agents.ts +105 -0
  14. package/src/adapters/beads/config.ts +17 -0
  15. package/src/adapters/beads/index.ts +4 -0
  16. package/src/adapters/beads/issues.ts +156 -0
  17. package/src/adapters/beads/specifications.ts +55 -0
  18. package/src/adapters/environments/index.ts +43 -0
  19. package/src/adapters/environments/worktrees.ts +78 -0
  20. package/src/adapters/teammates/index.ts +15 -0
  21. package/src/assets/agent/planner.md +196 -0
  22. package/src/assets/command/brainstorm.md +60 -0
  23. package/src/assets/command/specify.md +135 -0
  24. package/src/assets/command/work.md +247 -0
  25. package/src/assets/index.ts +37 -0
  26. package/src/cli/commands/manifest.ts +6 -0
  27. package/src/cli/commands/spec/sync.ts +47 -0
  28. package/src/cli/commands/work.ts +110 -0
  29. package/src/cli/index.ts +11 -0
  30. package/src/plugin.ts +45 -0
  31. package/src/tools/i-am-done.ts +44 -0
  32. package/src/tools/i-am-stuck.ts +49 -0
  33. package/src/tools/index.ts +2 -0
  34. package/src/use-cases/index.ts +5 -0
  35. package/src/use-cases/inject-beads-issue.ts +97 -0
  36. package/src/use-cases/sync-specifications.ts +48 -0
  37. package/src/use-cases/sync-teammates.ts +35 -0
  38. package/src/use-cases/track-specs.ts +91 -0
  39. package/src/use-cases/work-on-issue.ts +110 -0
  40. package/src/utils/chain.ts +60 -0
  41. package/src/utils/frontmatter.spec.ts +491 -0
  42. package/src/utils/frontmatter.ts +317 -0
  43. package/src/utils/opencode.ts +102 -0
  44. package/src/utils/polling.ts +41 -0
  45. package/src/utils/projects.ts +35 -0
  46. package/src/utils/shell/client.spec.ts +106 -0
  47. package/src/utils/shell/client.ts +117 -0
  48. package/src/utils/shell/error.ts +29 -0
  49. package/src/utils/shell/index.ts +2 -0
  50. 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,6 @@
1
+ export default {
2
+ work: () => import("./work"),
3
+ spec: {
4
+ sync: () => import("./spec/sync"),
5
+ },
6
+ };
@@ -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>;
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createCLI } from "@bunli/core";
4
+
5
+ import manifest from "./commands/manifest";
6
+
7
+ const cli = await createCLI();
8
+
9
+ await cli.load(manifest);
10
+
11
+ await cli.run();
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
+ });