@armstrongnate/april 0.0.3 → 0.0.4
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 +1 -1
- package/config.example.yaml +1 -4
- package/dist/config.js +2 -4
- package/dist/processes.js +1 -6
- package/dist/spawner.js +2 -6
- package/package.json +1 -1
- package/skills/issue-worker/SKILL.md +32 -15
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ april watches for GitHub issues assigned to you with a specific label, then spin
|
|
|
8
8
|
|
|
9
9
|
- [Node.js](https://nodejs.org/) >= 22
|
|
10
10
|
- [gh](https://cli.github.com/) (authenticated)
|
|
11
|
-
- The `gh-webhook` extension: `gh extension install cli/gh-webhook`
|
|
11
|
+
- The [`gh-webhook` extension](https://github.com/cli/gh-webhook): `gh extension install cli/gh-webhook`
|
|
12
12
|
- [tmux](https://github.com/tmux/tmux)
|
|
13
13
|
- [Claude Code](https://claude.ai/claude-code) CLI
|
|
14
14
|
|
package/config.example.yaml
CHANGED
|
@@ -2,10 +2,7 @@ assignee: "your-github-username"
|
|
|
2
2
|
label: "agent:todo"
|
|
3
3
|
claudeSkill: "issue-worker"
|
|
4
4
|
# claudeModel: "opus" # optional, defaults to opus
|
|
5
|
-
#
|
|
6
|
-
# - "Edit"
|
|
7
|
-
# - "Write"
|
|
8
|
-
# - "Bash(*)"
|
|
5
|
+
# claudePermissionMode: "auto" # optional, defaults to auto (others: default, acceptEdits, plan, bypassPermissions)
|
|
9
6
|
port: 7890
|
|
10
7
|
repos:
|
|
11
8
|
- owner: "org"
|
package/dist/config.js
CHANGED
|
@@ -63,9 +63,7 @@ export function loadConfig() {
|
|
|
63
63
|
const label = validateString(parsed, "label", "config");
|
|
64
64
|
const claudeSkill = validateString(parsed, "claudeSkill", "config");
|
|
65
65
|
const claudeModel = typeof parsed.claudeModel === "string" ? parsed.claudeModel.trim() : undefined;
|
|
66
|
-
const
|
|
67
|
-
? parsed.claudeAllowedTools.filter((t) => typeof t === "string" && t.trim().length > 0).map((t) => t.trim())
|
|
68
|
-
: undefined;
|
|
66
|
+
const claudePermissionMode = typeof parsed.claudePermissionMode === "string" ? parsed.claudePermissionMode.trim() : undefined;
|
|
69
67
|
const port = Number(parsed.port);
|
|
70
68
|
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
71
69
|
throw new Error(`config: "port" must be an integer between 1024 and 65535, got: ${parsed.port}`);
|
|
@@ -96,7 +94,7 @@ export function loadConfig() {
|
|
|
96
94
|
: undefined;
|
|
97
95
|
return { owner, name, path: resolvedPath, defaultBranch, slackChannel, postWorktreeHook };
|
|
98
96
|
});
|
|
99
|
-
const config = { assignee, label, claudeSkill, claudeModel,
|
|
97
|
+
const config = { assignee, label, claudeSkill, claudeModel, claudePermissionMode, port, repos };
|
|
100
98
|
log.info(`Config loaded: assignee=${assignee}, label=${label}, repos=${repos.map((r) => `${r.owner}/${r.name}`).join(", ")}`);
|
|
101
99
|
return config;
|
|
102
100
|
}
|
package/dist/processes.js
CHANGED
|
@@ -30,7 +30,6 @@ function cleanupStaleWebhooks(repoKey) {
|
|
|
30
30
|
const INITIAL_BACKOFF_MS = 1000;
|
|
31
31
|
const MAX_BACKOFF_MS = 30_000;
|
|
32
32
|
const UPTIME_RESET_MS = 60_000;
|
|
33
|
-
const MAX_CONSECUTIVE_FAILURES = 5;
|
|
34
33
|
const forwarders = [];
|
|
35
34
|
function spawnForwarder(config, repoKey, url) {
|
|
36
35
|
const state = {
|
|
@@ -82,12 +81,8 @@ function spawnForwarder(config, repoKey, url) {
|
|
|
82
81
|
state.consecutiveFailures++;
|
|
83
82
|
state.backoffMs = Math.min(state.backoffMs * 2, MAX_BACKOFF_MS);
|
|
84
83
|
}
|
|
85
|
-
if (state.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
86
|
-
log.error(`Forwarder for ${repoKey} failed ${MAX_CONSECUTIVE_FAILURES} consecutive times, giving up`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
84
|
log.warn(`Forwarder for ${repoKey} exited (code=${code}, signal=${signal}), ` +
|
|
90
|
-
`restarting in ${state.backoffMs}ms (
|
|
85
|
+
`restarting in ${state.backoffMs}ms (consecutive failures: ${state.consecutiveFailures})`);
|
|
91
86
|
setTimeout(() => start(), state.backoffMs);
|
|
92
87
|
});
|
|
93
88
|
}
|
package/dist/spawner.js
CHANGED
|
@@ -149,15 +149,11 @@ export function spawnClaude(config, repo, issue, worktreePath, sessionName) {
|
|
|
149
149
|
// Session does not exist, proceed
|
|
150
150
|
}
|
|
151
151
|
const model = config.claudeModel || "opus";
|
|
152
|
-
const
|
|
153
|
-
...(config.claudeAllowedTools ?? ["Read", "Search", "Edit", "Write", "Bash(*)"]),
|
|
154
|
-
...(repo.slackChannel ? ["mcp__plugin_slack_slack__*"] : []),
|
|
155
|
-
];
|
|
152
|
+
const permissionMode = config.claudePermissionMode || "auto";
|
|
156
153
|
const slackPart = repo.slackChannel ? ` Post the PR to Slack channel #${repo.slackChannel}.` : "";
|
|
157
154
|
const prompt = `/${config.claudeSkill} Read GitHub issue #${issue.number} on ${repo.owner}/${repo.name} using the gh CLI. Implement it and open a PR.${slackPart}`;
|
|
158
155
|
log.debug(`Prompt: ${prompt}`);
|
|
159
|
-
const
|
|
160
|
-
const claudeCommand = `claude --model ${model} ${allowedToolsArgs}`;
|
|
156
|
+
const claudeCommand = `claude --model ${model} --permission-mode ${permissionMode}`;
|
|
161
157
|
log.info(`Spawning tmux session "${sessionName}" with claude`);
|
|
162
158
|
execSync(`tmux new-session -d -s ${JSON.stringify(sessionName)} -c ${JSON.stringify(worktreePath)} ${JSON.stringify(claudeCommand)}`);
|
|
163
159
|
// Send the prompt via send-keys after Claude starts
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: issue-worker
|
|
3
|
-
description: Autonomously work a GitHub issue end-to-end — read, implement,
|
|
3
|
+
description: Autonomously work a GitHub issue end-to-end — read, implement, open a PR, and monitor CI/review feedback.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# issue-worker
|
|
7
7
|
|
|
8
|
-
You have been assigned a GitHub issue. Work it to completion autonomously. Do not stop to ask for approval or confirmation — go straight from reading the issue to opening a PR.
|
|
8
|
+
You have been assigned a GitHub issue. Work it to completion autonomously. Do not stop to ask for approval or confirmation — go straight from reading the issue to opening a PR, then monitor and respond to CI failures and review feedback.
|
|
9
9
|
|
|
10
10
|
## 1. Read the issue
|
|
11
11
|
|
|
@@ -25,16 +25,17 @@ gh issue view {issue_number} --repo {owner}/{repo} --comments
|
|
|
25
25
|
- Write or update tests as appropriate
|
|
26
26
|
- Ensure the code builds/lints/passes tests
|
|
27
27
|
|
|
28
|
-
## 4. Review
|
|
28
|
+
## 4. Review
|
|
29
29
|
|
|
30
|
-
Run `
|
|
30
|
+
Run `/simplify` to review your changes for reuse, quality, and efficiency, and fix anything it surfaces.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
|
|
32
|
+
Then do a quick manual pass on things `/simplify` won't catch:
|
|
33
|
+
|
|
34
|
+
- **Correctness:** Does the change fully address the issue? Any missing edge cases?
|
|
35
|
+
- **Tests:** Are the tests meaningful, or are they just asserting on mocks?
|
|
36
|
+
- **Cleanup:** Any leftover debug code, TODOs, or commented-out lines?
|
|
37
|
+
|
|
38
|
+
Fix anything you find before moving on.
|
|
38
39
|
|
|
39
40
|
## 5. Commit, push, and open a PR
|
|
40
41
|
|
|
@@ -42,12 +43,28 @@ Run `git diff` and review your own changes. Fix any issues before committing.
|
|
|
42
43
|
gh pr create --title "..." --body "..."
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
## 6. Post to Slack (if instructed)
|
|
47
|
+
|
|
48
|
+
If the prompt specifies a Slack channel, use the Slack MCP tool to post a message with a link to the PR. Format: `<pr_url|PR> repo-name: title of the pr`
|
|
49
|
+
|
|
50
|
+
## 7. Monitor CI and review feedback
|
|
51
|
+
|
|
52
|
+
After creating the PR, monitor it until all checks pass and all review feedback is addressed.
|
|
53
|
+
|
|
54
|
+
Loop:
|
|
55
|
+
1. Sleep for 3 minutes (`sleep 180`)
|
|
56
|
+
2. Check CI status: `gh pr checks {pr_number} --repo {owner}/{repo}`
|
|
57
|
+
3. If any checks failed, read the failure logs, fix the issue, commit, and push
|
|
58
|
+
4. Check for review comments: `gh pr view {pr_number} --repo {owner}/{repo} --comments`
|
|
59
|
+
5. If there are new or unresolved comments, address them, commit, and push
|
|
60
|
+
6. Repeat from step 1
|
|
61
|
+
|
|
62
|
+
Stop when:
|
|
63
|
+
- All CI checks pass AND
|
|
64
|
+
- No unresolved review comments remain
|
|
65
|
+
|
|
66
|
+
Once everything is green, update the issue labels:
|
|
46
67
|
|
|
47
68
|
```
|
|
48
69
|
gh issue edit {issue_number} --repo {owner}/{repo} --add-label agent:review --remove-label agent:wip
|
|
49
70
|
```
|
|
50
|
-
|
|
51
|
-
## 6. Post to Slack (if instructed)
|
|
52
|
-
|
|
53
|
-
If the prompt specifies a Slack channel, use the Slack MCP tool to post a message with a link to the PR. Format: `<pr_url|PR> title of the pr`
|