@horizon-ai-dev/shokunin 0.1.0-alpha.1 → 0.1.0-alpha.7
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/README.md +57 -0
- package/dist/commands/sn-configure.d.ts +3 -2
- package/dist/commands/sn-configure.d.ts.map +1 -1
- package/dist/commands/sn-configure.js +77 -10
- package/dist/commands/sn-configure.js.map +1 -1
- package/dist/plugin.d.ts +10 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +133 -47
- package/dist/plugin.js.map +1 -1
- package/package.json +17 -8
- package/scripts/agent/prompt.txt +270 -0
- package/scripts/agent/test-failure-prompt.txt +40 -0
- package/scripts/agent-loop.sh +439 -0
package/README.md
CHANGED
|
@@ -157,6 +157,63 @@ Use when:
|
|
|
157
157
|
- `repeat_interval_seconds`: Interval for repeat alerts
|
|
158
158
|
- `max_repeats`: Maximum number of repeat alerts
|
|
159
159
|
|
|
160
|
+
## Agent Loop (Autonomous Mode)
|
|
161
|
+
|
|
162
|
+
The plugin includes `agent-loop`, a bash script for running autonomous coding sessions with beads task tracking.
|
|
163
|
+
|
|
164
|
+
### Installation
|
|
165
|
+
|
|
166
|
+
After installing the plugin, the `agent-loop` command becomes available:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Install plugin globally to get the agent-loop command
|
|
170
|
+
npm install -g @horizon-ai-dev/shokunin
|
|
171
|
+
|
|
172
|
+
# Or run directly via npx
|
|
173
|
+
npx @horizon-ai-dev/shokunin agent-loop
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Usage
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Run with default 10 iterations
|
|
180
|
+
agent-loop
|
|
181
|
+
|
|
182
|
+
# Run with custom max iterations
|
|
183
|
+
agent-loop 20
|
|
184
|
+
|
|
185
|
+
# Initialize beads if not present
|
|
186
|
+
agent-loop --init-beads
|
|
187
|
+
|
|
188
|
+
# Combine options
|
|
189
|
+
agent-loop 50 --init-beads
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### What It Does
|
|
193
|
+
|
|
194
|
+
Each iteration:
|
|
195
|
+
1. **Syncs with remote** - Pulls latest git changes and beads tasks
|
|
196
|
+
2. **Finds ready work** - Uses `bd ready` to find unblocked tasks
|
|
197
|
+
3. **Claims and implements** - Picks highest priority task, marks in_progress, implements
|
|
198
|
+
4. **Runs E2E tests** - If `pnpm e2e` exists, runs full test suite
|
|
199
|
+
5. **Handles failures** - Creates P0 blocking tasks for E2E regressions
|
|
200
|
+
6. **Syncs and pushes** - Commits changes, syncs beads, pushes to remote
|
|
201
|
+
|
|
202
|
+
### Prerequisites
|
|
203
|
+
|
|
204
|
+
- OpenCode CLI installed (`npm install -g opencode`)
|
|
205
|
+
- Beads CLI installed (`bd` command available)
|
|
206
|
+
- Git repository with remote configured
|
|
207
|
+
- Beads initialized in project (`.beads/` directory)
|
|
208
|
+
|
|
209
|
+
### Files Included
|
|
210
|
+
|
|
211
|
+
| File | Purpose |
|
|
212
|
+
|------|---------|
|
|
213
|
+
| `scripts/agent-loop.sh` | Main orchestration script |
|
|
214
|
+
| `scripts/agent/prompt.txt` | Agent instructions for beads-driven development |
|
|
215
|
+
| `scripts/agent/test-failure-prompt.txt` | Prompt for creating P0 tasks on E2E failures |
|
|
216
|
+
|
|
160
217
|
## Bundled Skills
|
|
161
218
|
|
|
162
219
|
The plugin includes 6 bundled skills:
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* 5. Install beads git hooks
|
|
13
13
|
* 6. Configure playwright-mcp in opencode.json
|
|
14
14
|
* 7. Copy 6 skills to .opencode/skill/
|
|
15
|
-
* 8.
|
|
16
|
-
* 9.
|
|
15
|
+
* 8. Copy agent-loop.sh and agent prompt files
|
|
16
|
+
* 9. Smart-merge model config into opencode.json
|
|
17
|
+
* 10. Add note to AGENTS.md about runtime context
|
|
17
18
|
*/
|
|
18
19
|
import type { Config } from "@opencode-ai/sdk";
|
|
19
20
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sn-configure.d.ts","sourceRoot":"","sources":["../../src/commands/sn-configure.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sn-configure.d.ts","sourceRoot":"","sources":["../../src/commands/sn-configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;GAEG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAuT3C;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,SAAS,CAOhD,CAAC"}
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* 5. Install beads git hooks
|
|
13
13
|
* 6. Configure playwright-mcp in opencode.json
|
|
14
14
|
* 7. Copy 6 skills to .opencode/skill/
|
|
15
|
-
* 8.
|
|
16
|
-
* 9.
|
|
15
|
+
* 8. Copy agent-loop.sh and agent prompt files
|
|
16
|
+
* 9. Smart-merge model config into opencode.json
|
|
17
|
+
* 10. Add note to AGENTS.md about runtime context
|
|
17
18
|
*/
|
|
18
19
|
/**
|
|
19
20
|
* Command name (without leading slash).
|
|
@@ -167,21 +168,79 @@ Note: The skill files are bundled with the Shokunin plugin. To copy them, you'll
|
|
|
167
168
|
|
|
168
169
|
Since we're running as a command template, use bash to copy:
|
|
169
170
|
\`\`\`bash
|
|
170
|
-
# Find the plugin location
|
|
171
|
-
|
|
171
|
+
# Find the plugin location
|
|
172
|
+
find_plugin_dir() {
|
|
173
|
+
# Try npm global location first
|
|
174
|
+
local npm_global_plugins="$(npm root -g)/@horizon-ai-dev/shokunin"
|
|
175
|
+
if [ -d "$npm_global_plugins/skills" ]; then
|
|
176
|
+
echo "$npm_global_plugins"
|
|
177
|
+
return
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Try local node_modules
|
|
181
|
+
local local_plugins="$(npm root)/@horizon-ai-dev/shokunin"
|
|
182
|
+
if [ -d "$local_plugins/skills" ]; then
|
|
183
|
+
echo "$local_plugins"
|
|
184
|
+
return
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Try relative path for development
|
|
188
|
+
if [ -d "./packages/shokunin-opencode-plugin/skills" ]; then
|
|
189
|
+
echo "./packages/shokunin-opencode-plugin"
|
|
190
|
+
return
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo ""
|
|
194
|
+
}
|
|
172
195
|
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
PLUGIN_DIR="$(find_plugin_dir)"
|
|
197
|
+
|
|
198
|
+
if [ -z "$PLUGIN_DIR" ]; then
|
|
199
|
+
echo "Error: Could not find Shokunin plugin directory"
|
|
200
|
+
exit 1
|
|
201
|
+
fi
|
|
175
202
|
|
|
176
203
|
# Copy each skill
|
|
177
204
|
for skill in prd-generator prd-decomposition agent-development command-development skill-development playwright-skill; do
|
|
178
|
-
if [ -d "$PLUGIN_DIR/$skill" ]; then
|
|
179
|
-
cp -r "$PLUGIN_DIR/$skill" ".opencode/skill/"
|
|
205
|
+
if [ -d "$PLUGIN_DIR/skills/$skill" ]; then
|
|
206
|
+
cp -r "$PLUGIN_DIR/skills/$skill" ".opencode/skill/"
|
|
180
207
|
fi
|
|
181
208
|
done
|
|
182
209
|
\`\`\`
|
|
183
210
|
|
|
184
|
-
## Step 8:
|
|
211
|
+
## Step 8: Copy Agent Loop Script and Prompts
|
|
212
|
+
|
|
213
|
+
Copy the agent-loop.sh script and its associated prompt files for autonomous agent execution:
|
|
214
|
+
|
|
215
|
+
\`\`\`bash
|
|
216
|
+
# Create the scripts directory if it doesn't exist
|
|
217
|
+
mkdir -p scripts/agent
|
|
218
|
+
|
|
219
|
+
# Copy agent-loop.sh to project root scripts directory
|
|
220
|
+
if [ -f "$PLUGIN_DIR/scripts/agent-loop.sh" ]; then
|
|
221
|
+
cp "$PLUGIN_DIR/scripts/agent-loop.sh" "./scripts/agent-loop.sh"
|
|
222
|
+
chmod +x "./scripts/agent-loop.sh"
|
|
223
|
+
echo "Copied agent-loop.sh"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# Copy agent prompt files
|
|
227
|
+
if [ -d "$PLUGIN_DIR/scripts/agent" ]; then
|
|
228
|
+
cp -r "$PLUGIN_DIR/scripts/agent/"* "./scripts/agent/"
|
|
229
|
+
echo "Copied agent prompt files"
|
|
230
|
+
fi
|
|
231
|
+
\`\`\`
|
|
232
|
+
|
|
233
|
+
The agent-loop files include:
|
|
234
|
+
- \`scripts/agent-loop.sh\` - Main agent loop script for autonomous task execution
|
|
235
|
+
- \`scripts/agent/prompt.txt\` - Primary agent prompt for task work
|
|
236
|
+
- \`scripts/agent/test-failure-prompt.txt\` - Prompt for handling test failures
|
|
237
|
+
|
|
238
|
+
After copying, you can run the agent loop with:
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
./scripts/agent-loop.sh [max_iterations] [--init-beads]
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
## Step 9: Smart-Merge Model Config
|
|
185
244
|
|
|
186
245
|
Based on the user's selection in Step 1, merge the model configuration into opencode.json.
|
|
187
246
|
|
|
@@ -202,7 +261,7 @@ Smart-merge rules:
|
|
|
202
261
|
- For conflicts, keep existing value and report the conflict
|
|
203
262
|
- Show user what changes were made
|
|
204
263
|
|
|
205
|
-
## Step
|
|
264
|
+
## Step 10: Update AGENTS.md
|
|
206
265
|
|
|
207
266
|
Check if AGENTS.md exists:
|
|
208
267
|
\`\`\`bash
|
|
@@ -241,15 +300,23 @@ MCP Servers:
|
|
|
241
300
|
Skills Installed:
|
|
242
301
|
- [list of skills copied]
|
|
243
302
|
|
|
303
|
+
Agent Loop:
|
|
304
|
+
- scripts/agent-loop.sh: [copied]
|
|
305
|
+
- scripts/agent/prompt.txt: [copied]
|
|
306
|
+
- scripts/agent/test-failure-prompt.txt: [copied]
|
|
307
|
+
|
|
244
308
|
Files Modified:
|
|
245
309
|
- opencode.json
|
|
246
310
|
- .opencode/skill/
|
|
311
|
+
- scripts/agent-loop.sh
|
|
312
|
+
- scripts/agent/
|
|
247
313
|
- AGENTS.md
|
|
248
314
|
|
|
249
315
|
Next Steps:
|
|
250
316
|
1. Run /sn-check-config to verify configuration
|
|
251
317
|
2. Try /sn-review-pr to review code changes
|
|
252
318
|
3. Use Tab to switch to sn-code-reviewer agent
|
|
319
|
+
4. Run ./scripts/agent-loop.sh to start autonomous agent
|
|
253
320
|
\`\`\`
|
|
254
321
|
|
|
255
322
|
## Error Handling
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sn-configure.js","sourceRoot":"","sources":["../../src/commands/sn-configure.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sn-configure.js","sourceRoot":"","sources":["../../src/commands/sn-configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAE3C;;GAEG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;CAWrB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;EAqBvB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAwQqB,CAAC;AAErC;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,CAAC,YAAY,CAAC,EAAE;QACd,WAAW,EACT,qHAAqH;QACvH,QAAQ,EAAE,gBAAgB;QAC1B,OAAO,EAAE,IAAI,EAAE,6CAA6C;KAC7D;CACF,CAAC"}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -17,14 +17,21 @@
|
|
|
17
17
|
* - sn-silent-failure-hunter (subagent) - Error handling analysis
|
|
18
18
|
* - sn-type-design-analyzer (subagent) - TypeScript type design
|
|
19
19
|
* - sn-skill-reviewer (subagent) - OpenCode skill review
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT: This plugin handles context injection differently from raw beads:
|
|
22
|
+
* - Combines SHOKUNIN_RULES + beads-context into a SINGLE session.prompt() call
|
|
23
|
+
* - This prevents the freeze bug caused by multiple session.prompt() calls
|
|
24
|
+
* within the chat.message hook (see issue investigation 2026-01-18)
|
|
20
25
|
*/
|
|
21
26
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
22
27
|
/**
|
|
23
28
|
* The main Shokunin plugin export.
|
|
24
29
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
30
|
+
* Unlike a simple BeadsPlugin wrapper, this plugin:
|
|
31
|
+
* 1. Loads beads commands and agents directly (not via BeadsPlugin)
|
|
32
|
+
* 2. Handles context injection with a SINGLE session.prompt() call
|
|
33
|
+
* that combines both SHOKUNIN_RULES and beads-context
|
|
34
|
+
* 3. This prevents the freeze bug caused by nested session.prompt() calls
|
|
28
35
|
*/
|
|
29
36
|
export declare const ShokuninPlugin: Plugin;
|
|
30
37
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAS,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA6ItE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,MAoF5B,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -17,8 +17,15 @@
|
|
|
17
17
|
* - sn-silent-failure-hunter (subagent) - Error handling analysis
|
|
18
18
|
* - sn-type-design-analyzer (subagent) - TypeScript type design
|
|
19
19
|
* - sn-skill-reviewer (subagent) - OpenCode skill review
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT: This plugin handles context injection differently from raw beads:
|
|
22
|
+
* - Combines SHOKUNIN_RULES + beads-context into a SINGLE session.prompt() call
|
|
23
|
+
* - This prevents the freeze bug caused by multiple session.prompt() calls
|
|
24
|
+
* within the chat.message hook (see issue investigation 2026-01-18)
|
|
20
25
|
*/
|
|
21
|
-
|
|
26
|
+
// Import beads utilities directly - we handle context injection ourselves
|
|
27
|
+
// to avoid the freeze bug caused by multiple session.prompt() calls
|
|
28
|
+
import { BEADS_GUIDANCE, loadAgent, loadCommands, } from "opencode-beads/src/vendor";
|
|
22
29
|
// Shokunin agents
|
|
23
30
|
import { snCodeReviewerAgent } from "./agents/sn-code-reviewer.js";
|
|
24
31
|
import { snCodeSimplifierAgent } from "./agents/sn-code-simplifier.js";
|
|
@@ -33,7 +40,8 @@ import { snConfigureCommand } from "./commands/sn-configure.js";
|
|
|
33
40
|
import { snReviewPrCommand } from "./commands/sn-review-pr.js";
|
|
34
41
|
import { snUpdateCommand } from "./commands/sn-update.js";
|
|
35
42
|
// Shokunin context
|
|
36
|
-
import {
|
|
43
|
+
import { SHOKUNIN_RULES } from "./context/shokunin-rules.js";
|
|
44
|
+
// Note: looksLikeCompaction is no longer needed - we use session.compacted event instead
|
|
37
45
|
// Shokunin tools
|
|
38
46
|
import { shokuninTools } from "./tools/emergency-stop.js";
|
|
39
47
|
/**
|
|
@@ -57,77 +65,155 @@ const shokuninAgents = {
|
|
|
57
65
|
...snTypeDesignAnalyzerAgent,
|
|
58
66
|
...snSkillReviewerAgent,
|
|
59
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Get the current model/agent context for a session by querying messages.
|
|
70
|
+
*
|
|
71
|
+
* Mirrors OpenCode's internal lastModel() logic to find the most recent
|
|
72
|
+
* user message. Used during event handling when we don't have direct access
|
|
73
|
+
* to the current user message's context.
|
|
74
|
+
*/
|
|
75
|
+
async function getSessionContext(client, sessionID) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await client.session.messages({
|
|
78
|
+
path: { id: sessionID },
|
|
79
|
+
query: { limit: 50 },
|
|
80
|
+
});
|
|
81
|
+
if (response.data) {
|
|
82
|
+
for (const msg of response.data) {
|
|
83
|
+
if (msg.info.role === "user" && "model" in msg.info && msg.info.model) {
|
|
84
|
+
return { model: msg.info.model, agent: msg.info.agent };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// On error, return undefined (let opencode use its default)
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Inject combined Shokunin + Beads context into a session.
|
|
96
|
+
*
|
|
97
|
+
* IMPORTANT: This combines both contexts into a SINGLE session.prompt() call
|
|
98
|
+
* to avoid the freeze bug caused by multiple session.prompt() calls within
|
|
99
|
+
* the chat.message hook.
|
|
100
|
+
*
|
|
101
|
+
* Runs `bd prime` and combines the output with SHOKUNIN_RULES.
|
|
102
|
+
* Silently skips beads context if bd is not installed or not initialized.
|
|
103
|
+
*/
|
|
104
|
+
async function injectCombinedContext(client, $, sessionID, context) {
|
|
105
|
+
// Start with shokunin rules
|
|
106
|
+
let combinedContext = SHOKUNIN_RULES;
|
|
107
|
+
// Try to add beads context
|
|
108
|
+
try {
|
|
109
|
+
const primeOutput = await $ `bd prime`.text();
|
|
110
|
+
if (primeOutput && primeOutput.trim() !== "") {
|
|
111
|
+
combinedContext += `\n\n<beads-context>
|
|
112
|
+
${primeOutput.trim()}
|
|
113
|
+
</beads-context>
|
|
114
|
+
|
|
115
|
+
${BEADS_GUIDANCE}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Silent skip if bd prime fails (not installed or not initialized)
|
|
120
|
+
// Shokunin rules will still be injected
|
|
121
|
+
}
|
|
122
|
+
// Single injection call with combined context
|
|
123
|
+
try {
|
|
124
|
+
await client.session.prompt({
|
|
125
|
+
path: { id: sessionID },
|
|
126
|
+
body: {
|
|
127
|
+
noReply: true,
|
|
128
|
+
model: context?.model,
|
|
129
|
+
agent: context?.agent,
|
|
130
|
+
parts: [{ type: "text", text: combinedContext, synthetic: true }],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Silent skip if injection fails
|
|
136
|
+
}
|
|
137
|
+
}
|
|
60
138
|
/**
|
|
61
139
|
* The main Shokunin plugin export.
|
|
62
140
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
141
|
+
* Unlike a simple BeadsPlugin wrapper, this plugin:
|
|
142
|
+
* 1. Loads beads commands and agents directly (not via BeadsPlugin)
|
|
143
|
+
* 2. Handles context injection with a SINGLE session.prompt() call
|
|
144
|
+
* that combines both SHOKUNIN_RULES and beads-context
|
|
145
|
+
* 3. This prevents the freeze bug caused by nested session.prompt() calls
|
|
66
146
|
*/
|
|
67
|
-
export const ShokuninPlugin = async (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
147
|
+
export const ShokuninPlugin = async ({ client, $ }) => {
|
|
148
|
+
// Load beads commands and agents directly
|
|
149
|
+
const [beadsCommands, beadsAgents] = await Promise.all([
|
|
150
|
+
loadCommands(),
|
|
151
|
+
loadAgent(),
|
|
152
|
+
]);
|
|
153
|
+
// Track sessions that have received combined context
|
|
72
154
|
const injectedSessions = new Set();
|
|
73
|
-
// Shokunin-specific hooks
|
|
74
155
|
const shokuninHooks = {
|
|
75
156
|
config: async (config) => {
|
|
76
157
|
// Inject Shokunin agents
|
|
77
158
|
config.agent = {
|
|
78
159
|
...config.agent,
|
|
79
160
|
...shokuninAgents,
|
|
161
|
+
...beadsAgents,
|
|
80
162
|
};
|
|
81
163
|
// Inject Shokunin commands
|
|
82
164
|
config.command = {
|
|
83
165
|
...config.command,
|
|
84
166
|
...shokuninCommands,
|
|
167
|
+
...beadsCommands,
|
|
85
168
|
};
|
|
86
|
-
// Call beads config hook if it exists
|
|
87
|
-
if (beadsHooks.config) {
|
|
88
|
-
await beadsHooks.config(config);
|
|
89
|
-
}
|
|
90
169
|
},
|
|
91
170
|
// Register Shokunin tools
|
|
92
171
|
tool: shokuninTools,
|
|
93
|
-
// Inject shokunin-rules context on first message
|
|
94
|
-
"chat.message": async (hookInput,
|
|
172
|
+
// Inject combined shokunin-rules + beads context on first message
|
|
173
|
+
"chat.message": async (hookInput, _output) => {
|
|
95
174
|
const sessionID = hookInput.sessionID;
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Check if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
},
|
|
175
|
+
// Skip if already injected this session
|
|
176
|
+
if (injectedSessions.has(sessionID))
|
|
177
|
+
return;
|
|
178
|
+
// Check if combined context was already injected (handles plugin reload/reconnection)
|
|
179
|
+
try {
|
|
180
|
+
const existing = await client.session.messages({
|
|
181
|
+
path: { id: sessionID },
|
|
182
|
+
});
|
|
183
|
+
if (existing.data) {
|
|
184
|
+
const hasContext = existing.data.some((msg) => {
|
|
185
|
+
const parts = msg.parts || msg.info.parts;
|
|
186
|
+
if (!parts)
|
|
187
|
+
return false;
|
|
188
|
+
return parts.some((part) => part.type === "text" &&
|
|
189
|
+
(part.text?.includes("<shokunin-rules>") ||
|
|
190
|
+
part.text?.includes("<beads-context>")));
|
|
113
191
|
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
192
|
+
if (hasContext) {
|
|
193
|
+
injectedSessions.add(sessionID);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
118
196
|
}
|
|
119
197
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
198
|
+
catch {
|
|
199
|
+
// On error, proceed with injection
|
|
200
|
+
}
|
|
201
|
+
injectedSessions.add(sessionID);
|
|
202
|
+
// Inject combined context with a SINGLE session.prompt() call
|
|
203
|
+
await injectCombinedContext(client, $, sessionID, {
|
|
204
|
+
model: hookInput.model,
|
|
205
|
+
agent: hookInput.agent,
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
// Re-inject context after session compaction
|
|
209
|
+
event: async ({ event }) => {
|
|
210
|
+
if (event.type === "session.compacted") {
|
|
211
|
+
const sessionID = event.properties.sessionID;
|
|
212
|
+
const context = await getSessionContext(client, sessionID);
|
|
213
|
+
await injectCombinedContext(client, $, sessionID, context);
|
|
123
214
|
}
|
|
124
215
|
},
|
|
125
216
|
};
|
|
126
|
-
|
|
127
|
-
// This ensures our config hook runs and then calls beads' config hook
|
|
128
|
-
return {
|
|
129
|
-
...beadsHooks,
|
|
130
|
-
...shokuninHooks,
|
|
131
|
-
};
|
|
217
|
+
return shokuninHooks;
|
|
132
218
|
};
|
|
133
219
|
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@horizon-ai-dev/shokunin",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Complete AI-assisted development workflow plugin for OpenCode. Re-exports opencode-beads with specialized agents, commands, and bundled skills.",
|
|
6
6
|
"author": "Horizon AI Dev",
|
|
@@ -15,20 +15,20 @@
|
|
|
15
15
|
"beads",
|
|
16
16
|
"shokunin",
|
|
17
17
|
"ai",
|
|
18
|
-
"agent"
|
|
18
|
+
"agent",
|
|
19
|
+
"autonomous"
|
|
19
20
|
],
|
|
20
21
|
"main": "dist/index.js",
|
|
21
22
|
"types": "dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"agent-loop": "./scripts/agent-loop.sh"
|
|
25
|
+
},
|
|
22
26
|
"files": [
|
|
23
27
|
"dist",
|
|
24
28
|
"vendor",
|
|
29
|
+
"scripts",
|
|
25
30
|
"README.md"
|
|
26
31
|
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "tsc",
|
|
29
|
-
"typecheck": "tsc --noEmit",
|
|
30
|
-
"prepublishOnly": "pnpm build"
|
|
31
|
-
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@opencode-ai/plugin": "^1.0.143",
|
|
34
34
|
"@opencode-ai/sdk": "^1.0.143",
|
|
@@ -42,5 +42,14 @@
|
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"bun": ">=1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"publish:alpha": "pnpm version prerelease --preid=alpha --no-git-tag-version && git add package.json && git commit -m \"chore: bump to $(node -p \"require('./package.json').version\")\" && pnpm publish --access public --tag alpha --no-git-checks",
|
|
50
|
+
"publish:beta": "pnpm version prerelease --preid=beta --no-git-tag-version && git add package.json && git commit -m \"chore: bump to $(node -p \"require('./package.json').version\")\" && pnpm publish --access public --tag beta --no-git-checks",
|
|
51
|
+
"publish:release": "pnpm version patch && pnpm publish --access public",
|
|
52
|
+
"publish:minor": "pnpm version minor && pnpm publish --access public",
|
|
53
|
+
"publish:major": "pnpm version major && pnpm publish --access public"
|
|
45
54
|
}
|
|
46
|
-
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Autonomous Agent Instructions (Beads Edition)
|
|
2
|
+
|
|
3
|
+
You are an autonomous coding agent working on a software project using Beads for task tracking.
|
|
4
|
+
|
|
5
|
+
## Beads Overview
|
|
6
|
+
|
|
7
|
+
Beads is a distributed, git-backed graph issue tracker designed for AI agents. Issues are stored as JSONL in `.beads/` and synchronized via git.
|
|
8
|
+
|
|
9
|
+
Key concepts:
|
|
10
|
+
- `bd ready` - Find tasks with no blockers (ready to work)
|
|
11
|
+
- `bd show <id>` - View task details
|
|
12
|
+
- `bd update <id> --status in_progress` - Claim work
|
|
13
|
+
- `bd close <id>` - Complete work
|
|
14
|
+
- `bd sync` - Synchronize with remote (CRITICAL at session end)
|
|
15
|
+
|
|
16
|
+
## Your Task
|
|
17
|
+
|
|
18
|
+
1. **Synchronize with remote first** - Run `bd sync` to pull latest tasks/changes
|
|
19
|
+
2. **Read the current state** - Run `bd ready --json` to find available work
|
|
20
|
+
3. **Check for in-progress work** - Run `bd list --status=in_progress --json` to resume any interrupted work
|
|
21
|
+
4. **Check branch alignment** - Verify you're on the correct working branch
|
|
22
|
+
5. **Pick the highest priority ready task** - Select from `bd ready` output (P0 highest, P4 lowest)
|
|
23
|
+
6. **Claim the task** - Run `bd update <id> --status in_progress --json`
|
|
24
|
+
7. **Implement the task** - Do the actual work
|
|
25
|
+
8. **Run quality checks** - typecheck, lint, test (use whatever your project requires)
|
|
26
|
+
9. **Update AGENTS.md files** if you discover reusable patterns (see below)
|
|
27
|
+
10. **If checks pass**, commit ALL code changes with message: `feat: [Task ID] - [Task Title]`
|
|
28
|
+
11. **Close the task** - Run `bd close <id> --reason "Implemented and tested" --json`
|
|
29
|
+
12. **Add learnings** - If significant discoveries, create a note task or update the closed task's notes
|
|
30
|
+
13. **Synchronize** - Run `bd sync` to push all changes
|
|
31
|
+
|
|
32
|
+
## Recovery from Interruption
|
|
33
|
+
|
|
34
|
+
On startup, ALWAYS check for interrupted work:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 1. Sync with remote to get latest state
|
|
38
|
+
bd sync
|
|
39
|
+
|
|
40
|
+
# 2. Check for any in-progress work (might be from crashed session)
|
|
41
|
+
bd list --status=in_progress --json
|
|
42
|
+
|
|
43
|
+
# 3. If found, either:
|
|
44
|
+
# - Resume the work if it's still valid
|
|
45
|
+
# - Reset status if work was completed but not closed:
|
|
46
|
+
# bd close <id> --reason "Completed in previous session"
|
|
47
|
+
# - Or reopen if it needs rework:
|
|
48
|
+
# bd update <id> --status open --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Progress Tracking with Beads
|
|
52
|
+
|
|
53
|
+
Use Beads' built-in tracking:
|
|
54
|
+
|
|
55
|
+
### Recording Progress
|
|
56
|
+
```bash
|
|
57
|
+
# Update task with implementation notes
|
|
58
|
+
bd update <id> --notes "Implemented auth validation. Changed files: src/auth.ts, tests/auth.test.ts"
|
|
59
|
+
|
|
60
|
+
# Add design notes if relevant
|
|
61
|
+
bd update <id> --design "Used JWT validation pattern from @repo/auth"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Recording Learnings
|
|
65
|
+
```bash
|
|
66
|
+
# For significant patterns discovered, create a documentation task
|
|
67
|
+
bd create "Document: [Pattern Name]" -t chore -p 3 --description "Pattern discovered while working on <parent-id>: [description]" --json
|
|
68
|
+
|
|
69
|
+
# Or if it's a quick note, add to the task's notes field
|
|
70
|
+
bd update <id> --notes "Learning: Always use IF NOT EXISTS for migrations"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Discovering Related Work
|
|
74
|
+
```bash
|
|
75
|
+
# If you find a bug or new requirement while working, track it:
|
|
76
|
+
bd create "Found: [issue description]" -t bug -p 2 --deps discovered-from:<current-task-id> --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Consolidate Patterns in AGENTS.md
|
|
80
|
+
|
|
81
|
+
If you discover a **reusable pattern** that future iterations should know, add it to the relevant AGENTS.md file (project root or directory-specific):
|
|
82
|
+
|
|
83
|
+
```markdown
|
|
84
|
+
## Codebase Patterns
|
|
85
|
+
- Example: Use `sql<number>` template for aggregations
|
|
86
|
+
- Example: Always use `IF NOT EXISTS` for migrations
|
|
87
|
+
- Example: Export types from actions.ts for UI components
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Only add patterns that are **general and reusable**, not task-specific details.
|
|
91
|
+
|
|
92
|
+
## Update AGENTS.md Files
|
|
93
|
+
|
|
94
|
+
Before committing, check if any edited files have learnings worth preserving:
|
|
95
|
+
|
|
96
|
+
1. **Identify directories with edited files**
|
|
97
|
+
2. **Check for existing AGENTS.md** in those directories or parent directories
|
|
98
|
+
3. **Add valuable learnings**:
|
|
99
|
+
- API patterns or conventions specific to that module
|
|
100
|
+
- Gotchas or non-obvious requirements
|
|
101
|
+
- Dependencies between files
|
|
102
|
+
- Testing approaches for that area
|
|
103
|
+
|
|
104
|
+
**Do NOT add:**
|
|
105
|
+
- Task-specific implementation details (those go in the task notes)
|
|
106
|
+
- Temporary debugging notes
|
|
107
|
+
- Information already tracked in Beads
|
|
108
|
+
|
|
109
|
+
## Quality Requirements
|
|
110
|
+
|
|
111
|
+
- ALL commits must pass your project's quality checks (typecheck, lint, test)
|
|
112
|
+
- Do NOT commit broken code
|
|
113
|
+
- Keep changes focused and minimal
|
|
114
|
+
- Follow existing code patterns
|
|
115
|
+
|
|
116
|
+
Run these commands and ensure they pass:
|
|
117
|
+
```bash
|
|
118
|
+
pnpm check # Linting
|
|
119
|
+
pnpm test # Unit tests
|
|
120
|
+
pnpm build # Full build (includes typecheck) - allow up to 10 min
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## E2E Testing (MANDATORY)
|
|
124
|
+
|
|
125
|
+
### Distinction: Skill vs Persistent Tests
|
|
126
|
+
|
|
127
|
+
| Tool | Purpose | When to Use |
|
|
128
|
+
|------|---------|-------------|
|
|
129
|
+
| `playwright-skill` | Interactive browser exploration | During development, debugging, one-off verification |
|
|
130
|
+
| E2E test files | Permanent regression tests | Encoding acceptance criteria, run every iteration |
|
|
131
|
+
|
|
132
|
+
**Key principle:** The playwright-skill is for exploratory testing during development. Acceptance criteria MUST be encoded as persistent E2E tests that run every iteration to prevent regressions.
|
|
133
|
+
|
|
134
|
+
### Before Closing ANY UI Task
|
|
135
|
+
|
|
136
|
+
1. **Encode acceptance criteria as persistent E2E test**
|
|
137
|
+
- Location: See app-specific AGENTS.md for test paths (e.g., `apps/app/e2e/`)
|
|
138
|
+
- Each acceptance criterion = one or more test assertions
|
|
139
|
+
- Tests must be self-contained and repeatable
|
|
140
|
+
|
|
141
|
+
2. **Run ALL E2E tests (not just new ones)**
|
|
142
|
+
```bash
|
|
143
|
+
pnpm e2e
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
3. **All tests must pass**
|
|
147
|
+
- If ANY test fails (including tests for other features), you MUST NOT close the task
|
|
148
|
+
- Fix the regression first
|
|
149
|
+
- The agent-loop will automatically create a P0 blocking task if E2E tests fail
|
|
150
|
+
|
|
151
|
+
### E2E Test Template
|
|
152
|
+
|
|
153
|
+
For task `bd-abc123` with acceptance criteria:
|
|
154
|
+
- Filter dropdown has options: All, Active, Completed
|
|
155
|
+
- Selecting "Active" shows only active tasks
|
|
156
|
+
|
|
157
|
+
Create `apps/app/e2e/tasks/bd-abc123-filter.spec.ts`:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { expect, test } from '@playwright/test'
|
|
161
|
+
|
|
162
|
+
const TITLE_PATTERN = /.+/
|
|
163
|
+
|
|
164
|
+
test.describe('Task Filter (bd-abc123)', () => {
|
|
165
|
+
test('filter dropdown has correct options', async ({ page }) => {
|
|
166
|
+
await page.goto('/tasks')
|
|
167
|
+
await page.getByRole('combobox', { name: /filter/i }).click()
|
|
168
|
+
await expect(page.getByRole('option', { name: 'All' })).toBeVisible()
|
|
169
|
+
await expect(page.getByRole('option', { name: 'Active' })).toBeVisible()
|
|
170
|
+
await expect(page.getByRole('option', { name: 'Completed' })).toBeVisible()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('selecting Active shows only active tasks', async ({ page }) => {
|
|
174
|
+
await page.goto('/tasks')
|
|
175
|
+
await page.getByRole('combobox', { name: /filter/i }).selectOption('Active')
|
|
176
|
+
// Assert filtering worked...
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Browser Testing During Development
|
|
182
|
+
|
|
183
|
+
For interactive browser exploration during development, use the `playwright-skill`:
|
|
184
|
+
|
|
185
|
+
1. Load the `playwright-skill` skill (use the skill tool)
|
|
186
|
+
2. Navigate to the relevant page
|
|
187
|
+
3. Verify the UI changes work as expected
|
|
188
|
+
4. Take screenshots if helpful
|
|
189
|
+
|
|
190
|
+
**IMPORTANT:** This is for exploratory testing only. You MUST ALSO create persistent E2E tests from acceptance criteria (see above).
|
|
191
|
+
|
|
192
|
+
## Stop Condition
|
|
193
|
+
|
|
194
|
+
After completing a task, check if ALL work is done:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
bd ready --json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
If `bd ready` returns an empty array (no ready tasks), check overall status:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
bd stats --json
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If all tasks are complete (open=0, in_progress=0), reply with:
|
|
207
|
+
<promise>COMPLETE</promise>
|
|
208
|
+
|
|
209
|
+
If there are still tasks with blockers, check:
|
|
210
|
+
```bash
|
|
211
|
+
bd blocked --json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
And report what's blocking progress.
|
|
215
|
+
|
|
216
|
+
If there are still ready tasks, end your response normally (another iteration will pick up the next task).
|
|
217
|
+
|
|
218
|
+
## Important
|
|
219
|
+
|
|
220
|
+
- Work on ONE task per iteration
|
|
221
|
+
- Always run `bd sync` at the START and END of each iteration
|
|
222
|
+
- Commit frequently
|
|
223
|
+
- Keep CI green
|
|
224
|
+
- Use `--json` flag for all bd commands (easier to parse)
|
|
225
|
+
|
|
226
|
+
## Essential bd Commands Reference
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Finding work
|
|
230
|
+
bd ready --json # Tasks ready to work on
|
|
231
|
+
bd list --status=open --json # All open tasks
|
|
232
|
+
bd list --status=in_progress --json # In-progress work
|
|
233
|
+
bd blocked --json # What's blocked and why
|
|
234
|
+
bd show <id> --json # Detailed task view
|
|
235
|
+
|
|
236
|
+
# Working on tasks
|
|
237
|
+
bd update <id> --status in_progress --json # Claim work
|
|
238
|
+
bd update <id> --notes "..." --json # Add notes
|
|
239
|
+
bd close <id> --reason "..." --json # Complete work
|
|
240
|
+
|
|
241
|
+
# Creating new tasks
|
|
242
|
+
bd create "Title" -t task|bug|feature -p 0-4 --json
|
|
243
|
+
bd create "Title" --deps discovered-from:<id> --json # Link to parent
|
|
244
|
+
|
|
245
|
+
# Dependencies
|
|
246
|
+
bd dep add <child> <parent> --type blocks|related|discovered-from
|
|
247
|
+
|
|
248
|
+
# Synchronization (CRITICAL)
|
|
249
|
+
bd sync # Full sync with remote
|
|
250
|
+
bd sync --status # Check sync status
|
|
251
|
+
|
|
252
|
+
# Project health
|
|
253
|
+
bd stats --json # Overall statistics
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Priority Levels
|
|
257
|
+
|
|
258
|
+
- `0` (P0) - Critical: security, data loss, broken builds
|
|
259
|
+
- `1` (P1) - High: major features, important bugs
|
|
260
|
+
- `2` (P2) - Medium: nice-to-have, minor bugs
|
|
261
|
+
- `3` (P3) - Low: polish, optimization
|
|
262
|
+
- `4` (P4) - Backlog: future ideas
|
|
263
|
+
|
|
264
|
+
## Issue Types
|
|
265
|
+
|
|
266
|
+
- `bug` - Something broken
|
|
267
|
+
- `feature` - New functionality
|
|
268
|
+
- `task` - General work item
|
|
269
|
+
- `epic` - Large feature with subtasks
|
|
270
|
+
- `chore` - Maintenance work
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# E2E Test Failure - Create Blocking Fix Task
|
|
2
|
+
|
|
3
|
+
E2E tests failed during the agent loop. You MUST create a beads task to fix this regression.
|
|
4
|
+
|
|
5
|
+
## Test Failure Output
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{{E2E_OUTPUT}}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Your Task
|
|
12
|
+
|
|
13
|
+
1. **Analyze the failure** - Identify which test(s) failed and why
|
|
14
|
+
2. **Create a blocking beads task** with:
|
|
15
|
+
- Title: "Fix E2E regression: [brief description]"
|
|
16
|
+
- Type: bug
|
|
17
|
+
- Priority: 0 (P0 - Critical, blocks all work)
|
|
18
|
+
- Description: Include the failing test name, error message, and likely cause
|
|
19
|
+
- Acceptance criteria: "E2E test [name] passes", "Full E2E suite passes"
|
|
20
|
+
|
|
21
|
+
3. **Add dependency** - If you can identify which recent task likely caused this, link them:
|
|
22
|
+
```bash
|
|
23
|
+
bd dep add <fix-task-id> <causing-task-id> --type discovered-from
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
4. **Sync** - Run `bd sync` to save the task
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bd create "Fix E2E regression: Task filter dropdown not visible" \
|
|
32
|
+
--type bug \
|
|
33
|
+
--priority 0 \
|
|
34
|
+
--description "E2E test 'filter dropdown has correct options' fails with: Timeout waiting for selector '[role=combobox]'. Likely caused by recent filter component refactor." \
|
|
35
|
+
--acceptance "E2E test 'filter dropdown has correct options' passes
|
|
36
|
+
Full E2E suite passes (pnpm e2e)" \
|
|
37
|
+
--labels "area:frontend,regression"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Do NOT attempt to fix the code in this session. Just create the task with sufficient context for the next iteration to fix it.
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Agent Loop - Long-running AI agent loop using OpenCode with Beads task tracking
|
|
3
|
+
# Usage: agent-loop [max_iterations] [--init-beads]
|
|
4
|
+
#
|
|
5
|
+
# Features:
|
|
6
|
+
# - Uses Beads for distributed, git-backed task tracking
|
|
7
|
+
# - Syncs with remote before each iteration to get latest tasks
|
|
8
|
+
# - Recovers gracefully from unexpected termination
|
|
9
|
+
# - Archives completed runs for historical reference
|
|
10
|
+
#
|
|
11
|
+
# This script is part of the @horizon-ai-dev/shokunin OpenCode plugin.
|
|
12
|
+
# It can be run from any directory with a beads-initialized project.
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
MAX_ITERATIONS=${1:-10}
|
|
17
|
+
INIT_BEADS=false
|
|
18
|
+
|
|
19
|
+
# Parse arguments
|
|
20
|
+
for arg in "$@"; do
|
|
21
|
+
case $arg in
|
|
22
|
+
--init-beads)
|
|
23
|
+
INIT_BEADS=true
|
|
24
|
+
shift
|
|
25
|
+
;;
|
|
26
|
+
[0-9]*)
|
|
27
|
+
MAX_ITERATIONS=$arg
|
|
28
|
+
shift
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
# Resolve script location (handles symlinks from npm bin)
|
|
34
|
+
resolve_script_dir() {
|
|
35
|
+
local source="${BASH_SOURCE[0]}"
|
|
36
|
+
local dir
|
|
37
|
+
# Resolve symlinks
|
|
38
|
+
while [ -h "$source" ]; do
|
|
39
|
+
dir="$(cd -P "$(dirname "$source")" && pwd)"
|
|
40
|
+
source="$(readlink "$source")"
|
|
41
|
+
# Handle relative symlinks
|
|
42
|
+
[[ $source != /* ]] && source="$dir/$source"
|
|
43
|
+
done
|
|
44
|
+
dir="$(cd -P "$(dirname "$source")" && pwd)"
|
|
45
|
+
echo "$dir"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SCRIPT_DIR="$(resolve_script_dir)"
|
|
49
|
+
PROMPT_FILE="$SCRIPT_DIR/agent/prompt.txt"
|
|
50
|
+
TEST_FAILURE_PROMPT_FILE="$SCRIPT_DIR/agent/test-failure-prompt.txt"
|
|
51
|
+
|
|
52
|
+
# Working directory where the user runs the command
|
|
53
|
+
WORK_DIR="$(pwd)"
|
|
54
|
+
BEADS_DIR="$WORK_DIR/.beads"
|
|
55
|
+
AGENT_DIR="$WORK_DIR/.agent-loop"
|
|
56
|
+
CONFIG_FILE="$AGENT_DIR/opencode.json"
|
|
57
|
+
STATE_FILE="$AGENT_DIR/.agent-state"
|
|
58
|
+
|
|
59
|
+
# Extended timeout for turbo builds (10 minutes in milliseconds)
|
|
60
|
+
export OPENCODE_TIMEOUT=600000
|
|
61
|
+
|
|
62
|
+
# Color codes for output
|
|
63
|
+
RED='\033[0;31m'
|
|
64
|
+
GREEN='\033[0;32m'
|
|
65
|
+
YELLOW='\033[1;33m'
|
|
66
|
+
BLUE='\033[0;34m'
|
|
67
|
+
NC='\033[0m' # No Color
|
|
68
|
+
|
|
69
|
+
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
70
|
+
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|
71
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
72
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
73
|
+
|
|
74
|
+
# Create OpenCode config for autonomous mode (all permissions allowed)
|
|
75
|
+
create_opencode_config() {
|
|
76
|
+
cat > "$CONFIG_FILE" << 'EOF'
|
|
77
|
+
{
|
|
78
|
+
"$schema": "https://opencode.ai/config.json",
|
|
79
|
+
"share": "auto",
|
|
80
|
+
"autoupdate": false,
|
|
81
|
+
"compaction": {
|
|
82
|
+
"auto": true,
|
|
83
|
+
"prune": true
|
|
84
|
+
},
|
|
85
|
+
"permission": {
|
|
86
|
+
"edit": "allow",
|
|
87
|
+
"bash": {
|
|
88
|
+
"*": "allow",
|
|
89
|
+
"*terraform apply*": "allow",
|
|
90
|
+
"*terraform destroy*": "allow",
|
|
91
|
+
"*terraform import*": "allow",
|
|
92
|
+
"*terraform state rm*": "allow",
|
|
93
|
+
"*terraform state mv*": "allow",
|
|
94
|
+
"*terraform taint*": "allow",
|
|
95
|
+
"*terraform apply -replace*": "allow",
|
|
96
|
+
"*terraform state replace-provider*": "allow",
|
|
97
|
+
"*gcloud *": "allow",
|
|
98
|
+
"*rm -rf*": "allow",
|
|
99
|
+
"*rm -r*": "allow",
|
|
100
|
+
"chmod 777": "allow",
|
|
101
|
+
"chmod -R": "allow",
|
|
102
|
+
"chown -R": "allow",
|
|
103
|
+
"*curl http*": "allow",
|
|
104
|
+
"*wget http*": "allow",
|
|
105
|
+
"*sudo*": "allow",
|
|
106
|
+
"*su*": "allow",
|
|
107
|
+
"bd*": "allow",
|
|
108
|
+
"*apt install*": "allow",
|
|
109
|
+
"*apt-get install*": "allow",
|
|
110
|
+
"*pip install*": "allow",
|
|
111
|
+
"*pnpm install -g*": "allow",
|
|
112
|
+
"*yarn global add*": "allow",
|
|
113
|
+
"*systemctl*": "allow",
|
|
114
|
+
"*service*": "allow",
|
|
115
|
+
"*kill -9*": "allow",
|
|
116
|
+
"*killall*": "allow",
|
|
117
|
+
"*mount*": "allow",
|
|
118
|
+
"*umount*": "allow",
|
|
119
|
+
"*fdisk*": "allow",
|
|
120
|
+
"*mkfs*": "allow",
|
|
121
|
+
"*dd*": "allow",
|
|
122
|
+
"*ssh*": "allow",
|
|
123
|
+
"*scp*": "allow",
|
|
124
|
+
"*rsync*": "allow",
|
|
125
|
+
"*supabase*": "allow",
|
|
126
|
+
"*psql*": "allow"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
EOF
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Check if bd (beads CLI) is available
|
|
134
|
+
check_beads_installed() {
|
|
135
|
+
if ! command -v bd &> /dev/null; then
|
|
136
|
+
log_error "Beads CLI (bd) not found. Please install it first:"
|
|
137
|
+
echo " curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash"
|
|
138
|
+
echo " Or: npm install -g @beads/bd"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
log_info "Beads CLI found: $(which bd)"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Check if opencode is available
|
|
145
|
+
check_opencode_installed() {
|
|
146
|
+
if ! command -v opencode &> /dev/null; then
|
|
147
|
+
log_error "OpenCode CLI not found. Please install it first:"
|
|
148
|
+
echo " npm install -g opencode"
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
log_info "OpenCode CLI found: $(which opencode)"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Initialize Beads if not already initialized
|
|
155
|
+
init_beads_if_needed() {
|
|
156
|
+
if [ ! -d "$BEADS_DIR" ]; then
|
|
157
|
+
if [ "$INIT_BEADS" = true ]; then
|
|
158
|
+
log_info "Initializing Beads in $WORK_DIR..."
|
|
159
|
+
bd init --quiet
|
|
160
|
+
log_success "Beads initialized"
|
|
161
|
+
else
|
|
162
|
+
log_error "Beads not initialized. Run with --init-beads or manually run 'bd init'"
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
else
|
|
166
|
+
log_info "Beads directory found at $BEADS_DIR"
|
|
167
|
+
fi
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Sync with remote to get latest tasks and push any local changes
|
|
171
|
+
sync_with_remote() {
|
|
172
|
+
log_info "Syncing with remote..."
|
|
173
|
+
|
|
174
|
+
# First, pull latest git changes
|
|
175
|
+
if git remote -v | grep -q origin; then
|
|
176
|
+
git pull --rebase origin "$(git branch --show-current)" 2>/dev/null || {
|
|
177
|
+
log_warn "Git pull failed, continuing with local state"
|
|
178
|
+
}
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Then sync beads (imports from JSONL if newer, exports if DB is newer)
|
|
182
|
+
bd sync 2>/dev/null || {
|
|
183
|
+
log_warn "Beads sync had issues, attempting import..."
|
|
184
|
+
bd import -i .beads/issues.jsonl 2>/dev/null || true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
log_success "Sync complete"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Check for and recover from interrupted work
|
|
191
|
+
check_interrupted_work() {
|
|
192
|
+
log_info "Checking for interrupted work..."
|
|
193
|
+
|
|
194
|
+
local in_progress=$(bd list --status=in_progress --json 2>/dev/null | jq -r 'length // 0')
|
|
195
|
+
|
|
196
|
+
if [ "$in_progress" -gt 0 ]; then
|
|
197
|
+
log_warn "Found $in_progress task(s) in progress from previous session"
|
|
198
|
+
echo "The agent will attempt to resume or reconcile these tasks."
|
|
199
|
+
|
|
200
|
+
# Show the in-progress tasks
|
|
201
|
+
bd list --status=in_progress --json 2>/dev/null | jq -r '.[] | " - \(.id): \(.title)"'
|
|
202
|
+
else
|
|
203
|
+
log_info "No interrupted work found"
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Get current work status for logging
|
|
208
|
+
get_work_status() {
|
|
209
|
+
local ready=$(bd ready --json 2>/dev/null | jq -r 'length // 0')
|
|
210
|
+
local in_progress=$(bd list --status=in_progress --json 2>/dev/null | jq -r 'length // 0')
|
|
211
|
+
local blocked=$(bd blocked --json 2>/dev/null | jq -r 'length // 0')
|
|
212
|
+
local stats=$(bd stats --json 2>/dev/null)
|
|
213
|
+
local total_open=$(echo "$stats" | jq -r '.open // 0')
|
|
214
|
+
local total_closed=$(echo "$stats" | jq -r '.closed // 0')
|
|
215
|
+
|
|
216
|
+
echo "Tasks: Ready=$ready, In-Progress=$in_progress, Blocked=$blocked, Open=$total_open, Closed=$total_closed"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Save agent state for recovery
|
|
220
|
+
save_state() {
|
|
221
|
+
local iteration=$1
|
|
222
|
+
local session_id=$2
|
|
223
|
+
|
|
224
|
+
cat > "$STATE_FILE" << EOF
|
|
225
|
+
{
|
|
226
|
+
"iteration": $iteration,
|
|
227
|
+
"session_id": "$session_id",
|
|
228
|
+
"timestamp": "$(date -Iseconds)",
|
|
229
|
+
"branch": "$(git branch --show-current 2>/dev/null || echo 'unknown')"
|
|
230
|
+
}
|
|
231
|
+
EOF
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Load previous state if it exists
|
|
235
|
+
load_state() {
|
|
236
|
+
if [ -f "$STATE_FILE" ]; then
|
|
237
|
+
cat "$STATE_FILE"
|
|
238
|
+
else
|
|
239
|
+
echo "{}"
|
|
240
|
+
fi
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# Archive completed run
|
|
244
|
+
archive_run() {
|
|
245
|
+
local run_name=$1
|
|
246
|
+
local archive_dir="$AGENT_DIR/archive"
|
|
247
|
+
local date_prefix=$(date +%Y-%m-%d-%H%M%S)
|
|
248
|
+
local archive_path="$archive_dir/$date_prefix-$run_name"
|
|
249
|
+
|
|
250
|
+
mkdir -p "$archive_path"
|
|
251
|
+
|
|
252
|
+
# Export current beads state for the archive
|
|
253
|
+
bd list --json > "$archive_path/tasks-final.json" 2>/dev/null || true
|
|
254
|
+
bd stats --json > "$archive_path/stats-final.json" 2>/dev/null || true
|
|
255
|
+
|
|
256
|
+
# Copy agent state
|
|
257
|
+
[ -f "$STATE_FILE" ] && cp "$STATE_FILE" "$archive_path/"
|
|
258
|
+
|
|
259
|
+
log_info "Run archived to: $archive_path"
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Main execution starts here
|
|
263
|
+
echo ""
|
|
264
|
+
echo "================================================================"
|
|
265
|
+
echo " Beads-Powered Agent Loop"
|
|
266
|
+
echo " Max Iterations: $MAX_ITERATIONS"
|
|
267
|
+
echo " Working Directory: $WORK_DIR"
|
|
268
|
+
echo "================================================================"
|
|
269
|
+
echo ""
|
|
270
|
+
|
|
271
|
+
# Pre-flight checks
|
|
272
|
+
check_beads_installed
|
|
273
|
+
check_opencode_installed
|
|
274
|
+
init_beads_if_needed
|
|
275
|
+
mkdir -p "$AGENT_DIR"
|
|
276
|
+
|
|
277
|
+
# Sync with remote to get latest state
|
|
278
|
+
sync_with_remote
|
|
279
|
+
|
|
280
|
+
# Check for interrupted work from previous runs
|
|
281
|
+
check_interrupted_work
|
|
282
|
+
|
|
283
|
+
# Show current status
|
|
284
|
+
log_info "$(get_work_status)"
|
|
285
|
+
|
|
286
|
+
# Create OpenCode config for autonomous mode
|
|
287
|
+
create_opencode_config
|
|
288
|
+
|
|
289
|
+
# Read the prompt file content
|
|
290
|
+
if [ ! -f "$PROMPT_FILE" ]; then
|
|
291
|
+
log_error "Prompt file not found at $PROMPT_FILE"
|
|
292
|
+
log_error "This may indicate a broken installation. Try reinstalling the plugin."
|
|
293
|
+
exit 1
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
PROMPT_CONTENT=$(cat "$PROMPT_FILE")
|
|
297
|
+
|
|
298
|
+
# Main iteration loop
|
|
299
|
+
for i in $(seq 1 $MAX_ITERATIONS); do
|
|
300
|
+
echo ""
|
|
301
|
+
echo "================================================================"
|
|
302
|
+
echo " Iteration $i of $MAX_ITERATIONS - $(date '+%Y-%m-%d %H:%M:%S')"
|
|
303
|
+
echo "================================================================"
|
|
304
|
+
|
|
305
|
+
# Sync before each iteration to catch any external changes
|
|
306
|
+
log_info "Pre-iteration sync..."
|
|
307
|
+
sync_with_remote
|
|
308
|
+
|
|
309
|
+
# Show current work status
|
|
310
|
+
log_info "$(get_work_status)"
|
|
311
|
+
|
|
312
|
+
# Check if there's actually work to do
|
|
313
|
+
READY_COUNT=$(bd ready --json 2>/dev/null | jq -r 'length // 0')
|
|
314
|
+
IN_PROGRESS_COUNT=$(bd list --status=in_progress --json 2>/dev/null | jq -r 'length // 0')
|
|
315
|
+
|
|
316
|
+
if [ "$READY_COUNT" -eq 0 ] && [ "$IN_PROGRESS_COUNT" -eq 0 ]; then
|
|
317
|
+
log_info "No ready or in-progress tasks found"
|
|
318
|
+
|
|
319
|
+
# Check if everything is complete
|
|
320
|
+
STATS=$(bd stats --json 2>/dev/null)
|
|
321
|
+
OPEN=$(echo "$STATS" | jq -r '.open // 0')
|
|
322
|
+
|
|
323
|
+
if [ "$OPEN" -eq 0 ]; then
|
|
324
|
+
log_success "All tasks complete!"
|
|
325
|
+
archive_run "completed"
|
|
326
|
+
rm -f "$CONFIG_FILE"
|
|
327
|
+
exit 0
|
|
328
|
+
else
|
|
329
|
+
log_warn "Open tasks exist but none are ready (possibly blocked)"
|
|
330
|
+
bd blocked --json 2>/dev/null | jq -r '.[] | " Blocked: \(.id) - \(.title)"' || true
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Generate a unique session title for this iteration
|
|
335
|
+
SESSION_TITLE="agent-loop-iteration-$i-$(date +%Y%m%d-%H%M%S)"
|
|
336
|
+
|
|
337
|
+
# Save state before running
|
|
338
|
+
save_state $i "$SESSION_TITLE"
|
|
339
|
+
|
|
340
|
+
# Run opencode in non-interactive mode
|
|
341
|
+
log_info "Starting OpenCode session: $SESSION_TITLE"
|
|
342
|
+
|
|
343
|
+
OUTPUT=$(OPENCODE_CONFIG="$CONFIG_FILE" opencode run \
|
|
344
|
+
--title "$SESSION_TITLE" \
|
|
345
|
+
"$PROMPT_CONTENT" 2>&1 | tee /dev/stderr) || true
|
|
346
|
+
|
|
347
|
+
# Extract session ID from output if available
|
|
348
|
+
SESSION_ID=$(echo "$OUTPUT" | grep -oP 'session[:\s]+\K[a-zA-Z0-9-]+' | head -1 || echo "iteration-$i")
|
|
349
|
+
export OPENCODE_SESSION_ID="$SESSION_ID"
|
|
350
|
+
|
|
351
|
+
# Run ALL E2E tests to verify no regressions (if pnpm e2e exists)
|
|
352
|
+
if [ -f "package.json" ] && grep -q '"e2e"' package.json 2>/dev/null; then
|
|
353
|
+
log_info "Running full E2E test suite..."
|
|
354
|
+
E2E_FAILED=false
|
|
355
|
+
E2E_OUTPUT=$(pnpm e2e --reporter=line 2>&1) || E2E_FAILED=true
|
|
356
|
+
|
|
357
|
+
if [ "$E2E_FAILED" = true ]; then
|
|
358
|
+
log_error "E2E tests failed! Creating blocking task for remediation..."
|
|
359
|
+
|
|
360
|
+
# Save the failure output for context
|
|
361
|
+
E2E_FAILURE_FILE="$AGENT_DIR/.e2e-failure-$i"
|
|
362
|
+
echo "$E2E_OUTPUT" > "$E2E_FAILURE_FILE"
|
|
363
|
+
|
|
364
|
+
# Run opencode with the test-failure prompt to create a blocking beads task
|
|
365
|
+
if [ -f "$TEST_FAILURE_PROMPT_FILE" ]; then
|
|
366
|
+
TEST_FAILURE_PROMPT=$(cat "$TEST_FAILURE_PROMPT_FILE")
|
|
367
|
+
# Replace placeholder with actual test output (first 100 lines to avoid token overflow)
|
|
368
|
+
TEST_OUTPUT_EXCERPT=$(head -100 "$E2E_FAILURE_FILE")
|
|
369
|
+
TEST_FAILURE_PROMPT="${TEST_FAILURE_PROMPT//\{\{E2E_OUTPUT\}\}/$TEST_OUTPUT_EXCERPT}"
|
|
370
|
+
|
|
371
|
+
OPENCODE_CONFIG="$CONFIG_FILE" opencode run \
|
|
372
|
+
--title "e2e-failure-iteration-$i" \
|
|
373
|
+
"$TEST_FAILURE_PROMPT" 2>&1 | tee /dev/stderr || true
|
|
374
|
+
else
|
|
375
|
+
log_warn "Test failure prompt file not found, skipping task creation"
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
# Clean up failure file
|
|
379
|
+
rm -f "$E2E_FAILURE_FILE"
|
|
380
|
+
|
|
381
|
+
# Continue to next iteration (which should pick up the blocking fix task)
|
|
382
|
+
log_warn "E2E failure recorded as P0 blocking task. Next iteration will prioritize the fix."
|
|
383
|
+
else
|
|
384
|
+
log_success "E2E tests passed"
|
|
385
|
+
fi
|
|
386
|
+
else
|
|
387
|
+
log_info "No E2E tests configured (no 'e2e' script in package.json)"
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# Post-iteration sync to ensure all changes are pushed
|
|
391
|
+
log_info "Post-iteration sync..."
|
|
392
|
+
bd sync 2>/dev/null || log_warn "Post-iteration sync had issues"
|
|
393
|
+
|
|
394
|
+
# Push git changes
|
|
395
|
+
if git remote -v | grep -q origin; then
|
|
396
|
+
git push origin "$(git branch --show-current)" 2>/dev/null || {
|
|
397
|
+
log_warn "Git push failed, changes are local only"
|
|
398
|
+
}
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
# Check for completion signal
|
|
402
|
+
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
|
|
403
|
+
echo ""
|
|
404
|
+
log_success "Agent completed all tasks!"
|
|
405
|
+
log_info "Completed at iteration $i of $MAX_ITERATIONS"
|
|
406
|
+
|
|
407
|
+
# Final sync and archive
|
|
408
|
+
bd sync 2>/dev/null || true
|
|
409
|
+
archive_run "completed"
|
|
410
|
+
|
|
411
|
+
# Clean up config file
|
|
412
|
+
rm -f "$CONFIG_FILE"
|
|
413
|
+
exit 0
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
# Show post-iteration status
|
|
417
|
+
log_info "Post-iteration: $(get_work_status)"
|
|
418
|
+
|
|
419
|
+
echo ""
|
|
420
|
+
log_info "Iteration $i complete. Continuing in 2 seconds..."
|
|
421
|
+
sleep 2
|
|
422
|
+
done
|
|
423
|
+
|
|
424
|
+
# Final sync before exit
|
|
425
|
+
log_info "Final sync..."
|
|
426
|
+
bd sync 2>/dev/null || true
|
|
427
|
+
|
|
428
|
+
# Archive the incomplete run
|
|
429
|
+
archive_run "max-iterations-reached"
|
|
430
|
+
|
|
431
|
+
# Clean up config file
|
|
432
|
+
rm -f "$CONFIG_FILE"
|
|
433
|
+
|
|
434
|
+
echo ""
|
|
435
|
+
log_warn "Agent reached max iterations ($MAX_ITERATIONS) without completing all tasks."
|
|
436
|
+
log_info "Final status: $(get_work_status)"
|
|
437
|
+
log_info "Run 'bd ready --json' to see remaining tasks"
|
|
438
|
+
log_info "Run 'bd blocked --json' to see blocked tasks"
|
|
439
|
+
exit 1
|