opencode-teammate 0.1.0-next.1
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,87 @@
|
|
|
1
|
+
// This file was automatically generated by Bunli.
|
|
2
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
3
|
+
|
|
4
|
+
import type { Command, CLI, GeneratedOptionMeta, RegisteredCommands, CommandOptions, GeneratedCommandMeta } from '@bunli/core'
|
|
5
|
+
import { createGeneratedHelpers, registerGeneratedStore } from '@bunli/core'
|
|
6
|
+
|
|
7
|
+
import SpecSync from '../src/cli/commands/spec/sync.js'
|
|
8
|
+
import Work from '../src/cli/commands/work.js'
|
|
9
|
+
|
|
10
|
+
// Narrow list of command names to avoid typeof-cycles in types
|
|
11
|
+
const names = ['spec/sync', 'work'] as const
|
|
12
|
+
type GeneratedNames = typeof names[number]
|
|
13
|
+
|
|
14
|
+
const modules: Record<GeneratedNames, Command<any>> = {
|
|
15
|
+
'spec/sync': SpecSync,
|
|
16
|
+
'work': Work
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
const metadata: Record<GeneratedNames, GeneratedCommandMeta> = {
|
|
20
|
+
'spec/sync': {
|
|
21
|
+
name: 'spec/sync',
|
|
22
|
+
description: 'Syncs the specifications with beads epics',
|
|
23
|
+
options: {
|
|
24
|
+
'target': { type: 'z.string.transform.optional', required: false, hasDefault: false, description: 'Specification file or directory to sync', short: 't', isTransform: true, fileType: 'directory', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' },
|
|
25
|
+
'watch': { type: 'z.boolean.default', required: true, hasDefault: true, default: false, description: 'Watch target for changes', short: 'w', schema: {"type":"zod","method":"default","args":[{"type":"unknown","raw":{"type":"BooleanLiteral","start":586,"end":591,"loc":{"start":{"line":22,"column":38,"index":586},"end":{"line":22,"column":43,"index":591}},"value":false}}]}, validator: '(val) => true' }
|
|
26
|
+
},
|
|
27
|
+
path: '../src/cli/commands/spec/sync'
|
|
28
|
+
},
|
|
29
|
+
'work': {
|
|
30
|
+
name: 'work',
|
|
31
|
+
description: 'Starts RALPH loop on opened tasks',
|
|
32
|
+
alias: ["ralph"],
|
|
33
|
+
options: {
|
|
34
|
+
'concurrency': { type: 'z.coerce.number.min.default', required: true, hasDefault: true, default: 1, description: 'How many workers can be spawned at the same time', short: 'c', min: 1, minLength: 1, schema: {"type":"zod","method":"default","args":[{"type":"unknown","raw":{"type":"NumericLiteral","start":862,"end":863,"loc":{"start":{"line":22,"column":57,"index":862},"end":{"line":22,"column":58,"index":863}},"extra":{"rawValue":1,"raw":"1"},"value":1}}]}, validator: '(val) => true' },
|
|
35
|
+
'task': { type: 'z.string.optional', required: false, hasDefault: false, description: 'Task (including subtasks) to work on. When not provided, workers can be spawned for any ready tasks.', short: 't', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' },
|
|
36
|
+
'project': { type: 'z.string.optional', required: false, hasDefault: false, description: 'Project to work on. When not provided, workers can be spawned for any ready tasks.', short: 'p', schema: {"type":"zod","method":"optional","args":[]}, validator: '(val) => true' }
|
|
37
|
+
},
|
|
38
|
+
path: '../src/cli/commands/work'
|
|
39
|
+
}
|
|
40
|
+
} as const
|
|
41
|
+
|
|
42
|
+
export const generated = registerGeneratedStore(createGeneratedHelpers(modules, metadata))
|
|
43
|
+
|
|
44
|
+
export const commands = generated.commands
|
|
45
|
+
export const commandMeta = generated.metadata
|
|
46
|
+
|
|
47
|
+
export interface GeneratedCLI {
|
|
48
|
+
register(cli?: CLI<any>): GeneratedCLI
|
|
49
|
+
list(): Array<{
|
|
50
|
+
name: GeneratedNames
|
|
51
|
+
command: (typeof modules)[GeneratedNames]
|
|
52
|
+
metadata: (typeof metadata)[GeneratedNames]
|
|
53
|
+
}>
|
|
54
|
+
get<Name extends GeneratedNames>(name: Name): (typeof modules)[Name]
|
|
55
|
+
getMetadata<Name extends GeneratedNames>(name: Name): (typeof metadata)[Name]
|
|
56
|
+
getFlags<Name extends keyof RegisteredCommands & string>(name: Name): CommandOptions<Name>
|
|
57
|
+
getFlagsMeta<Name extends GeneratedNames>(name: Name): Record<string, GeneratedOptionMeta>
|
|
58
|
+
withCLI(cli: CLI<any>): { execute(name: string, options: unknown): Promise<void> }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const cli: GeneratedCLI = {
|
|
62
|
+
register: (cliInstance?: CLI<any>) => { generated.register(cliInstance); return cli },
|
|
63
|
+
list: () => generated.list(),
|
|
64
|
+
get: <Name extends GeneratedNames>(name: Name) => generated.get(name),
|
|
65
|
+
getMetadata: <Name extends GeneratedNames>(name: Name) => generated.getMetadata(name),
|
|
66
|
+
getFlags: <Name extends keyof RegisteredCommands & string>(name: Name) => generated.getFlags(name) as CommandOptions<Name>,
|
|
67
|
+
getFlagsMeta: <Name extends GeneratedNames>(name: Name) => generated.getFlagsMeta(name),
|
|
68
|
+
withCLI: (cliInstance) => generated.withCLI(cliInstance)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Enhanced helper functions
|
|
72
|
+
export const listCommands = () => generated.list().map(c => c.name)
|
|
73
|
+
export const getCommandApi = <Name extends GeneratedNames>(name: Name) => generated.getMetadata(name)
|
|
74
|
+
export const getTypedFlags = <Name extends GeneratedNames>(name: Name) => generated.getFlags(name) as CommandOptions<Name>
|
|
75
|
+
export const validateCommand = <Name extends GeneratedNames>(name: Name, flags: Record<string, unknown>) => generated.validateCommand(name, flags)
|
|
76
|
+
export const findCommandByName = <Name extends GeneratedNames>(name: Name) => generated.findByName(name)
|
|
77
|
+
export const findCommandsByDescription = (searchTerm: string) => generated.findByDescription(searchTerm)
|
|
78
|
+
export const getCommandNames = () => generated.getCommandNames()
|
|
79
|
+
|
|
80
|
+
// Auto-register on import for zero-config usage
|
|
81
|
+
export default cli
|
|
82
|
+
|
|
83
|
+
// Ensure module augmentation happens on import
|
|
84
|
+
declare module '@bunli/core' {
|
|
85
|
+
// Precise key mapping without typeof cycles
|
|
86
|
+
interface RegisteredCommands extends Record<GeneratedNames, Command<any>> {}
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
checks:
|
|
11
|
+
name: Formatting & Lint
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup Bun
|
|
19
|
+
uses: oven-sh/setup-bun@v2
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: bun install --frozen-lockfile
|
|
23
|
+
|
|
24
|
+
- name: Generate
|
|
25
|
+
run: bun run generate
|
|
26
|
+
|
|
27
|
+
- name: Check formatting
|
|
28
|
+
run: bun run fmt --check
|
|
29
|
+
|
|
30
|
+
- name: Run lint (includes type checks)
|
|
31
|
+
run: bun run lint
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
name: Release (next)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["CI"]
|
|
6
|
+
types: [completed]
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: release-next-main
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release-next:
|
|
14
|
+
if: >
|
|
15
|
+
github.event.workflow_run.conclusion == 'success' &&
|
|
16
|
+
github.event.workflow_run.head_branch == 'main'
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
permissions:
|
|
19
|
+
contents: write
|
|
20
|
+
id-token: write
|
|
21
|
+
|
|
22
|
+
env:
|
|
23
|
+
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
24
|
+
RELEASE_BRANCH: release
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- name: Checkout triggering main commit
|
|
28
|
+
uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
ref: ${{ env.SOURCE_SHA }}
|
|
32
|
+
|
|
33
|
+
- name: Setup Bun
|
|
34
|
+
uses: oven-sh/setup-bun@v2
|
|
35
|
+
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: bun install --frozen-lockfile
|
|
38
|
+
|
|
39
|
+
- name: Prepare local release branch
|
|
40
|
+
shell: bash
|
|
41
|
+
run: |
|
|
42
|
+
git fetch origin --tags
|
|
43
|
+
if git ls-remote --heads origin "${RELEASE_BRANCH}" | grep -q "${RELEASE_BRANCH}$"; then
|
|
44
|
+
git fetch origin "${RELEASE_BRANCH}:${RELEASE_BRANCH}"
|
|
45
|
+
git checkout "${RELEASE_BRANCH}"
|
|
46
|
+
else
|
|
47
|
+
git checkout --orphan "${RELEASE_BRANCH}"
|
|
48
|
+
git rm -rf .
|
|
49
|
+
git checkout "${SOURCE_SHA}" -- .
|
|
50
|
+
git config user.name "github-actions[bot]"
|
|
51
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
52
|
+
git commit -m "chore(next): initialize ${RELEASE_BRANCH} from ${SOURCE_SHA:0:7}"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
- name: Sync source tree from triggering main commit
|
|
56
|
+
shell: bash
|
|
57
|
+
run: |
|
|
58
|
+
git rm -rf .
|
|
59
|
+
git checkout "${SOURCE_SHA}" -- .
|
|
60
|
+
|
|
61
|
+
- name: Read package name
|
|
62
|
+
id: pkg
|
|
63
|
+
shell: bash
|
|
64
|
+
run: |
|
|
65
|
+
PKG_NAME="$(node -p "require('./package.json').name")"
|
|
66
|
+
echo "name=${PKG_NAME}" >> "$GITHUB_OUTPUT"
|
|
67
|
+
echo "Package: ${PKG_NAME}"
|
|
68
|
+
|
|
69
|
+
- name: Resolve semver base from npm latest
|
|
70
|
+
id: base
|
|
71
|
+
shell: bash
|
|
72
|
+
run: |
|
|
73
|
+
PKG="${{ steps.pkg.outputs.name }}"
|
|
74
|
+
LATEST="$(npm view "${PKG}" version 2>/dev/null || true)"
|
|
75
|
+
|
|
76
|
+
if [ -z "${LATEST}" ]; then
|
|
77
|
+
# First ever publish for this package
|
|
78
|
+
MAJOR=0
|
|
79
|
+
MINOR=1
|
|
80
|
+
PATCH=0
|
|
81
|
+
else
|
|
82
|
+
# Expect X.Y.Z on latest channel
|
|
83
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST}"
|
|
84
|
+
PATCH=$((PATCH + 1))
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
BASE="${MAJOR}.${MINOR}.${PATCH}"
|
|
88
|
+
echo "base=${BASE}" >> "$GITHUB_OUTPUT"
|
|
89
|
+
echo "Base next line: ${BASE}"
|
|
90
|
+
|
|
91
|
+
- name: Compute next prerelease version
|
|
92
|
+
id: ver
|
|
93
|
+
shell: bash
|
|
94
|
+
run: |
|
|
95
|
+
BASE="${{ steps.base.outputs.base }}"
|
|
96
|
+
|
|
97
|
+
# tags look like: 1.2.1-next.1, 1.2.1-next.2, ...
|
|
98
|
+
LAST_TAG="$(git tag --list "${BASE}-next.*" --sort=-v:refname | head -n1 || true)"
|
|
99
|
+
|
|
100
|
+
if [ -z "${LAST_TAG}" ]; then
|
|
101
|
+
X=1
|
|
102
|
+
else
|
|
103
|
+
LAST_NUM="${LAST_TAG##*.}"
|
|
104
|
+
if [ -z "${LAST_NUM}" ] || [ "${LAST_NUM}" = "${LAST_TAG}" ]; then
|
|
105
|
+
X=1
|
|
106
|
+
else
|
|
107
|
+
X=$((LAST_NUM + 1))
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
VERSION="${BASE}-next.${X}+sha.${SOURCE_SHA:0:7}"
|
|
112
|
+
|
|
113
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
114
|
+
echo "Computed next version: ${VERSION}"
|
|
115
|
+
|
|
116
|
+
- name: Bump package version
|
|
117
|
+
run: bun pm version "${{ steps.ver.outputs.version }}" --no-git-tag-version
|
|
118
|
+
|
|
119
|
+
- name: Build
|
|
120
|
+
run: bun run build
|
|
121
|
+
|
|
122
|
+
- name: Commit release on release branch
|
|
123
|
+
shell: bash
|
|
124
|
+
run: |
|
|
125
|
+
VERSION="${{ steps.ver.outputs.version }}"
|
|
126
|
+
git config user.name "github-actions[bot]"
|
|
127
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
128
|
+
|
|
129
|
+
git add -A
|
|
130
|
+
git commit -m "release(next): v${VERSION}"
|
|
131
|
+
|
|
132
|
+
# - name: Publish to npm (next dist-tag)
|
|
133
|
+
# env:
|
|
134
|
+
# NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
135
|
+
# run: bun publish --access public --tag next
|
|
136
|
+
|
|
137
|
+
- name: Push release history
|
|
138
|
+
shell: bash
|
|
139
|
+
run: |
|
|
140
|
+
git push origin "${RELEASE_BRANCH}"
|
package/.oxfmtrc.json
ADDED
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Folder-specific settings
|
|
2
|
+
//
|
|
3
|
+
// For a full list of overridable settings, and general information on folder-specific settings,
|
|
4
|
+
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
|
5
|
+
{
|
|
6
|
+
"format_on_save": "on",
|
|
7
|
+
"autosave": "on_focus_change",
|
|
8
|
+
"code_actions_on_format": {
|
|
9
|
+
"source.fixAll.oxlint": true,
|
|
10
|
+
"source.organizeImports.oxlint": true,
|
|
11
|
+
"source.removeUnusedImports": true
|
|
12
|
+
},
|
|
13
|
+
"languages": {
|
|
14
|
+
"JavaScript": {
|
|
15
|
+
"formatter": {
|
|
16
|
+
"language_server": {
|
|
17
|
+
"name": "oxfmt"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"TypeScript": {
|
|
22
|
+
"formatter": {
|
|
23
|
+
"language_server": {
|
|
24
|
+
"name": "oxfmt"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"JSON": {
|
|
29
|
+
"formatter": {
|
|
30
|
+
"language_server": {
|
|
31
|
+
"name": "oxfmt"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"JSONC": {
|
|
36
|
+
"formatter": {
|
|
37
|
+
"language_server": {
|
|
38
|
+
"name": "oxfmt"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"YAML": {
|
|
43
|
+
"formatter": {
|
|
44
|
+
"language_server": {
|
|
45
|
+
"name": "oxfmt"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"Markdown": {
|
|
50
|
+
"formatter": {
|
|
51
|
+
"language_server": {
|
|
52
|
+
"name": "oxfmt"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"lsp": {
|
|
58
|
+
"oxlint": {
|
|
59
|
+
"initialization_options": {
|
|
60
|
+
"typeAware": true,
|
|
61
|
+
"run": "onType",
|
|
62
|
+
"fixKind": "safe_fix"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"oxfmt": {
|
|
66
|
+
"initialization_options": {
|
|
67
|
+
"fmt.experimental": true,
|
|
68
|
+
"settings": {
|
|
69
|
+
"run": "onSave",
|
|
70
|
+
"fmt.experimental": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"file_scan_exclusions": ["**/.git", "**/.DS_Store", "**/Thumbs.db"]
|
|
76
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# opencode-teammate
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/bunli.config.ts
ADDED
package/bunup.config.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineConfig } from "bunup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig([
|
|
4
|
+
{
|
|
5
|
+
name: "plugin",
|
|
6
|
+
entry: "src/plugin.ts",
|
|
7
|
+
format: "esm",
|
|
8
|
+
target: "bun",
|
|
9
|
+
outDir: "dist",
|
|
10
|
+
noExternal: ["radashi"],
|
|
11
|
+
drop: ["console"],
|
|
12
|
+
dts: {
|
|
13
|
+
tsgo: true,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "cli",
|
|
18
|
+
entry: "src/cli/index.ts",
|
|
19
|
+
format: "esm",
|
|
20
|
+
target: "bun",
|
|
21
|
+
noExternal: ["radashi"],
|
|
22
|
+
outDir: "dist/bin",
|
|
23
|
+
minify: true,
|
|
24
|
+
splitting: false,
|
|
25
|
+
clean: false,
|
|
26
|
+
onSuccess: `
|
|
27
|
+
mv dist/bin/index.js dist/bin/mate
|
|
28
|
+
chmod +x dist/bin/mate
|
|
29
|
+
`,
|
|
30
|
+
},
|
|
31
|
+
]);
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-teammate",
|
|
3
|
+
"version": "0.1.0-next.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"bin": {
|
|
6
|
+
"mate": "./src/cli/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"module": "src/plugin.ts",
|
|
10
|
+
"imports": {
|
|
11
|
+
"#utils/*": "./src/utils/*"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"generate": "bunli generate",
|
|
15
|
+
"cli:dev": "bunli dev",
|
|
16
|
+
"build": "bunup",
|
|
17
|
+
"lint": "oxlint --type-aware --type-check",
|
|
18
|
+
"fmt": "oxfmt"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@bunli/core": "^0.5.4",
|
|
22
|
+
"bunli": "^0.5.3",
|
|
23
|
+
"radashi": "~12.7.1",
|
|
24
|
+
"zod": "^4.3.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@opencode-ai/plugin": "^1.1.59",
|
|
28
|
+
"@tsconfig/bun": "^1.0.10",
|
|
29
|
+
"@types/bun": "~1.3.9",
|
|
30
|
+
"@typescript/native-preview": "^7.0.0-dev.20260212.1",
|
|
31
|
+
"bunup": "^0.16.26",
|
|
32
|
+
"oxfmt": "^0.32.0",
|
|
33
|
+
"oxlint": "^1.47.0",
|
|
34
|
+
"oxlint-tsgolint": "^0.12.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as specifications from "./specifications";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { withResolvers } from "radashi";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
export const MARKDOWN = new Bun.Glob("**/*.md");
|
|
7
|
+
|
|
8
|
+
export type Specification = ReturnType<typeof createItem>;
|
|
9
|
+
|
|
10
|
+
export type Frontmatter = z.infer<typeof Frontmatter>;
|
|
11
|
+
export const Frontmatter = z.object({
|
|
12
|
+
issue: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function use(options: { target: string; ignore?: Bun.Glob; watch: boolean }) {
|
|
16
|
+
if (options.watch) return watch(options);
|
|
17
|
+
else return scan(options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function* watch(options: { target: string; ignore?: Bun.Glob }) {
|
|
21
|
+
const touched = new Map<string, number | bigint>();
|
|
22
|
+
const queue: Array<Specification> = [];
|
|
23
|
+
let resolver: PromiseWithResolvers<string> | undefined;
|
|
24
|
+
|
|
25
|
+
const watcher = fs.watch(options.target, async (event, filename) => {
|
|
26
|
+
if (event === "change" && filename) {
|
|
27
|
+
if (!MARKDOWN.match(filename)) return;
|
|
28
|
+
if (options.ignore?.match(filename)) return;
|
|
29
|
+
|
|
30
|
+
const item = createItem(options.target, filename);
|
|
31
|
+
const hash = Bun.hash(await item.file.text());
|
|
32
|
+
if (touched.get(filename) === hash) return;
|
|
33
|
+
|
|
34
|
+
touched.set(filename, hash);
|
|
35
|
+
queue.push(createItem(options.target, filename));
|
|
36
|
+
|
|
37
|
+
if (resolver) {
|
|
38
|
+
resolver.resolve(filename);
|
|
39
|
+
resolver = undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
while (true) {
|
|
46
|
+
const item = queue.shift();
|
|
47
|
+
|
|
48
|
+
if (item) yield item;
|
|
49
|
+
else {
|
|
50
|
+
resolver = withResolvers();
|
|
51
|
+
await resolver.promise;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
watcher.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function* scan(options: { target: string; ignore?: Bun.Glob }) {
|
|
60
|
+
for await (const filename of MARKDOWN.scan({ cwd: options.target })) {
|
|
61
|
+
if (options.ignore?.match(filename)) continue;
|
|
62
|
+
|
|
63
|
+
yield createItem(options.target, filename);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createItem(target: string, filename: string) {
|
|
68
|
+
const filepath = path.join(target, filename);
|
|
69
|
+
return { filename, filepath: path.parse(filepath), file: Bun.file(filepath) };
|
|
70
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isEmpty, listify, sift } from "radashi";
|
|
2
|
+
|
|
3
|
+
// import * as shell from "#utils/shell/index.js";
|
|
4
|
+
import * as shell from "../../utils/shell/index.js";
|
|
5
|
+
|
|
6
|
+
import * as issues from "./issues";
|
|
7
|
+
|
|
8
|
+
export type AgentState = "idle" | "spawning" | "running" | "working" | "stuck" | "done" | "stopped";
|
|
9
|
+
|
|
10
|
+
export interface Agent {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
rig: string | null;
|
|
14
|
+
role_bead: string | null;
|
|
15
|
+
role_type: string | null;
|
|
16
|
+
agent_state: AgentState;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UpdatedAgentState {
|
|
20
|
+
agent: string;
|
|
21
|
+
agent_state: AgentState;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StuckOptions {
|
|
25
|
+
context?: AgentContext;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function stuck(client: shell.client.ClientInput, slug: string, options: StuckOptions) {
|
|
30
|
+
const agent = await state(client, slug, "stuck");
|
|
31
|
+
|
|
32
|
+
await issues.update(
|
|
33
|
+
client,
|
|
34
|
+
{ id: agent.agent },
|
|
35
|
+
{
|
|
36
|
+
"add-label": createContextLabels(options.context),
|
|
37
|
+
description: options.reason,
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return agent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DoneOptions {
|
|
45
|
+
context?: AgentContext;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function done(
|
|
49
|
+
client: shell.client.ClientInput,
|
|
50
|
+
slug: string,
|
|
51
|
+
options: DoneOptions = {},
|
|
52
|
+
) {
|
|
53
|
+
const agent = await state(client, slug, "done");
|
|
54
|
+
|
|
55
|
+
await issues.update(
|
|
56
|
+
client,
|
|
57
|
+
{ id: agent.agent },
|
|
58
|
+
{
|
|
59
|
+
"add-label": createContextLabels(options.context),
|
|
60
|
+
description: "",
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return agent;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function state(client: shell.client.ClientInput, slug: string, state: AgentState) {
|
|
68
|
+
const $ = shell.client.use(client);
|
|
69
|
+
|
|
70
|
+
const agent = await $<UpdatedAgentState>`bd agent state gram-${slug} ${state} --json`.json();
|
|
71
|
+
|
|
72
|
+
return agent;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function use(
|
|
76
|
+
client: shell.client.ClientInput,
|
|
77
|
+
input: { slug: string } | { id: string },
|
|
78
|
+
) {
|
|
79
|
+
const $ = shell.client.use(client);
|
|
80
|
+
|
|
81
|
+
const id = "slug" in input ? `gram-${input.slug}` : input.id;
|
|
82
|
+
|
|
83
|
+
const agent = await $<Agent>`bd agent show ${id} --json`.json();
|
|
84
|
+
|
|
85
|
+
return agent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface AgentContext {
|
|
89
|
+
agent?: string;
|
|
90
|
+
model?: string;
|
|
91
|
+
provider?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createContextLabels(context?: AgentContext) {
|
|
95
|
+
if (!context) return undefined;
|
|
96
|
+
|
|
97
|
+
const labels = sift(
|
|
98
|
+
listify(
|
|
99
|
+
context as Record<string, string | undefined>,
|
|
100
|
+
(key, value) => value && `${key}:${value}`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return isEmpty(labels) ? undefined : labels;
|
|
105
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// import * as shell from "#utils/shell/index.js";
|
|
2
|
+
import * as shell from "../../utils/shell/index.js";
|
|
3
|
+
|
|
4
|
+
export interface Config {
|
|
5
|
+
key: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function issuePrefix(client: shell.client.ClientInput) {
|
|
10
|
+
return get(client, { key: "issue_prefix" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function get(client: shell.client.ClientInput, input: { key: string }) {
|
|
14
|
+
const $ = shell.client.use(client);
|
|
15
|
+
|
|
16
|
+
return await $<Config>`bd config get ${input.key} --json`.json();
|
|
17
|
+
}
|