bueller-wheel 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -20
- package/dist/index.js +178 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Bueller Wheel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Life moves pretty fast. If you don't stop and look around once in a while, you could miss it.
|
|
4
|
+
|
|
5
|
+
This is a headless issue processor that runs in a loop and uses Claude to resolve issues or ticket files written in markdown. It plays nicely with Claude Code and uses the sames settings config.
|
|
4
6
|
|
|
5
7
|
## Quick Start
|
|
6
8
|
|
|
@@ -10,28 +12,26 @@ mkdir -p issues/open
|
|
|
10
12
|
echo "@user: Please create a test file with 'Hello World'" > issues/open/p0-100-my-task.md
|
|
11
13
|
|
|
12
14
|
# Run bueller-wheel to complete the task
|
|
13
|
-
npx bueller-wheel
|
|
15
|
+
npx bueller-wheel --run
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
## Why?
|
|
17
19
|
|
|
18
|
-
Claude
|
|
19
|
-
|
|
20
|
-
**Issues as directory-based threads** - Each task becomes a reviewable conversation stored as markdown that gets moved along a file-based "kanban" board. This solves a few problems:
|
|
21
|
-
- **Code review**: When Claude burns through multiple tasks, it's hard to know what happened. The issues structure creates discrete, reviewable units of work.
|
|
22
|
-
- **Iteration without re-prompting**: Markdown files are structured as a back-and-forth between you and Claude, which lets you append follow-ups naturally without starting over.
|
|
23
|
-
- **Human-editable prompts**: Storing issues as simple markdown (not JSON or a database) makes it easy to edit, append, or retroactively clean up conversations to improve prompting.
|
|
20
|
+
You want Claude to autonomously work on a large pile of issues while you go on a day trip into the city. Bueller Wheel helps tackle several issues you might encounter:
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
- **Claude stops processing after a few issues**: Claude tends to stop processing after completing a few tasks. Bueller Wheel keeps prompting Claude to work until all of the issues have been resolved.
|
|
23
|
+
- **Claude forgets what it's doing**: As Claude uses up its context window, it tends to forget what it was working on. Bueller Wheel runs Claude with a fresh context window and prompt for each issue.
|
|
24
|
+
- **You forget what Claude was doing**: If you successfully get Claude to work on a large number of tasks, you end up with a pile of code to review. Bueller Wheel structures each issue as a discrete reviewable chunk of work, in a format amenable to multiple iterations of feedback between you and Claude.
|
|
25
|
+
- **Claude keeps making the same mistakes**: An agent that forgets its history is doomed to repeat it. Bueller Wheel sets up an FAQ directory for Claude to speed up resolution of frequent pitfalls.
|
|
26
26
|
|
|
27
|
-
**Note
|
|
27
|
+
**Note**: Bueller Wheel is not a full-fledged task management system. It has no concept of assignment or dependency apart from linear file ordering. The sweet spot for this tool is **solo developers working on a single branch**. That said, you can make [parallel branches and agents](#working-with-multiple-branches) work.
|
|
28
28
|
|
|
29
29
|
## How It Works
|
|
30
30
|
|
|
31
31
|
**The Processing Loop**
|
|
32
32
|
|
|
33
33
|
1. Bueller Wheel finds the next issue in `issues/open/` (sorted alphabetically by filename)
|
|
34
|
-
2. Claude
|
|
34
|
+
2. Claude reads the issue and works on the task
|
|
35
35
|
3. Claude appends its work summary to the issue file
|
|
36
36
|
4. Claude decides the outcome:
|
|
37
37
|
- **CONTINUE**: Keep working (stays in `open/`)
|
|
@@ -45,7 +45,7 @@ Claude Code agents work better when they focus on one thing at a time. Bueller W
|
|
|
45
45
|
- Move issues from `review/` or `stuck/` back to `open/` if more work is required
|
|
46
46
|
- Delete issues from `review/` when done reviewing, or archive them however you want
|
|
47
47
|
|
|
48
|
-
**Each iteration is a fresh Claude
|
|
48
|
+
**Each iteration is a fresh Claude session** - no memory between iterations, which keeps context focused.
|
|
49
49
|
|
|
50
50
|
**Inherits your project's Claude Code setup** - `bueller-wheel` uses the Anthropic API credential from whichever user you're logged in as. It inherits the same `.claude/settings.json` or `.claude/settings.local.json` as the Claude Code project it's used in. Whatever permissions apply to your regular `claude` CLI should also apply to `bueller-wheel`, with the exception that `bueller-wheel` starts in "accept edits" mode.
|
|
51
51
|
|
|
@@ -139,18 +139,37 @@ The `open/` directory acts as an inbox for the agent on the current branch. The
|
|
|
139
139
|
## CLI Options
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
|
-
|
|
142
|
+
# Start the agent loop with various options
|
|
143
|
+
npx bueller-wheel --run
|
|
144
|
+
npx bueller-wheel --git
|
|
145
|
+
npx bueller-wheel --max 50
|
|
146
|
+
npx bueller-wheel --continue "fix the bug"
|
|
147
|
+
|
|
148
|
+
# Summarize issues
|
|
149
|
+
npx bueller-wheel --summarize p1-003-task.md
|
|
150
|
+
npx bueller-wheel --summarize p1-003.md p2-005.md --index 1
|
|
151
|
+
|
|
152
|
+
# Combine with other options
|
|
153
|
+
npx bueller-wheel --run --issues-dir ./my-issues --faq-dir ./my-faq
|
|
154
|
+
npx bueller-wheel --max 50 --git --prompt ./my-prompt.md
|
|
143
155
|
|
|
144
156
|
# Or if installed globally
|
|
145
|
-
bueller-wheel --
|
|
157
|
+
bueller-wheel --run
|
|
158
|
+
bueller-wheel --git
|
|
146
159
|
```
|
|
147
160
|
|
|
161
|
+
**Run Commands** (one required):
|
|
162
|
+
- `--run`: Explicitly start the agent loop with defaults
|
|
163
|
+
- `--git`: Enable automatic git commits and start the loop
|
|
164
|
+
- `--max <number>`: Start with maximum N iterations (default: `25`)
|
|
165
|
+
- `--continue [prompt]`: Continue from previous session. Optional prompt defaults to "continue" if not provided
|
|
166
|
+
- `--summarize <issue...>`: Display abbreviated summaries of issue conversation history
|
|
167
|
+
|
|
168
|
+
**Configuration Options**:
|
|
148
169
|
- `--issues-dir <path>`: Issues directory (default: `./issues`)
|
|
149
170
|
- `--faq-dir <path>`: FAQ directory (default: `./faq`)
|
|
150
|
-
- `--max-iterations <number>`: Maximum iterations (default: `25`)
|
|
151
|
-
- `--git-commit`: Enable automatic git commits after each iteration (default: disabled)
|
|
152
171
|
- `--prompt <path>`: Path to custom prompt template file (default: `<issues-dir>/prompt.md`)
|
|
153
|
-
- `--
|
|
172
|
+
- `--index <N>` or `--index <M,N>`: Expand specific messages when using `--summarize` (see below)
|
|
154
173
|
|
|
155
174
|
### Custom Prompt Templates
|
|
156
175
|
|
|
@@ -212,7 +231,7 @@ Note that only the immediate prior iteration is continued. The next iteration wi
|
|
|
212
231
|
|
|
213
232
|
### Git Auto-Commit
|
|
214
233
|
|
|
215
|
-
When `--git
|
|
234
|
+
When `--git` is enabled, Bueller Wheel will automatically create a git commit after each iteration where work was done on an issue.
|
|
216
235
|
|
|
217
236
|
The commit message format includes the issue ID (the filename minus the `.md`) and status:
|
|
218
237
|
```
|
|
@@ -221,13 +240,68 @@ p0-002-git in progress
|
|
|
221
240
|
p0-002-git stuck
|
|
222
241
|
p0-002-git unknown
|
|
223
242
|
```
|
|
243
|
+
|
|
244
|
+
### Issue Summarization
|
|
245
|
+
|
|
246
|
+
The `--summarize` command provides a quick way to review issue conversation history without opening files. This is especially useful for:
|
|
247
|
+
- Quickly understanding what happened in an issue
|
|
248
|
+
- Reviewing multiple issues at once
|
|
249
|
+
- Checking the status and progress of work
|
|
250
|
+
|
|
251
|
+
**Basic Usage:**
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Summarize a single issue (by filename - searches across open/, review/, stuck/)
|
|
255
|
+
npx bueller-wheel --summarize p1-003-task.md
|
|
256
|
+
|
|
257
|
+
# Summarize by partial filename
|
|
258
|
+
npx bueller-wheel --summarize p1-003.md
|
|
259
|
+
|
|
260
|
+
# Summarize with full path
|
|
261
|
+
npx bueller-wheel --summarize /path/to/issues/open/p1-003-task.md
|
|
262
|
+
|
|
263
|
+
# Summarize multiple issues
|
|
264
|
+
npx bueller-wheel --summarize p1-003.md p1-004.md p2-001.md
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Summary Format:**
|
|
268
|
+
|
|
269
|
+
By default, summaries show:
|
|
270
|
+
- Issue filename and status (open/review/stuck)
|
|
271
|
+
- First message: up to 300 characters
|
|
272
|
+
- Middle messages: up to 80 characters each (abbreviated)
|
|
273
|
+
- Last message: up to 300 characters
|
|
274
|
+
|
|
275
|
+
**Expanding Messages:**
|
|
276
|
+
|
|
277
|
+
Use `--index` to expand specific messages to their full content:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# Expand a single message at index 2
|
|
281
|
+
npx bueller-wheel --summarize p1-003.md --index 2
|
|
282
|
+
|
|
283
|
+
# Expand a range of messages (indices 1 through 3)
|
|
284
|
+
npx bueller-wheel --summarize p1-003.md --index 1,3
|
|
285
|
+
|
|
286
|
+
# Works with multiple issues (expands same indices for all)
|
|
287
|
+
npx bueller-wheel --summarize p1-003.md p1-004.md --index 0,2
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Note:** Message indices are 0-based (first message is index 0).
|
|
291
|
+
|
|
224
292
|
## Development
|
|
225
293
|
|
|
226
|
-
`pnpm run dev` will execute the current `src/index.ts` script file with whatever args you pass to it.
|
|
294
|
+
`pnpm run dev` will execute the current `src/index.ts` script file with whatever args you pass to it. For example:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
pnpm run dev -- --run
|
|
298
|
+
pnpm run dev -- --git
|
|
299
|
+
pnpm run dev -- --max 10
|
|
300
|
+
```
|
|
227
301
|
|
|
228
302
|
## End-to-End Testing
|
|
229
303
|
|
|
230
|
-
**These tests use your actual
|
|
304
|
+
**These tests use your actual Anthropic / Claude credentials!**
|
|
231
305
|
|
|
232
306
|
```bash
|
|
233
307
|
# Run all tests
|
package/dist/index.js
CHANGED
|
@@ -3,24 +3,42 @@ import { execSync } from 'node:child_process';
|
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
6
|
+
import { expandMessages, formatIssueSummary, resolveIssueReference, summarizeIssue, } from './issue-summarize.js';
|
|
7
|
+
// Colors for output
|
|
8
|
+
const colors = {
|
|
9
|
+
red: '\x1b[0;31m',
|
|
10
|
+
green: '\x1b[0;32m',
|
|
11
|
+
yellow: '\x1b[1;33m',
|
|
12
|
+
blue: '\x1b[0;34m',
|
|
13
|
+
cyan: '\x1b[0;36m',
|
|
14
|
+
reset: '\x1b[0m',
|
|
15
|
+
};
|
|
6
16
|
const ISSUE_DIR_OPEN = 'open';
|
|
7
17
|
const ISSUE_DIR_REVIEW = 'review';
|
|
8
18
|
const ISSUE_DIR_STUCK = 'stuck';
|
|
9
19
|
function showHelp() {
|
|
10
20
|
console.log(`
|
|
11
|
-
Bueller - Headless Claude Code Issue Processor
|
|
21
|
+
Bueller Wheel - Headless Claude Code Issue Processor
|
|
12
22
|
|
|
13
23
|
USAGE:
|
|
14
|
-
bueller [OPTIONS]
|
|
24
|
+
bueller-wheel run [OPTIONS] Start the agent loop
|
|
25
|
+
bueller-wheel issue ISSUE... [OPTIONS] View issue summaries
|
|
26
|
+
|
|
27
|
+
COMMANDS:
|
|
28
|
+
run Start the agent loop to process issues
|
|
29
|
+
issue ISSUE... Summarize one or more issues (accepts file paths or filenames)
|
|
15
30
|
|
|
16
31
|
OPTIONS:
|
|
17
32
|
--help Show this help message and exit
|
|
33
|
+
--git Enable automatic git commits (on by default, run command only)
|
|
34
|
+
--no-git Disable automatic git commits (run command only)
|
|
35
|
+
--max N Maximum number of iterations to run (default: 25, run command only)
|
|
36
|
+
--continue [PROMPT] Continue from previous session (default prompt: "continue", run command only)
|
|
37
|
+
--index N Expand message at index N (issue command only)
|
|
38
|
+
--index M,N Expand message range from M to N (issue command only)
|
|
18
39
|
--issues-dir DIR Directory containing issue queue (default: ./issues)
|
|
19
40
|
--faq-dir DIR Directory containing FAQ/troubleshooting guides (default: ./faq)
|
|
20
|
-
--
|
|
21
|
-
--git, --git-commit Enable automatic git commits after each iteration
|
|
22
|
-
--prompt FILE Custom prompt template file (default: ./issues/prompt.md)
|
|
23
|
-
--continue [PROMPT] Continue from previous session (default prompt: "continue")
|
|
41
|
+
--prompt FILE Custom prompt template file (default: ./issues/prompt.md, run command only)
|
|
24
42
|
|
|
25
43
|
DIRECTORY STRUCTURE:
|
|
26
44
|
issues/
|
|
@@ -39,10 +57,14 @@ ISSUE FILE FORMAT:
|
|
|
39
57
|
p2: Non-blocking follow-up
|
|
40
58
|
|
|
41
59
|
EXAMPLES:
|
|
42
|
-
bueller
|
|
43
|
-
bueller
|
|
44
|
-
bueller --max
|
|
45
|
-
bueller --
|
|
60
|
+
bueller-wheel run
|
|
61
|
+
bueller-wheel run --no-git
|
|
62
|
+
bueller-wheel run --max 50
|
|
63
|
+
bueller-wheel run --continue "fix the bug"
|
|
64
|
+
bueller-wheel run --issues-dir ./my-issues --faq-dir ./my-faq
|
|
65
|
+
bueller-wheel issue p1-003-read-helper-002.md
|
|
66
|
+
bueller-wheel issue p1-003 p2-005 --index 1
|
|
67
|
+
bueller-wheel issue /path/to/issue.md --index 0,2
|
|
46
68
|
|
|
47
69
|
For more information, visit: https://github.com/anthropics/bueller
|
|
48
70
|
`);
|
|
@@ -50,18 +72,52 @@ For more information, visit: https://github.com/anthropics/bueller
|
|
|
50
72
|
function parseArgs() {
|
|
51
73
|
const args = process.argv.slice(2);
|
|
52
74
|
// Check for help flag first
|
|
53
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
75
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
54
76
|
showHelp();
|
|
55
77
|
process.exit(0);
|
|
56
78
|
}
|
|
79
|
+
// First argument should be the command
|
|
80
|
+
const command = args[0];
|
|
81
|
+
if (command !== 'run' && command !== 'issue') {
|
|
82
|
+
console.error(`${colors.red}Error: Unknown command "${command}". Use "run" or "issue".${colors.reset}\n`);
|
|
83
|
+
showHelp();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
// Define recognized flags
|
|
87
|
+
const recognizedFlags = new Set([
|
|
88
|
+
'--issues-dir',
|
|
89
|
+
'--faq-dir',
|
|
90
|
+
'--max',
|
|
91
|
+
'--git',
|
|
92
|
+
'--no-git',
|
|
93
|
+
'--prompt',
|
|
94
|
+
'--continue',
|
|
95
|
+
'--index',
|
|
96
|
+
'--help',
|
|
97
|
+
'-h',
|
|
98
|
+
]);
|
|
99
|
+
// Check for unrecognized flags
|
|
100
|
+
for (const arg of args.slice(1)) {
|
|
101
|
+
// Check if this looks like a flag (starts with -)
|
|
102
|
+
if (arg.startsWith('-')) {
|
|
103
|
+
if (!recognizedFlags.has(arg)) {
|
|
104
|
+
console.error(`${colors.red}Error: Unrecognized flag: ${arg}${colors.reset}\n`);
|
|
105
|
+
showHelp();
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
57
110
|
let issuesDir = './issues';
|
|
58
111
|
let faqDir = './faq';
|
|
59
112
|
let maxIterations = 25;
|
|
60
|
-
let gitCommit =
|
|
113
|
+
let gitCommit = true;
|
|
61
114
|
let promptFile = path.join('./issues', 'prompt.md');
|
|
62
115
|
let continueMode = false;
|
|
63
116
|
let continuePrompt = 'continue';
|
|
64
|
-
|
|
117
|
+
const issueReferences = [];
|
|
118
|
+
let issueIndex;
|
|
119
|
+
// Parse arguments starting from index 1 (skip the command)
|
|
120
|
+
for (let i = 1; i < args.length; i++) {
|
|
65
121
|
if (args[i] === '--issues-dir' && i + 1 < args.length) {
|
|
66
122
|
issuesDir = args[++i];
|
|
67
123
|
// Update default prompt file location if issues-dir is changed
|
|
@@ -72,12 +128,15 @@ function parseArgs() {
|
|
|
72
128
|
else if (args[i] === '--faq-dir' && i + 1 < args.length) {
|
|
73
129
|
faqDir = args[++i];
|
|
74
130
|
}
|
|
75
|
-
else if (args[i] === '--max
|
|
131
|
+
else if (args[i] === '--max' && i + 1 < args.length) {
|
|
76
132
|
maxIterations = parseInt(args[++i], 10);
|
|
77
133
|
}
|
|
78
|
-
else if (args[i] === '--git'
|
|
134
|
+
else if (args[i] === '--git') {
|
|
79
135
|
gitCommit = true;
|
|
80
136
|
}
|
|
137
|
+
else if (args[i] === '--no-git') {
|
|
138
|
+
gitCommit = false;
|
|
139
|
+
}
|
|
81
140
|
else if (args[i] === '--prompt' && i + 1 < args.length) {
|
|
82
141
|
promptFile = args[++i];
|
|
83
142
|
}
|
|
@@ -88,6 +147,21 @@ function parseArgs() {
|
|
|
88
147
|
continuePrompt = args[++i];
|
|
89
148
|
}
|
|
90
149
|
}
|
|
150
|
+
else if (args[i] === '--index' && i + 1 < args.length) {
|
|
151
|
+
issueIndex = args[++i];
|
|
152
|
+
}
|
|
153
|
+
else if (!args[i].startsWith('--')) {
|
|
154
|
+
// Non-flag argument - collect as issue reference for issue command
|
|
155
|
+
if (command === 'issue') {
|
|
156
|
+
issueReferences.push(args[i]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Validate issue command
|
|
161
|
+
if (command === 'issue' && issueReferences.length === 0) {
|
|
162
|
+
console.error(`${colors.red}Error: "issue" command requires at least one issue reference.${colors.reset}\n`);
|
|
163
|
+
showHelp();
|
|
164
|
+
process.exit(1);
|
|
91
165
|
}
|
|
92
166
|
return {
|
|
93
167
|
issuesDir,
|
|
@@ -97,6 +171,9 @@ function parseArgs() {
|
|
|
97
171
|
promptFile,
|
|
98
172
|
continueMode,
|
|
99
173
|
continuePrompt,
|
|
174
|
+
command: command,
|
|
175
|
+
issueReferences,
|
|
176
|
+
issueIndex,
|
|
100
177
|
};
|
|
101
178
|
}
|
|
102
179
|
async function ensureDirectories(issuesDir, faqDir) {
|
|
@@ -136,7 +213,7 @@ function gitCommit(issueFile, status) {
|
|
|
136
213
|
encoding: 'utf-8',
|
|
137
214
|
}).trim();
|
|
138
215
|
if (!untrackedFiles) {
|
|
139
|
-
console.log(
|
|
216
|
+
console.log(`${colors.cyan}No changes to commit${colors.reset}`);
|
|
140
217
|
return;
|
|
141
218
|
}
|
|
142
219
|
}
|
|
@@ -150,10 +227,10 @@ function gitCommit(issueFile, status) {
|
|
|
150
227
|
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
151
228
|
stdio: 'inherit',
|
|
152
229
|
});
|
|
153
|
-
console.log(
|
|
230
|
+
console.log(`${colors.green}\nGit commit created: ${commitMessage}${colors.reset}`);
|
|
154
231
|
}
|
|
155
232
|
catch (error) {
|
|
156
|
-
console.error(
|
|
233
|
+
console.error(`${colors.red}Failed to create git commit: ${String(error)}${colors.reset}`);
|
|
157
234
|
}
|
|
158
235
|
}
|
|
159
236
|
function getDefaultPromptTemplate() {
|
|
@@ -205,9 +282,16 @@ Here is a summary of the work I have done:
|
|
|
205
282
|
|
|
206
283
|
Your issue file: [ISSUE_FILE_PATH]
|
|
207
284
|
|
|
285
|
+
Issue files may be long. Use CLI commands to read:
|
|
286
|
+
- To summarize: \`npx bueller-wheel issue [ISSUE_FILE_PATH]\`
|
|
287
|
+
- To expand: \`npx bueller-wheel issue [ISSUE_FILE_PATH] --index <start>,<end>\`
|
|
288
|
+
|
|
208
289
|
1. **Read the issue**: Parse the conversation history in [ISSUE_FILE_PATH] to understand the task
|
|
209
290
|
2. **Work on the task**: Do what the issue requests. When encountering issues, always check for a relevant guide in [FAQ_DIR]/ first.
|
|
210
|
-
3. **
|
|
291
|
+
3. **Verify**: Verify the following pass:
|
|
292
|
+
- [ ] \`pnpm run lint:fix\`
|
|
293
|
+
- [ ] \`pnpm run typecheck\`
|
|
294
|
+
4. **Append your response**: Add your summary to [ISSUE_FILE_PATH] using this format:
|
|
211
295
|
\`\`\`
|
|
212
296
|
---
|
|
213
297
|
|
|
@@ -219,7 +303,7 @@ Your issue file: [ISSUE_FILE_PATH]
|
|
|
219
303
|
- Item 3
|
|
220
304
|
\`\`\`
|
|
221
305
|
|
|
222
|
-
|
|
306
|
+
5. **Decide the outcome**: Choose ONE of the following actions:
|
|
223
307
|
|
|
224
308
|
a. **CONTINUE** - You made progress but the task isn't complete yet
|
|
225
309
|
- Leave the issue in \`[ISSUES_DIR]/[ISSUE_DIR_OPEN]/\` for the next iteration
|
|
@@ -250,18 +334,22 @@ Your issue file: [ISSUE_FILE_PATH]
|
|
|
250
334
|
|
|
251
335
|
**Critical:** ALWAYS check the FAQ directory ([FAQ_DIR]/) to see if there is a guide when you encounter a problem.
|
|
252
336
|
|
|
337
|
+
## Adding to the FAQ
|
|
338
|
+
|
|
339
|
+
Consider adding a **CONCISE** FAQ in [FAQ_DIR]/ for non-obvious solutions, recurring issues, or multi-step troubleshooting that would help future agents. Skip trivial/one-off problems or topics already documented.
|
|
340
|
+
|
|
253
341
|
Now, please process the issue at [ISSUE_FILE_PATH].`;
|
|
254
342
|
}
|
|
255
343
|
async function loadOrCreatePromptTemplate(promptFile) {
|
|
256
344
|
// If prompt file exists, load it
|
|
257
345
|
try {
|
|
258
346
|
await fs.access(promptFile);
|
|
259
|
-
console.log(
|
|
347
|
+
console.log(`${colors.cyan}Loading prompt template from: ${promptFile}${colors.reset}`);
|
|
260
348
|
return await fs.readFile(promptFile, 'utf-8');
|
|
261
349
|
}
|
|
262
350
|
catch {
|
|
263
351
|
// Otherwise, create the default prompt template
|
|
264
|
-
console.log(
|
|
352
|
+
console.log(`${colors.yellow}Prompt file not found. Creating default template at: ${promptFile}${colors.reset}`);
|
|
265
353
|
const defaultTemplate = getDefaultPromptTemplate();
|
|
266
354
|
// Ensure the directory exists
|
|
267
355
|
const promptDir = path.dirname(promptFile);
|
|
@@ -289,7 +377,7 @@ function buildSystemPrompt(template, issuesDir, faqDir, issueFile) {
|
|
|
289
377
|
}
|
|
290
378
|
function logToolUse(block) {
|
|
291
379
|
process.stdout.write('\n');
|
|
292
|
-
process.stdout.write(
|
|
380
|
+
process.stdout.write(`${colors.cyan}[${block.name}]${colors.reset} `);
|
|
293
381
|
switch (block.name.toLowerCase()) {
|
|
294
382
|
case 'read':
|
|
295
383
|
case 'write':
|
|
@@ -302,18 +390,33 @@ function logToolUse(block) {
|
|
|
302
390
|
case 'glob':
|
|
303
391
|
process.stdout.write(`${block.input?.pattern}`);
|
|
304
392
|
break;
|
|
393
|
+
case 'grep': {
|
|
394
|
+
const pattern = block.input?.pattern;
|
|
395
|
+
const glob = block.input?.glob;
|
|
396
|
+
const path = block.input?.path;
|
|
397
|
+
if (pattern) {
|
|
398
|
+
process.stdout.write(`${pattern}`);
|
|
399
|
+
}
|
|
400
|
+
if (glob) {
|
|
401
|
+
process.stdout.write(` (${glob})`);
|
|
402
|
+
}
|
|
403
|
+
if (path) {
|
|
404
|
+
process.stdout.write(` (${path})`);
|
|
405
|
+
}
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
305
408
|
case 'todowrite': {
|
|
306
409
|
for (const todo of block.input?.todos ?? []) {
|
|
307
410
|
process.stdout.write('\n');
|
|
308
411
|
switch (todo.status) {
|
|
309
412
|
case 'in_progress':
|
|
310
|
-
process.stdout.write(
|
|
413
|
+
process.stdout.write(`${colors.yellow}⧖${colors.reset}`);
|
|
311
414
|
break;
|
|
312
415
|
case 'pending':
|
|
313
416
|
process.stdout.write('☐');
|
|
314
417
|
break;
|
|
315
418
|
case 'completed':
|
|
316
|
-
process.stdout.write(
|
|
419
|
+
process.stdout.write(`${colors.green}✓${colors.reset}`);
|
|
317
420
|
break;
|
|
318
421
|
default:
|
|
319
422
|
process.stdout.write(todo.status);
|
|
@@ -361,24 +464,62 @@ function logSDKMessage(item) {
|
|
|
361
464
|
async function runAgent(options) {
|
|
362
465
|
const { template, issuesDir, faqDir, issueFile, continueMode, continuePrompt } = options;
|
|
363
466
|
const systemPrompt = buildSystemPrompt(template, issuesDir, faqDir, issueFile);
|
|
364
|
-
console.log(
|
|
467
|
+
console.log(`${colors.blue}\n--- Starting agent ---${colors.reset}`);
|
|
365
468
|
const stream = query({
|
|
366
469
|
prompt: continueMode ? continuePrompt : systemPrompt,
|
|
367
470
|
options: {
|
|
368
471
|
settingSources: ['local', 'project', 'user'],
|
|
369
472
|
permissionMode: 'acceptEdits',
|
|
370
473
|
continue: continueMode,
|
|
474
|
+
canUseTool: async (toolName, input) => {
|
|
475
|
+
console.log(`${colors.red}Auto-denied tool:${colors.reset} ${toolName} ${String(input?.['command'] ?? input?.['file_path'] ?? '')}`);
|
|
476
|
+
return {
|
|
477
|
+
behavior: 'deny',
|
|
478
|
+
message: `You are running autonomously. The user cannot grant permission. Find a workaround or mark the issue as stuck. Check ${faqDir}/ to see if this is covered.`,
|
|
479
|
+
};
|
|
480
|
+
},
|
|
371
481
|
},
|
|
372
482
|
});
|
|
373
483
|
for await (const item of stream) {
|
|
374
484
|
logSDKMessage(item);
|
|
375
485
|
}
|
|
376
|
-
console.log(
|
|
486
|
+
console.log(`${colors.blue}\n--- Agent finished ---${colors.reset}`);
|
|
487
|
+
}
|
|
488
|
+
async function runIssue(config) {
|
|
489
|
+
for (const issueRef of config.issueReferences) {
|
|
490
|
+
// Normalize issue reference - add .md extension if missing
|
|
491
|
+
let normalizedRef = issueRef;
|
|
492
|
+
if (!issueRef.endsWith('.md')) {
|
|
493
|
+
normalizedRef = `${issueRef}.md`;
|
|
494
|
+
}
|
|
495
|
+
const located = await resolveIssueReference(normalizedRef, config.issuesDir);
|
|
496
|
+
if (!located) {
|
|
497
|
+
console.error(`${colors.red}Error: Could not find issue: ${issueRef}${colors.reset}\n`);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
let summary = await summarizeIssue(located);
|
|
502
|
+
// Apply index expansion if specified
|
|
503
|
+
if (config.issueIndex) {
|
|
504
|
+
summary = expandMessages(summary, config.issueIndex);
|
|
505
|
+
}
|
|
506
|
+
const formatted = formatIssueSummary(summary, config.issueIndex);
|
|
507
|
+
console.log(formatted);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
console.error(`${colors.red}Error summarizing ${issueRef}: ${String(error)}${colors.reset}\n`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
377
513
|
}
|
|
378
514
|
async function main() {
|
|
379
515
|
const config = parseArgs();
|
|
380
|
-
|
|
381
|
-
|
|
516
|
+
// Handle issue command
|
|
517
|
+
if (config.command === 'issue') {
|
|
518
|
+
await runIssue(config);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
console.log(`${colors.cyan}Bueller? Bueller?${colors.reset}`);
|
|
522
|
+
console.log(`${colors.cyan}-----------------${colors.reset}`);
|
|
382
523
|
console.log(`Issues directory: ${config.issuesDir}`);
|
|
383
524
|
console.log(`FAQ directory: ${config.faqDir}`);
|
|
384
525
|
console.log(`Max iterations: ${config.maxIterations}`);
|
|
@@ -393,21 +534,23 @@ async function main() {
|
|
|
393
534
|
let iteration = 0;
|
|
394
535
|
while (iteration < config.maxIterations) {
|
|
395
536
|
iteration++;
|
|
396
|
-
console.log(
|
|
537
|
+
console.log(`${colors.yellow}\n### Iteration ${iteration} ###${colors.reset}\n`);
|
|
397
538
|
const openIssues = await getOpenIssues(config.issuesDir);
|
|
398
539
|
if (openIssues.length === 0) {
|
|
399
|
-
console.log(
|
|
540
|
+
console.log(`${colors.green}No more issues in open/. Exiting.${colors.reset}`);
|
|
400
541
|
break;
|
|
401
542
|
}
|
|
402
543
|
console.log(`Found ${openIssues.length} open issue(s)`);
|
|
403
544
|
console.log(`Next issue: ${openIssues[0]}`);
|
|
404
545
|
const currentIssue = openIssues[0];
|
|
546
|
+
// Only use continue mode on the first iteration
|
|
547
|
+
const isFirstIteration = iteration === 1;
|
|
405
548
|
await runAgent({
|
|
406
549
|
template: promptTemplate,
|
|
407
550
|
issuesDir: config.issuesDir,
|
|
408
551
|
faqDir: config.faqDir,
|
|
409
552
|
issueFile: currentIssue,
|
|
410
|
-
continueMode: config.continueMode,
|
|
553
|
+
continueMode: config.continueMode && isFirstIteration,
|
|
411
554
|
continuePrompt: config.continuePrompt,
|
|
412
555
|
});
|
|
413
556
|
// Auto-commit if enabled and there's a current issue
|
|
@@ -451,12 +594,12 @@ async function main() {
|
|
|
451
594
|
}
|
|
452
595
|
}
|
|
453
596
|
if (iteration >= config.maxIterations) {
|
|
454
|
-
console.log(
|
|
597
|
+
console.log(`${colors.yellow}\nReached maximum iterations (${config.maxIterations}). Exiting.${colors.reset}`);
|
|
455
598
|
}
|
|
456
|
-
console.log(
|
|
599
|
+
console.log(`${colors.green}\nDone!${colors.reset}`);
|
|
457
600
|
}
|
|
458
601
|
main().catch((error) => {
|
|
459
|
-
console.error(
|
|
602
|
+
console.error(`${colors.red}Error:${colors.reset}`, error);
|
|
460
603
|
process.exit(1);
|
|
461
604
|
});
|
|
462
605
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED