@armstrongnate/april 0.0.3 → 0.0.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 +14 -2
- package/config.example.yaml +1 -4
- package/dist/cli.js +12 -5
- package/dist/commands/init.js +14 -10
- package/dist/commands/install-skill.js +50 -0
- package/dist/commands/upgrade.js +1 -3
- package/dist/config.js +2 -4
- package/dist/processes.js +1 -6
- package/dist/skill.js +20 -0
- 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
|
|
|
@@ -112,8 +112,10 @@ Healthy logs include `Starting webhook forwarder for <repo>` and no immediate er
|
|
|
112
112
|
|
|
113
113
|
| Command | What it does |
|
|
114
114
|
| --- | --- |
|
|
115
|
-
| `april init` | Copies the bundled `config.example.yaml` to `~/.config/april/config.yaml` and the `issue-worker` skill to `~/.claude/skills/`.
|
|
115
|
+
| `april init` | Copies the bundled `config.example.yaml` to `~/.config/april/config.yaml` and the `issue-worker` skill to `~/.claude/skills/`. **Only writes files that don't already exist** — never overwrites. |
|
|
116
116
|
| `april install` | Installs and starts the user service. Pass `--print` to see the unit/plist without writing it. |
|
|
117
|
+
| `april install-skill [-y]` | Install or refresh the issue-worker skill. Prompts before overwriting an existing copy; `--yes` skips the prompt (use in non-interactive scripts). |
|
|
118
|
+
| `april upgrade [VER]` | Upgrade the npm package, regenerate the unit, restart the service, and reconcile the skill. |
|
|
117
119
|
| `april uninstall` | Stops and removes the service. |
|
|
118
120
|
| `april start` / `stop` / `restart` | Lifecycle. |
|
|
119
121
|
| `april status` | Shows service status. |
|
|
@@ -185,6 +187,16 @@ april restart
|
|
|
185
187
|
|
|
186
188
|
**If you skip `april install` after upgrading, new template features (`EnvironmentFile=`, env-var changes, etc.) will not appear in your existing unit file** — `npm` only updates the package, not anything systemd has on disk.
|
|
187
189
|
|
|
190
|
+
`april upgrade` ends by running `april install-skill`, which:
|
|
191
|
+
|
|
192
|
+
- silently installs the skill if missing,
|
|
193
|
+
- says "already up to date" if your installed copy matches the bundled one, or
|
|
194
|
+
- **prompts you** before overwriting if your copy differs (in case you customized it).
|
|
195
|
+
|
|
196
|
+
For non-interactive scripts, pass `--yes` to `april install-skill` (or to the upgrade flow indirectly: `april install-skill -y`).
|
|
197
|
+
|
|
198
|
+
Your `~/.config/april/config.yaml` is never overwritten by any `april` command. To reset it from the example, delete the file manually and re-run `april init`.
|
|
199
|
+
|
|
188
200
|
## Troubleshooting
|
|
189
201
|
|
|
190
202
|
### `Required gh extension not installed: cli/gh-webhook`
|
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/cli.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
import { backend } from "./service/index.js";
|
|
3
3
|
import { run as runInit } from "./commands/init.js";
|
|
4
4
|
import { run as runUpgrade } from "./commands/upgrade.js";
|
|
5
|
+
import { run as runInstallSkill } from "./commands/install-skill.js";
|
|
5
6
|
const HELP = `april — issue worker
|
|
6
7
|
|
|
7
8
|
Usage:
|
|
8
9
|
april <command> [options]
|
|
9
10
|
|
|
10
11
|
Commands:
|
|
11
|
-
init Copy bundled config + skill to ~/.config/april and ~/.claude
|
|
12
|
+
init Copy bundled config + skill to ~/.config/april and ~/.claude (only if missing).
|
|
12
13
|
install [--print] Install and start the user service. --print emits the unit/plist to stdout instead.
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
install-skill [-y] Install or refresh the issue-worker skill. Prompts before overwriting an existing
|
|
15
|
+
one; --yes (-y) skips the prompt.
|
|
16
|
+
upgrade [VER] Upgrade the npm package, regenerate the unit, restart, and reconcile the skill.
|
|
17
|
+
VER defaults to "latest". --with npm|pnpm|yarn overrides the package manager.
|
|
15
18
|
uninstall Stop and remove the user service
|
|
16
19
|
start Start the service
|
|
17
20
|
stop Stop the service
|
|
@@ -22,8 +25,9 @@ Commands:
|
|
|
22
25
|
help Show this help
|
|
23
26
|
version Show version
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
Notes:
|
|
29
|
+
Nothing is ever overwritten silently. To reset config, delete ~/.config/april/config.yaml
|
|
30
|
+
and re-run init. To refresh the skill, use install-skill (it prompts before overwriting).
|
|
27
31
|
`;
|
|
28
32
|
function parseLogsArgs(args) {
|
|
29
33
|
let follow = false;
|
|
@@ -76,6 +80,9 @@ async function main() {
|
|
|
76
80
|
if (cmd === "upgrade") {
|
|
77
81
|
return runUpgrade(rest);
|
|
78
82
|
}
|
|
83
|
+
if (cmd === "install-skill") {
|
|
84
|
+
return await runInstallSkill(rest);
|
|
85
|
+
}
|
|
79
86
|
if (cmd === "daemon") {
|
|
80
87
|
// Run the long-running process inline. Importing index.js triggers main();
|
|
81
88
|
// we then hang forever and let its SIGINT/SIGTERM handlers terminate the process.
|
package/dist/commands/init.js
CHANGED
|
@@ -4,24 +4,24 @@ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { ensureEnvFile, envFilePath } from "../service/envfile.js";
|
|
6
6
|
import { isGhWebhookExtensionInstalled, GH_EXTENSION_INSTALL_CMD } from "../precheck.js";
|
|
7
|
+
import { SKILL_DST, bundledSkillPath, compareSkill } from "../skill.js";
|
|
7
8
|
// Resolve the bundled package root from this file's installed location.
|
|
8
|
-
// dist/commands/init.js -> dist/.. (the package root, where config.example.yaml
|
|
9
|
+
// dist/commands/init.js -> dist/.. (the package root, where config.example.yaml lives)
|
|
9
10
|
function packageRoot() {
|
|
10
11
|
const here = fileURLToPath(import.meta.url);
|
|
11
12
|
return resolve(dirname(here), "..", "..");
|
|
12
13
|
}
|
|
13
|
-
function copyIfMissing(src, dst, label
|
|
14
|
+
function copyIfMissing(src, dst, label) {
|
|
14
15
|
mkdirSync(dirname(dst), { recursive: true });
|
|
15
|
-
if (existsSync(dst)
|
|
16
|
-
console.log(` ${label}: already exists at ${dst}
|
|
16
|
+
if (existsSync(dst)) {
|
|
17
|
+
console.log(` ${label}: already exists at ${dst}`);
|
|
17
18
|
return "exists";
|
|
18
19
|
}
|
|
19
20
|
copyFileSync(src, dst);
|
|
20
21
|
console.log(` ${label}: wrote ${dst}`);
|
|
21
22
|
return "wrote";
|
|
22
23
|
}
|
|
23
|
-
export function run(
|
|
24
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
24
|
+
export function run(_args) {
|
|
25
25
|
const root = packageRoot();
|
|
26
26
|
console.log("april init");
|
|
27
27
|
console.log("");
|
|
@@ -31,14 +31,13 @@ export function run(args) {
|
|
|
31
31
|
console.error(` Cannot find bundled config.example.yaml at ${configSrc}`);
|
|
32
32
|
return 1;
|
|
33
33
|
}
|
|
34
|
-
const configResult = copyIfMissing(configSrc, configDst, "config"
|
|
35
|
-
const skillSrc =
|
|
36
|
-
const skillDst = join(homedir(), ".claude", "skills", "issue-worker", "SKILL.md");
|
|
34
|
+
const configResult = copyIfMissing(configSrc, configDst, "config");
|
|
35
|
+
const skillSrc = bundledSkillPath();
|
|
37
36
|
if (!existsSync(skillSrc)) {
|
|
38
37
|
console.error(` Cannot find bundled skill at ${skillSrc}`);
|
|
39
38
|
return 1;
|
|
40
39
|
}
|
|
41
|
-
copyIfMissing(skillSrc,
|
|
40
|
+
copyIfMissing(skillSrc, SKILL_DST, "skill");
|
|
42
41
|
const envState = ensureEnvFile();
|
|
43
42
|
console.log(` env: ${envState === "created" ? "wrote" : "already exists"} ${envFilePath()}`);
|
|
44
43
|
console.log("");
|
|
@@ -51,6 +50,11 @@ export function run(args) {
|
|
|
51
50
|
console.log(` Install with: ${GH_EXTENSION_INSTALL_CMD}`);
|
|
52
51
|
console.log(" (april will refuse to start without it.)");
|
|
53
52
|
}
|
|
53
|
+
// If the installed skill differs from what we shipped, surface it without prompting.
|
|
54
|
+
if (compareSkill() === "differs-from-bundled") {
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(` i installed skill differs from bundled. Refresh with: april install-skill`);
|
|
57
|
+
}
|
|
54
58
|
console.log("");
|
|
55
59
|
if (configResult === "wrote") {
|
|
56
60
|
console.log(`Next: edit ${configDst}, then run \`april install\`.`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { SKILL_DST, bundledSkillPath, compareSkill } from "../skill.js";
|
|
6
|
+
export async function run(args) {
|
|
7
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
8
|
+
const src = bundledSkillPath();
|
|
9
|
+
if (!existsSync(src)) {
|
|
10
|
+
console.error(`Cannot find bundled skill at ${src}`);
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
const state = compareSkill();
|
|
14
|
+
if (state === "matches-bundled") {
|
|
15
|
+
console.log(`✓ Skill at ${SKILL_DST} is already up to date`);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
if (state === "missing") {
|
|
19
|
+
mkdirSync(dirname(SKILL_DST), { recursive: true });
|
|
20
|
+
copyFileSync(src, SKILL_DST);
|
|
21
|
+
console.log(`✓ Installed skill at ${SKILL_DST}`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
// differs-from-bundled
|
|
25
|
+
console.log("Bundled issue-worker skill differs from the installed copy.");
|
|
26
|
+
console.log(` installed: ${SKILL_DST}`);
|
|
27
|
+
console.log(` bundled: ${src}`);
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log(`Diff with: diff ${SKILL_DST} ${src}`);
|
|
30
|
+
console.log("");
|
|
31
|
+
if (!yes) {
|
|
32
|
+
if (!stdin.isTTY) {
|
|
33
|
+
console.error("Refusing to overwrite without confirmation in a non-interactive session.\n" +
|
|
34
|
+
"Re-run with --yes to confirm, or run interactively to be prompted.");
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
38
|
+
const answer = (await rl.question("Overwrite installed skill with bundled version? [y/N] "))
|
|
39
|
+
.trim()
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
rl.close();
|
|
42
|
+
if (answer !== "y" && answer !== "yes") {
|
|
43
|
+
console.log("Skipped — installed skill is unchanged.");
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
copyFileSync(src, SKILL_DST);
|
|
48
|
+
console.log(`✓ Overwrote skill at ${SKILL_DST}`);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -2,7 +2,6 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
const PACKAGE = "@armstrongnate/april";
|
|
4
4
|
function detectPackageManager() {
|
|
5
|
-
// Where this script lives reveals which global install dir it's in.
|
|
6
5
|
const here = fileURLToPath(import.meta.url);
|
|
7
6
|
if (/[\\/]\.?pnpm[\\/]|[\\/]Library[\\/]pnpm[\\/]/.test(here))
|
|
8
7
|
return "pnpm";
|
|
@@ -30,7 +29,6 @@ function step(name, cmd, args) {
|
|
|
30
29
|
}
|
|
31
30
|
export function run(args) {
|
|
32
31
|
let pm = detectPackageManager();
|
|
33
|
-
// --with <pm> override
|
|
34
32
|
const withIdx = args.indexOf("--with");
|
|
35
33
|
if (withIdx >= 0) {
|
|
36
34
|
const v = args[withIdx + 1];
|
|
@@ -41,7 +39,6 @@ export function run(args) {
|
|
|
41
39
|
pm = v;
|
|
42
40
|
}
|
|
43
41
|
let ref = `${PACKAGE}@latest`;
|
|
44
|
-
// Allow `april upgrade <version>` to pin
|
|
45
42
|
const positional = args.filter((a, i) => !a.startsWith("--") && args[i - 1] !== "--with");
|
|
46
43
|
if (positional[0])
|
|
47
44
|
ref = `${PACKAGE}@${positional[0]}`;
|
|
@@ -50,6 +47,7 @@ export function run(args) {
|
|
|
50
47
|
// From here on, `april` resolves to the freshly installed binary on PATH.
|
|
51
48
|
step("Regenerating service unit (april install)", "april", ["install"]);
|
|
52
49
|
step("Restarting service (april restart)", "april", ["restart"]);
|
|
50
|
+
step("Reconciling skill (april install-skill)", "april", ["install-skill"]);
|
|
53
51
|
console.log("\n✓ Upgrade complete. Tail logs with: april logs -f");
|
|
54
52
|
return 0;
|
|
55
53
|
}
|
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/skill.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
export const SKILL_DST = join(homedir(), ".claude", "skills", "issue-worker", "SKILL.md");
|
|
6
|
+
export function bundledSkillPath() {
|
|
7
|
+
// dist/skill.js -> dist/.. -> package root, then skills/issue-worker/SKILL.md
|
|
8
|
+
const here = fileURLToPath(import.meta.url);
|
|
9
|
+
return resolve(dirname(here), "..", "skills", "issue-worker", "SKILL.md");
|
|
10
|
+
}
|
|
11
|
+
export function compareSkill() {
|
|
12
|
+
if (!existsSync(SKILL_DST))
|
|
13
|
+
return "missing";
|
|
14
|
+
const src = bundledSkillPath();
|
|
15
|
+
if (!existsSync(src))
|
|
16
|
+
return "matches-bundled"; // can't compare; don't alarm
|
|
17
|
+
return readFileSync(SKILL_DST).equals(readFileSync(src))
|
|
18
|
+
? "matches-bundled"
|
|
19
|
+
: "differs-from-bundled";
|
|
20
|
+
}
|
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`
|