@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 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
 
@@ -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
- # claudeAllowedTools: # optional, defaults to Edit, Write, Bash(*)
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 claudeAllowedTools = Array.isArray(parsed.claudeAllowedTools)
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, claudeAllowedTools, port, repos };
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 (failure ${state.consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
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 allowedTools = [
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 allowedToolsArgs = allowedTools.map((t) => `--allowedTools '${t}'`).join(" ");
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@armstrongnate/april",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "She does all the work so you don't have to. Watches GitHub issues and spawns Claude Code sessions to work them.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: issue-worker
3
- description: Autonomously work a GitHub issue end-to-end — read, implement, and open a PR with no human input required.
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 and simplify
28
+ ## 4. Review
29
29
 
30
- Run `git diff` and review your own changes. Fix any issues before committing.
30
+ Run `/simplify` to review your changes for reuse, quality, and efficiency, and fix anything it surfaces.
31
31
 
32
- - **Correctness:** Does it fully address the issue? Missing edge cases?
33
- - **Simplify:** Can anything be combined, inlined, or removed? Prefer fewer files, less indirection, and no unnecessary abstractions.
34
- - **Reuse:** Are you duplicating logic that already exists in the codebase? Use existing helpers and patterns.
35
- - **Cleanup:** Remove leftover debug code, TODOs, unused imports, and dead code.
36
- - **Style:** Match the conventions of the surrounding code.
37
- - **Tests:** Are the tests meaningful, not just testing mocks?
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
- Then update the issue labels:
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`