@every-env/compound-plugin 0.1.0 → 0.2.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/.claude/commands/triage-prs.md +193 -0
- package/.claude-plugin/marketplace.json +4 -4
- package/.github/workflows/ci.yml +25 -0
- package/README.md +25 -4
- package/docs/index.html +14 -14
- package/docs/pages/changelog.html +1 -1
- package/docs/pages/getting-started.html +1 -1
- package/docs/plans/2026-02-08-feat-pr-triage-and-merge-plan.md +128 -0
- package/package.json +1 -1
- package/plans/grow-your-own-garden-plugin-architecture.md +1 -1
- package/plugins/compound-engineering/.claude-plugin/plugin.json +3 -3
- package/plugins/compound-engineering/CHANGELOG.md +32 -0
- package/plugins/compound-engineering/CLAUDE.md +3 -4
- package/plugins/compound-engineering/README.md +20 -7
- package/plugins/compound-engineering/agents/research/best-practices-researcher.md +14 -3
- package/plugins/compound-engineering/agents/research/framework-docs-researcher.md +11 -3
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +2 -0
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +243 -0
- package/plugins/compound-engineering/agents/research/repo-research-analyst.md +5 -4
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -0
- package/plugins/compound-engineering/agents/review/pattern-recognition-specialist.md +1 -1
- package/plugins/compound-engineering/agents/review/schema-drift-detector.md +139 -0
- package/plugins/compound-engineering/commands/deepen-plan.md +5 -5
- package/plugins/compound-engineering/commands/report-bug.md +3 -3
- package/plugins/compound-engineering/commands/resolve_todo_parallel.md +2 -0
- package/plugins/compound-engineering/commands/slfg.md +31 -0
- package/plugins/compound-engineering/commands/technical_review.md +7 -0
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +124 -0
- package/plugins/compound-engineering/commands/workflows/compound.md +64 -27
- package/plugins/compound-engineering/commands/workflows/plan.md +127 -42
- package/plugins/compound-engineering/commands/workflows/review.md +12 -0
- package/plugins/compound-engineering/commands/workflows/work.md +72 -2
- package/plugins/compound-engineering/skills/brainstorming/SKILL.md +190 -0
- package/plugins/compound-engineering/skills/compound-docs/SKILL.md +9 -9
- package/plugins/compound-engineering/skills/compound-docs/assets/critical-pattern-template.md +1 -1
- package/plugins/compound-engineering/skills/compound-docs/assets/resolution-template.md +3 -3
- package/plugins/compound-engineering/skills/compound-docs/references/yaml-schema.md +1 -1
- package/plugins/compound-engineering/skills/create-agent-skills/SKILL.md +168 -192
- package/plugins/compound-engineering/skills/create-agent-skills/references/official-spec.md +74 -125
- package/plugins/compound-engineering/skills/create-agent-skills/references/skill-structure.md +109 -329
- package/plugins/compound-engineering/skills/document-review/SKILL.md +87 -0
- package/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh +2 -10
- package/plugins/compound-engineering/skills/orchestrating-swarms/SKILL.md +1717 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +89 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/scripts/get-pr-comments +68 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/scripts/resolve-pr-thread +23 -0
- package/src/commands/install.ts +3 -1
- package/src/commands/sync.ts +84 -0
- package/src/converters/claude-to-codex.ts +59 -2
- package/src/converters/claude-to-opencode.ts +7 -5
- package/src/index.ts +2 -0
- package/src/parsers/claude-home.ts +65 -0
- package/src/sync/codex.ts +92 -0
- package/src/sync/opencode.ts +75 -0
- package/src/targets/codex.ts +7 -2
- package/src/targets/opencode.ts +11 -2
- package/src/types/claude.ts +1 -1
- package/src/utils/files.ts +13 -0
- package/src/utils/symlink.ts +43 -0
- package/tests/cli.test.ts +7 -5
- package/tests/codex-converter.test.ts +83 -0
- package/tests/codex-writer.test.ts +32 -0
- package/tests/opencode-writer.test.ts +57 -0
- package/plugins/compound-engineering/commands/plan_review.md +0 -7
- package/plugins/compound-engineering/commands/resolve_pr_parallel.md +0 -49
package/src/targets/opencode.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import path from "path"
|
|
2
|
-
import { copyDir, ensureDir, writeJson, writeText } from "../utils/files"
|
|
2
|
+
import { backupFile, copyDir, ensureDir, writeJson, writeText } from "../utils/files"
|
|
3
3
|
import type { OpenCodeBundle } from "../types/opencode"
|
|
4
4
|
|
|
5
5
|
export async function writeOpenCodeBundle(outputRoot: string, bundle: OpenCodeBundle): Promise<void> {
|
|
6
6
|
const paths = resolveOpenCodePaths(outputRoot)
|
|
7
7
|
await ensureDir(paths.root)
|
|
8
|
+
|
|
9
|
+
const backupPath = await backupFile(paths.configPath)
|
|
10
|
+
if (backupPath) {
|
|
11
|
+
console.log(`Backed up existing config to ${backupPath}`)
|
|
12
|
+
}
|
|
8
13
|
await writeJson(paths.configPath, bundle.config)
|
|
9
14
|
|
|
10
15
|
const agentsDir = paths.agentsDir
|
|
@@ -28,7 +33,10 @@ export async function writeOpenCodeBundle(outputRoot: string, bundle: OpenCodeBu
|
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
function resolveOpenCodePaths(outputRoot: string) {
|
|
31
|
-
|
|
36
|
+
const base = path.basename(outputRoot)
|
|
37
|
+
// Global install: ~/.config/opencode (basename is "opencode")
|
|
38
|
+
// Project install: .opencode (basename is ".opencode")
|
|
39
|
+
if (base === "opencode" || base === ".opencode") {
|
|
32
40
|
return {
|
|
33
41
|
root: outputRoot,
|
|
34
42
|
configPath: path.join(outputRoot, "opencode.json"),
|
|
@@ -38,6 +46,7 @@ function resolveOpenCodePaths(outputRoot: string) {
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
// Custom output directory - nest under .opencode subdirectory
|
|
41
50
|
return {
|
|
42
51
|
root: outputRoot,
|
|
43
52
|
configPath: path.join(outputRoot, "opencode.json"),
|
package/src/types/claude.ts
CHANGED
package/src/utils/files.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { promises as fs } from "fs"
|
|
2
2
|
import path from "path"
|
|
3
3
|
|
|
4
|
+
export async function backupFile(filePath: string): Promise<string | null> {
|
|
5
|
+
if (!(await pathExists(filePath))) return null
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
9
|
+
const backupPath = `${filePath}.bak.${timestamp}`
|
|
10
|
+
await fs.copyFile(filePath, backupPath)
|
|
11
|
+
return backupPath
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export async function pathExists(filePath: string): Promise<boolean> {
|
|
5
18
|
try {
|
|
6
19
|
await fs.access(filePath)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "fs/promises"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a symlink, safely replacing any existing symlink at target.
|
|
5
|
+
* Only removes existing symlinks - refuses to delete real directories.
|
|
6
|
+
*/
|
|
7
|
+
export async function forceSymlink(source: string, target: string): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
const stat = await fs.lstat(target)
|
|
10
|
+
if (stat.isSymbolicLink()) {
|
|
11
|
+
// Safe to remove existing symlink
|
|
12
|
+
await fs.unlink(target)
|
|
13
|
+
} else if (stat.isDirectory()) {
|
|
14
|
+
// Refuse to delete real directories
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Cannot create symlink at ${target}: a real directory exists there. ` +
|
|
17
|
+
`Remove it manually if you want to replace it with a symlink.`
|
|
18
|
+
)
|
|
19
|
+
} else {
|
|
20
|
+
// Regular file - remove it
|
|
21
|
+
await fs.unlink(target)
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// ENOENT means target doesn't exist, which is fine
|
|
25
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
26
|
+
throw err
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
await fs.symlink(source, target)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate a skill name to prevent path traversal attacks.
|
|
34
|
+
* Returns true if safe, false if potentially malicious.
|
|
35
|
+
*/
|
|
36
|
+
export function isValidSkillName(name: string): boolean {
|
|
37
|
+
if (!name || name.length === 0) return false
|
|
38
|
+
if (name.includes("/") || name.includes("\\")) return false
|
|
39
|
+
if (name.includes("..")) return false
|
|
40
|
+
if (name.includes("\0")) return false
|
|
41
|
+
if (name === "." || name === "..") return false
|
|
42
|
+
return true
|
|
43
|
+
}
|
package/tests/cli.test.ts
CHANGED
|
@@ -63,7 +63,7 @@ describe("CLI", () => {
|
|
|
63
63
|
expect(await exists(path.join(tempRoot, ".opencode", "plugins", "converted-hooks.ts"))).toBe(true)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
test("install defaults output to ~/.opencode", async () => {
|
|
66
|
+
test("install defaults output to ~/.config/opencode", async () => {
|
|
67
67
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-local-default-"))
|
|
68
68
|
const fixtureRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
|
|
69
69
|
|
|
@@ -95,8 +95,9 @@ describe("CLI", () => {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
expect(stdout).toContain("Installed compound-engineering")
|
|
98
|
-
|
|
99
|
-
expect(await exists(path.join(tempRoot, ".
|
|
98
|
+
// OpenCode global config lives at ~/.config/opencode per XDG spec
|
|
99
|
+
expect(await exists(path.join(tempRoot, ".config", "opencode", "opencode.json"))).toBe(true)
|
|
100
|
+
expect(await exists(path.join(tempRoot, ".config", "opencode", "agents", "repo-research-analyst.md"))).toBe(true)
|
|
100
101
|
})
|
|
101
102
|
|
|
102
103
|
test("list returns plugins in a temp workspace", async () => {
|
|
@@ -174,8 +175,9 @@ describe("CLI", () => {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
expect(stdout).toContain("Installed compound-engineering")
|
|
177
|
-
|
|
178
|
-
expect(await exists(path.join(tempRoot, ".
|
|
178
|
+
// OpenCode global config lives at ~/.config/opencode per XDG spec
|
|
179
|
+
expect(await exists(path.join(tempRoot, ".config", "opencode", "opencode.json"))).toBe(true)
|
|
180
|
+
expect(await exists(path.join(tempRoot, ".config", "opencode", "agents", "repo-research-analyst.md"))).toBe(true)
|
|
179
181
|
})
|
|
180
182
|
|
|
181
183
|
test("convert writes OpenCode output", async () => {
|
|
@@ -89,6 +89,89 @@ describe("convertClaudeToCodex", () => {
|
|
|
89
89
|
expect(bundle.mcpServers?.local?.args).toEqual(["hello"])
|
|
90
90
|
})
|
|
91
91
|
|
|
92
|
+
test("transforms Task agent calls to skill references", () => {
|
|
93
|
+
const plugin: ClaudePlugin = {
|
|
94
|
+
...fixturePlugin,
|
|
95
|
+
commands: [
|
|
96
|
+
{
|
|
97
|
+
name: "plan",
|
|
98
|
+
description: "Planning with agents",
|
|
99
|
+
body: `Run these agents in parallel:
|
|
100
|
+
|
|
101
|
+
- Task repo-research-analyst(feature_description)
|
|
102
|
+
- Task learnings-researcher(feature_description)
|
|
103
|
+
|
|
104
|
+
Then consolidate findings.
|
|
105
|
+
|
|
106
|
+
Task best-practices-researcher(topic)`,
|
|
107
|
+
sourcePath: "/tmp/plugin/commands/plan.md",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
agents: [],
|
|
111
|
+
skills: [],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const bundle = convertClaudeToCodex(plugin, {
|
|
115
|
+
agentMode: "subagent",
|
|
116
|
+
inferTemperature: false,
|
|
117
|
+
permissions: "none",
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const commandSkill = bundle.generatedSkills.find((s) => s.name === "plan")
|
|
121
|
+
expect(commandSkill).toBeDefined()
|
|
122
|
+
const parsed = parseFrontmatter(commandSkill!.content)
|
|
123
|
+
|
|
124
|
+
// Task calls should be transformed to skill references
|
|
125
|
+
expect(parsed.body).toContain("Use the $repo-research-analyst skill to: feature_description")
|
|
126
|
+
expect(parsed.body).toContain("Use the $learnings-researcher skill to: feature_description")
|
|
127
|
+
expect(parsed.body).toContain("Use the $best-practices-researcher skill to: topic")
|
|
128
|
+
|
|
129
|
+
// Original Task syntax should not remain
|
|
130
|
+
expect(parsed.body).not.toContain("Task repo-research-analyst")
|
|
131
|
+
expect(parsed.body).not.toContain("Task learnings-researcher")
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("transforms slash commands to prompts syntax", () => {
|
|
135
|
+
const plugin: ClaudePlugin = {
|
|
136
|
+
...fixturePlugin,
|
|
137
|
+
commands: [
|
|
138
|
+
{
|
|
139
|
+
name: "plan",
|
|
140
|
+
description: "Planning with commands",
|
|
141
|
+
body: `After planning, you can:
|
|
142
|
+
|
|
143
|
+
1. Run /deepen-plan to enhance
|
|
144
|
+
2. Run /plan_review for feedback
|
|
145
|
+
3. Start /workflows:work to implement
|
|
146
|
+
|
|
147
|
+
Don't confuse with file paths like /tmp/output.md or /dev/null.`,
|
|
148
|
+
sourcePath: "/tmp/plugin/commands/plan.md",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
agents: [],
|
|
152
|
+
skills: [],
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const bundle = convertClaudeToCodex(plugin, {
|
|
156
|
+
agentMode: "subagent",
|
|
157
|
+
inferTemperature: false,
|
|
158
|
+
permissions: "none",
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const commandSkill = bundle.generatedSkills.find((s) => s.name === "plan")
|
|
162
|
+
expect(commandSkill).toBeDefined()
|
|
163
|
+
const parsed = parseFrontmatter(commandSkill!.content)
|
|
164
|
+
|
|
165
|
+
// Slash commands should be transformed to /prompts: syntax
|
|
166
|
+
expect(parsed.body).toContain("/prompts:deepen-plan")
|
|
167
|
+
expect(parsed.body).toContain("/prompts:plan_review")
|
|
168
|
+
expect(parsed.body).toContain("/prompts:workflows-work")
|
|
169
|
+
|
|
170
|
+
// File paths should NOT be transformed
|
|
171
|
+
expect(parsed.body).toContain("/tmp/output.md")
|
|
172
|
+
expect(parsed.body).toContain("/dev/null")
|
|
173
|
+
})
|
|
174
|
+
|
|
92
175
|
test("truncates generated skill descriptions to Codex limits and single line", () => {
|
|
93
176
|
const longDescription = `Line one\nLine two ${"a".repeat(2000)}`
|
|
94
177
|
const plugin: ClaudePlugin = {
|
|
@@ -73,4 +73,36 @@ describe("writeCodexBundle", () => {
|
|
|
73
73
|
expect(await exists(path.join(codexRoot, "prompts", "command-one.md"))).toBe(true)
|
|
74
74
|
expect(await exists(path.join(codexRoot, "skills", "skill-one", "SKILL.md"))).toBe(true)
|
|
75
75
|
})
|
|
76
|
+
|
|
77
|
+
test("backs up existing config.toml before overwriting", async () => {
|
|
78
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "codex-backup-"))
|
|
79
|
+
const codexRoot = path.join(tempRoot, ".codex")
|
|
80
|
+
const configPath = path.join(codexRoot, "config.toml")
|
|
81
|
+
|
|
82
|
+
// Create existing config
|
|
83
|
+
await fs.mkdir(codexRoot, { recursive: true })
|
|
84
|
+
const originalContent = "# My original config\n[custom]\nkey = \"value\"\n"
|
|
85
|
+
await fs.writeFile(configPath, originalContent)
|
|
86
|
+
|
|
87
|
+
const bundle: CodexBundle = {
|
|
88
|
+
prompts: [],
|
|
89
|
+
skillDirs: [],
|
|
90
|
+
generatedSkills: [],
|
|
91
|
+
mcpServers: { test: { command: "echo" } },
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await writeCodexBundle(codexRoot, bundle)
|
|
95
|
+
|
|
96
|
+
// New config should be written
|
|
97
|
+
const newConfig = await fs.readFile(configPath, "utf8")
|
|
98
|
+
expect(newConfig).toContain("[mcp_servers.test]")
|
|
99
|
+
|
|
100
|
+
// Backup should exist with original content
|
|
101
|
+
const files = await fs.readdir(codexRoot)
|
|
102
|
+
const backupFileName = files.find((f) => f.startsWith("config.toml.bak."))
|
|
103
|
+
expect(backupFileName).toBeDefined()
|
|
104
|
+
|
|
105
|
+
const backupContent = await fs.readFile(path.join(codexRoot, backupFileName!), "utf8")
|
|
106
|
+
expect(backupContent).toBe(originalContent)
|
|
107
|
+
})
|
|
76
108
|
})
|
|
@@ -59,4 +59,61 @@ describe("writeOpenCodeBundle", () => {
|
|
|
59
59
|
expect(await exists(path.join(outputRoot, "skills", "skill-one", "SKILL.md"))).toBe(true)
|
|
60
60
|
expect(await exists(path.join(outputRoot, ".opencode"))).toBe(false)
|
|
61
61
|
})
|
|
62
|
+
|
|
63
|
+
test("writes directly into ~/.config/opencode style output root", async () => {
|
|
64
|
+
// Simulates the global install path: ~/.config/opencode
|
|
65
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "config-opencode-"))
|
|
66
|
+
const outputRoot = path.join(tempRoot, ".config", "opencode")
|
|
67
|
+
const bundle: OpenCodeBundle = {
|
|
68
|
+
config: { $schema: "https://opencode.ai/config.json" },
|
|
69
|
+
agents: [{ name: "agent-one", content: "Agent content" }],
|
|
70
|
+
plugins: [],
|
|
71
|
+
skillDirs: [
|
|
72
|
+
{
|
|
73
|
+
name: "skill-one",
|
|
74
|
+
sourceDir: path.join(import.meta.dir, "fixtures", "sample-plugin", "skills", "skill-one"),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await writeOpenCodeBundle(outputRoot, bundle)
|
|
80
|
+
|
|
81
|
+
// Should write directly, not nested under .opencode
|
|
82
|
+
expect(await exists(path.join(outputRoot, "opencode.json"))).toBe(true)
|
|
83
|
+
expect(await exists(path.join(outputRoot, "agents", "agent-one.md"))).toBe(true)
|
|
84
|
+
expect(await exists(path.join(outputRoot, "skills", "skill-one", "SKILL.md"))).toBe(true)
|
|
85
|
+
expect(await exists(path.join(outputRoot, ".opencode"))).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("backs up existing opencode.json before overwriting", async () => {
|
|
89
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-backup-"))
|
|
90
|
+
const outputRoot = path.join(tempRoot, ".opencode")
|
|
91
|
+
const configPath = path.join(outputRoot, "opencode.json")
|
|
92
|
+
|
|
93
|
+
// Create existing config
|
|
94
|
+
await fs.mkdir(outputRoot, { recursive: true })
|
|
95
|
+
const originalConfig = { $schema: "https://opencode.ai/config.json", custom: "value" }
|
|
96
|
+
await fs.writeFile(configPath, JSON.stringify(originalConfig, null, 2))
|
|
97
|
+
|
|
98
|
+
const bundle: OpenCodeBundle = {
|
|
99
|
+
config: { $schema: "https://opencode.ai/config.json", new: "config" },
|
|
100
|
+
agents: [],
|
|
101
|
+
plugins: [],
|
|
102
|
+
skillDirs: [],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await writeOpenCodeBundle(outputRoot, bundle)
|
|
106
|
+
|
|
107
|
+
// New config should be written
|
|
108
|
+
const newConfig = JSON.parse(await fs.readFile(configPath, "utf8"))
|
|
109
|
+
expect(newConfig.new).toBe("config")
|
|
110
|
+
|
|
111
|
+
// Backup should exist with original content
|
|
112
|
+
const files = await fs.readdir(outputRoot)
|
|
113
|
+
const backupFileName = files.find((f) => f.startsWith("opencode.json.bak."))
|
|
114
|
+
expect(backupFileName).toBeDefined()
|
|
115
|
+
|
|
116
|
+
const backupContent = JSON.parse(await fs.readFile(path.join(outputRoot, backupFileName!), "utf8"))
|
|
117
|
+
expect(backupContent.custom).toBe("value")
|
|
118
|
+
})
|
|
62
119
|
})
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plan_review
|
|
3
|
-
description: Have multiple specialized agents review a plan in parallel
|
|
4
|
-
argument-hint: "[plan file path or plan content]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Have @agent-dhh-rails-reviewer @agent-kieran-rails-reviewer @agent-code-simplicity-reviewer review this plan in parallel.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: resolve_pr_parallel
|
|
3
|
-
description: Resolve all PR comments using parallel processing
|
|
4
|
-
argument-hint: "[optional: PR number or current PR]"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Resolve all PR comments using parallel processing.
|
|
8
|
-
|
|
9
|
-
Claude Code automatically detects and understands your git context:
|
|
10
|
-
|
|
11
|
-
- Current branch detection
|
|
12
|
-
- Associated PR context
|
|
13
|
-
- All PR comments and review threads
|
|
14
|
-
- Can work with any PR by specifying the PR number, or ask it.
|
|
15
|
-
|
|
16
|
-
## Workflow
|
|
17
|
-
|
|
18
|
-
### 1. Analyze
|
|
19
|
-
|
|
20
|
-
Get all unresolved comments for PR
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
gh pr status
|
|
24
|
-
bin/get-pr-comments PR_NUMBER
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 2. Plan
|
|
28
|
-
|
|
29
|
-
Create a TodoWrite list of all unresolved items grouped by type.
|
|
30
|
-
|
|
31
|
-
### 3. Implement (PARALLEL)
|
|
32
|
-
|
|
33
|
-
Spawn a pr-comment-resolver agent for each unresolved item in parallel.
|
|
34
|
-
|
|
35
|
-
So if there are 3 comments, it will spawn 3 pr-comment-resolver agents in parallel. liek this
|
|
36
|
-
|
|
37
|
-
1. Task pr-comment-resolver(comment1)
|
|
38
|
-
2. Task pr-comment-resolver(comment2)
|
|
39
|
-
3. Task pr-comment-resolver(comment3)
|
|
40
|
-
|
|
41
|
-
Always run all in parallel subagents/Tasks for each Todo item.
|
|
42
|
-
|
|
43
|
-
### 4. Commit & Resolve
|
|
44
|
-
|
|
45
|
-
- Commit changes
|
|
46
|
-
- Run bin/resolve-pr-thread THREAD_ID_1
|
|
47
|
-
- Push to remote
|
|
48
|
-
|
|
49
|
-
Last, check bin/get-pr-comments PR_NUMBER again to see if all comments are resolved. They should be, if not, repeat the process from 1.
|