@towles/tool 0.0.61 → 0.0.63
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/package.json +50 -57
- package/src/commands/agentboard.ts +176 -0
- package/src/commands/{auto-claude.ts → auto-claude/index.ts} +18 -28
- package/src/commands/auto-claude/list.ts +114 -0
- package/src/commands/auto-claude/retry.test.ts +138 -0
- package/src/commands/auto-claude/retry.ts +139 -0
- package/src/commands/auto-claude/status.test.ts +147 -0
- package/src/commands/auto-claude/status.ts +123 -0
- package/src/commands/base.ts +7 -2
- package/src/commands/config.ts +5 -7
- package/src/commands/doctor.ts +111 -12
- package/src/commands/gh/branch.ts +4 -4
- package/src/commands/gh/pr.ts +1 -0
- package/src/commands/graph/index.ts +169 -0
- package/src/commands/graph.test.ts +1 -1
- package/src/commands/install.ts +40 -68
- package/src/commands/journal/daily-notes.ts +3 -3
- package/src/commands/journal/meeting.ts +3 -3
- package/src/commands/journal/note.ts +3 -3
- package/src/lib/auto-claude/claude-cli.ts +183 -0
- package/src/lib/auto-claude/config.test.ts +6 -8
- package/src/lib/auto-claude/config.ts +3 -4
- package/src/lib/auto-claude/index.ts +2 -3
- package/src/lib/auto-claude/labels.test.ts +85 -0
- package/src/lib/auto-claude/labels.ts +42 -0
- package/src/lib/auto-claude/pipeline-execution.test.ts +129 -33
- package/src/lib/auto-claude/pipeline.test.ts +2 -2
- package/src/lib/auto-claude/pipeline.ts +120 -36
- package/src/lib/auto-claude/prompt-templates/01_plan.prompt.md +68 -0
- package/src/lib/auto-claude/prompt-templates/{05_implement.prompt.md → 02_implement.prompt.md} +3 -2
- package/src/lib/auto-claude/prompt-templates/03_simplify.prompt.md +52 -0
- package/src/lib/auto-claude/prompt-templates/{06_review.prompt.md → 04_review.prompt.md} +29 -6
- package/src/lib/auto-claude/prompt-templates/index.test.ts +9 -42
- package/src/lib/auto-claude/prompt-templates/index.ts +13 -28
- package/src/lib/auto-claude/run-claude.test.ts +48 -68
- package/src/lib/auto-claude/shell.ts +6 -0
- package/src/lib/auto-claude/steps/create-pr.ts +89 -25
- package/src/lib/auto-claude/steps/fetch-issues.ts +4 -1
- package/src/lib/auto-claude/steps/implement.ts +9 -16
- package/src/lib/auto-claude/steps/simple-steps.ts +34 -0
- package/src/lib/auto-claude/steps/steps.test.ts +68 -63
- package/src/lib/auto-claude/templates.test.ts +91 -0
- package/src/lib/auto-claude/templates.ts +34 -0
- package/src/lib/auto-claude/test-helpers.ts +2 -1
- package/src/lib/auto-claude/utils-execution.test.ts +9 -57
- package/src/lib/auto-claude/utils.test.ts +5 -9
- package/src/lib/auto-claude/utils.ts +27 -253
- package/src/lib/graph/analyzer.test.ts +451 -0
- package/src/lib/graph/analyzer.ts +165 -0
- package/src/lib/graph/index.ts +24 -0
- package/src/lib/graph/labels.ts +87 -0
- package/src/lib/graph/parser.test.ts +150 -0
- package/src/lib/graph/parser.ts +65 -0
- package/src/lib/graph/render.ts +25 -0
- package/src/lib/graph/server.ts +70 -0
- package/src/lib/graph/sessions.ts +104 -0
- package/src/lib/graph/tools.ts +90 -0
- package/src/lib/graph/treemap.ts +211 -0
- package/src/lib/graph/types.ts +80 -0
- package/src/lib/install/claude-settings.ts +64 -0
- package/src/lib/journal/editor.ts +33 -0
- package/src/lib/journal/fs.ts +13 -0
- package/src/lib/journal/index.ts +11 -0
- package/src/lib/journal/paths.ts +106 -0
- package/src/lib/journal/{utils.ts → templates.ts} +3 -151
- package/src/utils/fs.ts +19 -0
- package/src/utils/git/exec.ts +18 -0
- package/src/utils/git/gh-cli-wrapper.test.ts +47 -8
- package/src/utils/git/gh-cli-wrapper.ts +31 -19
- package/src/utils/render.ts +3 -1
- package/src/commands/graph.ts +0 -970
- package/src/lib/auto-claude/prompt-templates/01_research.prompt.md +0 -21
- package/src/lib/auto-claude/prompt-templates/02_plan.prompt.md +0 -27
- package/src/lib/auto-claude/prompt-templates/03_plan-annotations.prompt.md +0 -15
- package/src/lib/auto-claude/prompt-templates/04_plan-implementation.prompt.md +0 -35
- package/src/lib/auto-claude/prompt-templates/07_refresh.prompt.md +0 -30
- package/src/lib/auto-claude/steps/plan-annotations.ts +0 -54
- package/src/lib/auto-claude/steps/plan-implementation.ts +0 -14
- package/src/lib/auto-claude/steps/plan.ts +0 -14
- package/src/lib/auto-claude/steps/refresh.ts +0 -114
- package/src/lib/auto-claude/steps/remove-label.ts +0 -22
- package/src/lib/auto-claude/steps/research.ts +0 -21
- package/src/lib/auto-claude/steps/review.ts +0 -14
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
You are a code simplification agent. Review the changes on this branch and simplify the implementation.
|
|
2
|
+
|
|
3
|
+
The issue is in @{{ISSUE_DIR}}/initial-ramblings.md — for context only.
|
|
4
|
+
The completed summary is in @{{ISSUE_DIR}}/completed-summary.md.
|
|
5
|
+
The code lives primarily at `{{SCOPE_PATH}}/`.
|
|
6
|
+
The base branch is `{{MAIN_BRANCH}}`.
|
|
7
|
+
|
|
8
|
+
## How to work
|
|
9
|
+
|
|
10
|
+
1. Run `git diff {{MAIN_BRANCH}}...HEAD --stat` to see all changed files
|
|
11
|
+
2. Run `git diff {{MAIN_BRANCH}}...HEAD` to review the full diff
|
|
12
|
+
3. For each changed file, read the complete file (not just the diff) to understand the full context
|
|
13
|
+
|
|
14
|
+
## What to simplify
|
|
15
|
+
|
|
16
|
+
- **Dead code** — remove unused variables, imports, functions, or parameters added by the implementation
|
|
17
|
+
- **Over-abstraction** — inline single-use helpers, remove unnecessary wrapper functions, flatten needless indirection
|
|
18
|
+
- **Complexity** — simplify conditionals, reduce nesting, prefer early returns
|
|
19
|
+
- **Duplication** — extract repeated patterns into shared helpers (only when genuinely duplicated, not merely similar)
|
|
20
|
+
- **Naming** — rename unclear variables or functions for better readability
|
|
21
|
+
- **Type safety** — remove unnecessary type assertions, tighten `any` types
|
|
22
|
+
|
|
23
|
+
## What NOT to change
|
|
24
|
+
|
|
25
|
+
- Do not alter behavior — every simplification must be behavior-preserving
|
|
26
|
+
- Do not refactor code outside the scope of this branch's changes
|
|
27
|
+
- Do not add new features or fix unrelated bugs
|
|
28
|
+
- Do not remove code that is used elsewhere but appears unused in the diff
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
|
|
32
|
+
After each round of simplifications:
|
|
33
|
+
|
|
34
|
+
1. Run the project's type-check command — fix any errors
|
|
35
|
+
2. Run the project's test command — fix any regressions
|
|
36
|
+
3. Run the project's lint command — fix any violations
|
|
37
|
+
|
|
38
|
+
Commit simplifications as `refactor(scope): code-simplify review for issue #N` (use the actual issue number from the issue directory name).
|
|
39
|
+
|
|
40
|
+
## Output
|
|
41
|
+
|
|
42
|
+
Write @{{ISSUE_DIR}}/simplify-summary.md with:
|
|
43
|
+
|
|
44
|
+
- List of simplifications made (file path + what changed)
|
|
45
|
+
- If no simplifications were needed, state that explicitly
|
|
46
|
+
- Verification results (all checks pass / any issues encountered)
|
|
47
|
+
|
|
48
|
+
## Guidelines
|
|
49
|
+
|
|
50
|
+
- Prefer fewer, well-justified changes over many trivial ones
|
|
51
|
+
- If the implementation is already clean, say so — don't force changes
|
|
52
|
+
- Follow the project's coding conventions from CLAUDE.md
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
You are reviewing code changes for the issue in @{{ISSUE_DIR}}/initial-ramblings.md. The plan is in @{{ISSUE_DIR}}/plan.md
|
|
1
|
+
You are reviewing code changes for the issue in @{{ISSUE_DIR}}/initial-ramblings.md. The plan is in @{{ISSUE_DIR}}/plan.md.
|
|
2
2
|
|
|
3
3
|
## Automated checks
|
|
4
4
|
|
|
@@ -18,15 +18,38 @@ Run `git diff {{MAIN_BRANCH}}...HEAD` and check:
|
|
|
18
18
|
8. **Incomplete work** — TODOs, placeholders, unfinished implementations?
|
|
19
19
|
9. **Test coverage** — new behaviors covered by tests? Existing tests updated?
|
|
20
20
|
|
|
21
|
-
After review, run the code-simplify skill on changed files. Apply simplifications that improve clarity without changing behavior — commit separately.
|
|
22
|
-
|
|
23
21
|
Fix issues directly, commit as `fix(scope): review fixes for issue #N`.
|
|
24
22
|
|
|
25
23
|
## Write @{{ISSUE_DIR}}/review.md
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
**CRITICAL**: The first line of review.md must be exactly `PASS` or `FAIL` (no markdown, no prefix, just the word). This is machine-parsed.
|
|
26
|
+
|
|
27
|
+
Format:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
PASS
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
or
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
FAIL
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Followed by:
|
|
40
|
+
|
|
41
|
+
- **Issues found** — what was wrong and what you fixed (if any)
|
|
30
42
|
- **Confidence level** — high/medium/low
|
|
31
43
|
- **Notes** — anything the PR reviewer should check
|
|
32
44
|
- **Recommended follow-ups** — only if genuinely valuable. Omit if nothing worth flagging.
|
|
45
|
+
|
|
46
|
+
### When to PASS vs FAIL
|
|
47
|
+
|
|
48
|
+
- **PASS** — implementation is correct, tests pass, code quality is acceptable. Minor fixes you made during review are fine.
|
|
49
|
+
- **FAIL** — fundamentally broken: wrong approach, missing core functionality, unfixable regressions, or critical bugs you cannot fix in review. Explain what's wrong and what the implementer should change. Your feedback will be passed back to the implement step for a retry.
|
|
50
|
+
|
|
51
|
+
## Guidelines
|
|
52
|
+
|
|
53
|
+
- Follow the project's coding conventions from CLAUDE.md
|
|
54
|
+
- Be thorough but pragmatic — don't fail for style nits
|
|
55
|
+
- If you fix issues during review, that's a PASS (with fixes noted), not a FAIL
|
|
@@ -10,15 +10,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
|
|
11
11
|
describe("TEMPLATES", () => {
|
|
12
12
|
it("should have all expected keys", () => {
|
|
13
|
-
expect(Object.keys(TEMPLATES)).toEqual([
|
|
14
|
-
"research",
|
|
15
|
-
"plan",
|
|
16
|
-
"planAnnotations",
|
|
17
|
-
"planImplementation",
|
|
18
|
-
"implement",
|
|
19
|
-
"review",
|
|
20
|
-
"refresh",
|
|
21
|
-
]);
|
|
13
|
+
expect(Object.keys(TEMPLATES)).toEqual(["plan", "implement", "simplify", "review"]);
|
|
22
14
|
});
|
|
23
15
|
|
|
24
16
|
it("every template file should exist on disk", () => {
|
|
@@ -43,8 +35,8 @@ describe("TEMPLATES", () => {
|
|
|
43
35
|
});
|
|
44
36
|
|
|
45
37
|
describe("PIPELINE_STEPS", () => {
|
|
46
|
-
it("should have
|
|
47
|
-
expect(PIPELINE_STEPS).toHaveLength(
|
|
38
|
+
it("should have 4 steps", () => {
|
|
39
|
+
expect(PIPELINE_STEPS).toHaveLength(4);
|
|
48
40
|
});
|
|
49
41
|
|
|
50
42
|
it("each step should have order, name, and label", () => {
|
|
@@ -65,16 +57,7 @@ describe("PIPELINE_STEPS", () => {
|
|
|
65
57
|
});
|
|
66
58
|
|
|
67
59
|
it("names should match expected pipeline order", () => {
|
|
68
|
-
expect(PIPELINE_STEPS.map((s) => s.name)).toEqual([
|
|
69
|
-
"research",
|
|
70
|
-
"plan",
|
|
71
|
-
"plan-annotations",
|
|
72
|
-
"plan-implementation",
|
|
73
|
-
"implement",
|
|
74
|
-
"review",
|
|
75
|
-
"create-pr",
|
|
76
|
-
"remove-label",
|
|
77
|
-
]);
|
|
60
|
+
expect(PIPELINE_STEPS.map((s) => s.name)).toEqual(["plan", "implement", "simplify", "review"]);
|
|
78
61
|
});
|
|
79
62
|
|
|
80
63
|
it("labels should include the order number", () => {
|
|
@@ -92,25 +75,12 @@ describe("STEP_NAMES", () => {
|
|
|
92
75
|
});
|
|
93
76
|
|
|
94
77
|
describe("STEP_LABELS", () => {
|
|
95
|
-
it("should have
|
|
96
|
-
expect(Object.keys(STEP_LABELS)).toEqual([
|
|
97
|
-
"research",
|
|
98
|
-
"plan",
|
|
99
|
-
"planAnnotations",
|
|
100
|
-
"planImplementation",
|
|
101
|
-
"implement",
|
|
102
|
-
"review",
|
|
103
|
-
"createPr",
|
|
104
|
-
"removeLabel",
|
|
105
|
-
"refresh",
|
|
106
|
-
]);
|
|
78
|
+
it("should have keys matching step names", () => {
|
|
79
|
+
expect(Object.keys(STEP_LABELS)).toEqual(["plan", "implement", "simplify", "review"]);
|
|
107
80
|
});
|
|
108
81
|
|
|
109
|
-
it("
|
|
110
|
-
const
|
|
111
|
-
delete pipelineLabels.refresh;
|
|
112
|
-
|
|
113
|
-
for (const label of Object.values(pipelineLabels)) {
|
|
82
|
+
it("labels should have numeric prefixes", () => {
|
|
83
|
+
for (const label of Object.values(STEP_LABELS)) {
|
|
114
84
|
expect(label).toMatch(/^\d{2}-/);
|
|
115
85
|
}
|
|
116
86
|
});
|
|
@@ -127,12 +97,9 @@ describe("ARTIFACTS", () => {
|
|
|
127
97
|
it("should have all expected keys", () => {
|
|
128
98
|
expect(Object.keys(ARTIFACTS)).toEqual([
|
|
129
99
|
"initialRamblings",
|
|
130
|
-
"research",
|
|
131
100
|
"plan",
|
|
132
|
-
"planAnnotations",
|
|
133
|
-
"planAnnotationsAddressed",
|
|
134
|
-
"planImplementation",
|
|
135
101
|
"completedSummary",
|
|
102
|
+
"simplifySummary",
|
|
136
103
|
"review",
|
|
137
104
|
"prUrl",
|
|
138
105
|
]);
|
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
export const TEMPLATES = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
implement: "05_implement.prompt.md",
|
|
7
|
-
review: "06_review.prompt.md",
|
|
8
|
-
refresh: "07_refresh.prompt.md",
|
|
2
|
+
plan: "01_plan.prompt.md",
|
|
3
|
+
implement: "02_implement.prompt.md",
|
|
4
|
+
simplify: "03_simplify.prompt.md",
|
|
5
|
+
review: "04_review.prompt.md",
|
|
9
6
|
} as const;
|
|
10
7
|
|
|
11
8
|
export const PIPELINE_STEPS = [
|
|
12
|
-
{ order: 1, name: "
|
|
13
|
-
{ order: 2, name: "
|
|
14
|
-
{ order: 3, name: "
|
|
15
|
-
{ order: 4, name: "
|
|
16
|
-
{ order: 5, name: "implement", label: "05-Implement" },
|
|
17
|
-
{ order: 6, name: "review", label: "06-Review" },
|
|
18
|
-
{ order: 7, name: "create-pr", label: "07-Create PR" },
|
|
19
|
-
{ order: 8, name: "remove-label", label: "08-Remove Label" },
|
|
9
|
+
{ order: 1, name: "plan", label: "01-Plan" },
|
|
10
|
+
{ order: 2, name: "implement", label: "02-Implement" },
|
|
11
|
+
{ order: 3, name: "simplify", label: "03-Simplify" },
|
|
12
|
+
{ order: 4, name: "review", label: "04-Review" },
|
|
20
13
|
] as const;
|
|
21
14
|
|
|
22
15
|
export type StepName = (typeof PIPELINE_STEPS)[number]["name"];
|
|
23
16
|
export const STEP_NAMES = PIPELINE_STEPS.map((s) => s.name);
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/** Keyed lookup for step labels (includes standalone "refresh" step) */
|
|
30
|
-
export const STEP_LABELS = {
|
|
31
|
-
...Object.fromEntries(PIPELINE_STEPS.map((s) => [toCamelCase(s.name), s.label])),
|
|
32
|
-
refresh: "Refresh",
|
|
33
|
-
} as Record<string, string>;
|
|
18
|
+
/** Keyed lookup for step labels */
|
|
19
|
+
export const STEP_LABELS = Object.fromEntries(
|
|
20
|
+
PIPELINE_STEPS.map((s) => [s.name, s.label]),
|
|
21
|
+
) as Record<string, string>;
|
|
34
22
|
|
|
35
23
|
export const ARTIFACTS = {
|
|
36
24
|
initialRamblings: "initial-ramblings.md",
|
|
37
|
-
research: "research.md",
|
|
38
25
|
plan: "plan.md",
|
|
39
|
-
planAnnotations: "plan-annotations.md",
|
|
40
|
-
planAnnotationsAddressed: "plan-annotations-addressed.md",
|
|
41
|
-
planImplementation: "plan-implementation.md",
|
|
42
26
|
completedSummary: "completed-summary.md",
|
|
27
|
+
simplifySummary: "simplify-summary.md",
|
|
43
28
|
review: "review.md",
|
|
44
29
|
prUrl: "pr-url.txt",
|
|
45
30
|
} as const;
|
|
@@ -43,11 +43,10 @@ describe("runClaude (mocked spawn-claude)", () => {
|
|
|
43
43
|
|
|
44
44
|
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
45
45
|
|
|
46
|
-
const { runClaude } = await import("./
|
|
46
|
+
const { runClaude } = await import("./claude-cli");
|
|
47
47
|
|
|
48
48
|
const result = await runClaude({
|
|
49
49
|
promptFile: "test-prompt.md",
|
|
50
|
-
permissionMode: "plan",
|
|
51
50
|
maxTurns: 10,
|
|
52
51
|
});
|
|
53
52
|
|
|
@@ -62,8 +61,8 @@ describe("runClaude (mocked spawn-claude)", () => {
|
|
|
62
61
|
"--output-format",
|
|
63
62
|
"stream-json",
|
|
64
63
|
"--verbose",
|
|
65
|
-
"--
|
|
66
|
-
"
|
|
64
|
+
"--include-partial-messages",
|
|
65
|
+
"--dangerously-skip-permissions",
|
|
67
66
|
"--max-turns",
|
|
68
67
|
"10",
|
|
69
68
|
"@test-prompt.md",
|
|
@@ -71,85 +70,66 @@ describe("runClaude (mocked spawn-claude)", () => {
|
|
|
71
70
|
);
|
|
72
71
|
});
|
|
73
72
|
|
|
74
|
-
it("
|
|
73
|
+
it("logs tool names from stream_event and shows thinking indicator", async () => {
|
|
74
|
+
const infoSpy = vi.spyOn(consola, "info");
|
|
75
|
+
|
|
76
|
+
const thinkingEvent = {
|
|
77
|
+
type: "stream_event",
|
|
78
|
+
event: {
|
|
79
|
+
type: "content_block_start",
|
|
80
|
+
content_block: { type: "thinking" },
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const toolEvent = {
|
|
84
|
+
type: "stream_event",
|
|
85
|
+
event: {
|
|
86
|
+
type: "content_block_start",
|
|
87
|
+
content_block: { type: "tool_use", name: "Edit" },
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
const resultEvent = {
|
|
91
|
+
result: "Done",
|
|
92
|
+
is_error: false,
|
|
93
|
+
total_cost_usd: 0.01,
|
|
94
|
+
num_turns: 1,
|
|
95
|
+
};
|
|
96
|
+
|
|
75
97
|
mockSpawnImpl = () => ({
|
|
76
|
-
stdout:
|
|
98
|
+
stdout: [thinkingEvent, toolEvent, resultEvent].map((e) => JSON.stringify(e)).join("\n"),
|
|
77
99
|
exitCode: 0,
|
|
78
100
|
});
|
|
79
101
|
|
|
80
102
|
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
81
103
|
|
|
82
|
-
const { runClaude } = await import("./
|
|
104
|
+
const { runClaude } = await import("./claude-cli");
|
|
105
|
+
const result = await runClaude({ promptFile: "test.md" });
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
permissionMode: "acceptEdits",
|
|
87
|
-
});
|
|
107
|
+
expect(result.result).toBe("Done");
|
|
108
|
+
expect(result.num_turns).toBe(1);
|
|
88
109
|
|
|
89
|
-
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
92
|
-
});
|
|
110
|
+
const infoCalls = infoSpy.mock.calls.map((c) => String(c[0]));
|
|
111
|
+
expect(infoCalls.some((msg) => msg.includes("thinking"))).toBe(true);
|
|
112
|
+
expect(infoCalls.some((msg) => msg.includes("Edit"))).toBe(true);
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
mockSpawnImpl = () => {
|
|
97
|
-
callCount++;
|
|
98
|
-
if (callCount < 3) {
|
|
99
|
-
return { stdout: "", exitCode: 1 };
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
stdout: JSON.stringify({
|
|
103
|
-
result: "ok",
|
|
104
|
-
is_error: false,
|
|
105
|
-
total_cost_usd: 0,
|
|
106
|
-
num_turns: 1,
|
|
107
|
-
}),
|
|
108
|
-
exitCode: 0,
|
|
109
|
-
};
|
|
110
|
-
};
|
|
114
|
+
infoSpy.mockRestore();
|
|
115
|
+
});
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
maxRetries: 5,
|
|
117
|
-
retryDelayMs: 1,
|
|
118
|
-
maxRetryDelayMs: 5,
|
|
117
|
+
it("returns fallback when no result event in stream", async () => {
|
|
118
|
+
mockSpawnImpl = () => ({
|
|
119
|
+
stdout: '{"type":"system","message":"starting"}',
|
|
120
|
+
exitCode: 0,
|
|
119
121
|
});
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
124
|
+
|
|
125
|
+
const { runClaude } = await import("./claude-cli");
|
|
122
126
|
|
|
123
127
|
const result = await runClaude({
|
|
124
128
|
promptFile: "test.md",
|
|
125
|
-
permissionMode: "plan",
|
|
126
|
-
retry: true,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(result.result).toBe("ok");
|
|
130
|
-
expect(callCount).toBe(3);
|
|
131
|
-
}, 10_000);
|
|
132
|
-
|
|
133
|
-
it("throws after max retries exhausted", async () => {
|
|
134
|
-
mockSpawnImpl = () => ({ stdout: "", exitCode: 1 });
|
|
135
|
-
|
|
136
|
-
await initConfig({
|
|
137
|
-
repo: "test/repo",
|
|
138
|
-
mainBranch: "main",
|
|
139
|
-
loopRetryEnabled: true,
|
|
140
|
-
maxRetries: 2,
|
|
141
|
-
retryDelayMs: 1,
|
|
142
|
-
maxRetryDelayMs: 5,
|
|
143
129
|
});
|
|
144
130
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
promptFile: "test.md",
|
|
150
|
-
permissionMode: "plan",
|
|
151
|
-
retry: true,
|
|
152
|
-
}),
|
|
153
|
-
).rejects.toThrow("Claude failed after 2 retries");
|
|
154
|
-
}, 10_000);
|
|
131
|
+
expect(result.result).toBe("");
|
|
132
|
+
expect(result.is_error).toBe(false);
|
|
133
|
+
expect(result.total_cost_usd).toBe(0);
|
|
134
|
+
});
|
|
155
135
|
});
|
|
@@ -2,21 +2,23 @@ import { join } from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import consola from "consola";
|
|
4
4
|
|
|
5
|
+
import { fileExists, readFile, writeFile } from "../../../utils/fs.js";
|
|
6
|
+
import { execSafe, git } from "../../../utils/git/exec.js";
|
|
7
|
+
import { ghRaw } from "../../../utils/git/gh-cli-wrapper.js";
|
|
5
8
|
import { getConfig } from "../config.js";
|
|
6
|
-
import { ARTIFACTS
|
|
7
|
-
import {
|
|
9
|
+
import { ARTIFACTS } from "../prompt-templates/index.js";
|
|
10
|
+
import { log } from "../utils.js";
|
|
8
11
|
import type { IssueContext } from "../utils.js";
|
|
9
12
|
|
|
10
|
-
export async function
|
|
13
|
+
export async function createPr(ctx: IssueContext): Promise<string> {
|
|
11
14
|
const cfg = getConfig();
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
const existingUrl = await getExistingPrUrl(ctx.branch);
|
|
17
|
+
if (existingUrl) {
|
|
18
|
+
log(`PR already exists: ${existingUrl}`);
|
|
19
|
+
return existingUrl;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
logStep(STEP_LABELS.createPr, ctx);
|
|
19
|
-
|
|
20
22
|
await git(["push", "-u", cfg.remote, ctx.branch]);
|
|
21
23
|
|
|
22
24
|
const reviewPath = join(ctx.issueDir, ARTIFACTS.review);
|
|
@@ -33,9 +35,9 @@ export async function stepCreatePR(ctx: IssueContext): Promise<boolean> {
|
|
|
33
35
|
"",
|
|
34
36
|
"## Pipeline Artifacts",
|
|
35
37
|
"",
|
|
36
|
-
`- Research: \`.auto-claude/issue-${ctx.number}/research.md\``,
|
|
37
38
|
`- Plan: \`.auto-claude/issue-${ctx.number}/plan.md\``,
|
|
38
|
-
`- Implementation
|
|
39
|
+
`- Implementation: \`.auto-claude/issue-${ctx.number}/completed-summary.md\``,
|
|
40
|
+
`- Simplify: \`.auto-claude/issue-${ctx.number}/simplify-summary.md\``,
|
|
39
41
|
`- Review: \`.auto-claude/issue-${ctx.number}/review.md\``,
|
|
40
42
|
"",
|
|
41
43
|
"## Review Summary",
|
|
@@ -57,23 +59,84 @@ export async function stepCreatePR(ctx: IssueContext): Promise<boolean> {
|
|
|
57
59
|
ctx.title,
|
|
58
60
|
"--body",
|
|
59
61
|
body,
|
|
60
|
-
"--label",
|
|
61
|
-
cfg.triggerLabel,
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
-
if (prUrl) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
if (!prUrl) {
|
|
65
|
+
throw new Error("Failed to create PR — gh returned empty output");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
writeFile(join(ctx.issueDir, ARTIFACTS.prUrl), prUrl);
|
|
69
|
+
log(`PR created: ${prUrl}`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await attachArtifacts(ctx, prUrl);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
consola.warn(`Artifact upload failed (non-blocking): ${e}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return prUrl;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function attachArtifacts(ctx: IssueContext, prUrl: string): Promise<void> {
|
|
81
|
+
const archivePath = join(ctx.issueDir, "artifacts.tar.gz");
|
|
82
|
+
const tag = `ac-issue-${ctx.number}`;
|
|
83
|
+
const cfg = getConfig();
|
|
84
|
+
|
|
85
|
+
log("Archiving pipeline artifacts…");
|
|
86
|
+
await execSafe("tar", [
|
|
87
|
+
"czf",
|
|
88
|
+
archivePath,
|
|
89
|
+
"-C",
|
|
90
|
+
ctx.issueDir,
|
|
91
|
+
"--exclude=*.prompt.md",
|
|
92
|
+
"--exclude=artifacts.tar.gz",
|
|
93
|
+
".",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
log("Uploading artifacts to GitHub release…");
|
|
97
|
+
await ghRaw(["release", "delete", tag, "--yes", "--repo", cfg.repo]);
|
|
98
|
+
|
|
99
|
+
await ghRaw([
|
|
100
|
+
"release",
|
|
101
|
+
"create",
|
|
102
|
+
tag,
|
|
103
|
+
"--prerelease",
|
|
104
|
+
"--title",
|
|
105
|
+
`Artifacts: #${ctx.number}`,
|
|
106
|
+
"--repo",
|
|
107
|
+
cfg.repo,
|
|
108
|
+
archivePath,
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
const assetUrl = await ghRaw([
|
|
112
|
+
"release",
|
|
113
|
+
"view",
|
|
114
|
+
tag,
|
|
115
|
+
"--json",
|
|
116
|
+
"assets",
|
|
117
|
+
"--jq",
|
|
118
|
+
".assets[0].url",
|
|
119
|
+
"--repo",
|
|
120
|
+
cfg.repo,
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
if (!assetUrl) {
|
|
124
|
+
consola.warn("Could not get artifact download URL — skipping PR comment");
|
|
125
|
+
return;
|
|
71
126
|
}
|
|
72
127
|
|
|
73
|
-
|
|
128
|
+
const comment = [
|
|
129
|
+
"## Pipeline Artifacts",
|
|
130
|
+
"",
|
|
131
|
+
`[Download artifacts (tar.gz)](${assetUrl})`,
|
|
132
|
+
"",
|
|
133
|
+
"Contains: plan.md, completed-summary.md, simplify-summary.md, review.md",
|
|
134
|
+
].join("\n");
|
|
135
|
+
|
|
136
|
+
await ghRaw(["pr", "comment", prUrl, "--body", comment]);
|
|
74
137
|
}
|
|
75
138
|
|
|
76
|
-
async function
|
|
139
|
+
async function getExistingPrUrl(branch: string): Promise<string | null> {
|
|
77
140
|
const out = await ghRaw([
|
|
78
141
|
"pr",
|
|
79
142
|
"list",
|
|
@@ -84,12 +147,13 @@ async function hasOpenPR(branch: string): Promise<boolean> {
|
|
|
84
147
|
"--state",
|
|
85
148
|
"open",
|
|
86
149
|
"--json",
|
|
87
|
-
"
|
|
150
|
+
"url",
|
|
88
151
|
]);
|
|
89
152
|
try {
|
|
90
|
-
const prs = JSON.parse(out)
|
|
91
|
-
return
|
|
153
|
+
const prs = JSON.parse(out) as Array<{ url: string }>;
|
|
154
|
+
return prs.length > 0 ? prs[0].url : null;
|
|
92
155
|
} catch {
|
|
93
|
-
|
|
156
|
+
consola.debug(`Failed to parse PR list JSON for branch "${branch}"`);
|
|
157
|
+
return null;
|
|
94
158
|
}
|
|
95
159
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import consola from "consola";
|
|
1
2
|
import { getConfig } from "../config.js";
|
|
2
|
-
import {
|
|
3
|
+
import { gh } from "../../../utils/git/gh-cli-wrapper.js";
|
|
4
|
+
import { buildIssueContext, log } from "../utils.js";
|
|
3
5
|
import type { IssueContext } from "../utils.js";
|
|
4
6
|
|
|
5
7
|
interface GhIssue {
|
|
@@ -59,6 +61,7 @@ export async function fetchIssue(issueNumber: number): Promise<IssueContext | un
|
|
|
59
61
|
]);
|
|
60
62
|
return buildIssueContext(issue, cfg.repo, cfg.scopePath);
|
|
61
63
|
} catch {
|
|
64
|
+
consola.debug(`Could not fetch issue #${issueNumber} from ${cfg.repo}`);
|
|
62
65
|
return undefined;
|
|
63
66
|
}
|
|
64
67
|
}
|
|
@@ -4,16 +4,11 @@ import consola from "consola";
|
|
|
4
4
|
|
|
5
5
|
import { getConfig } from "../config.js";
|
|
6
6
|
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
log,
|
|
13
|
-
logStep,
|
|
14
|
-
resolveTemplate,
|
|
15
|
-
runClaude,
|
|
16
|
-
} from "../utils.js";
|
|
7
|
+
import { fileExists, readFile } from "../../../utils/fs.js";
|
|
8
|
+
import { git } from "../../../utils/git/exec.js";
|
|
9
|
+
import { runClaude } from "../claude-cli.js";
|
|
10
|
+
import { resolveTemplate } from "../templates.js";
|
|
11
|
+
import { buildTokens, log, logStep } from "../utils.js";
|
|
17
12
|
import type { IssueContext } from "../utils.js";
|
|
18
13
|
|
|
19
14
|
export async function stepImplement(ctx: IssueContext): Promise<boolean> {
|
|
@@ -29,15 +24,17 @@ export async function stepImplement(ctx: IssueContext): Promise<boolean> {
|
|
|
29
24
|
|
|
30
25
|
await git(["checkout", ctx.branch]);
|
|
31
26
|
|
|
27
|
+
const reviewPath = join(ctx.issueDir, ARTIFACTS.review);
|
|
28
|
+
const reviewFeedback = fileExists(reviewPath) ? readFile(reviewPath) : "";
|
|
29
|
+
|
|
32
30
|
for (let i = 1; i <= maxIterations; i++) {
|
|
33
31
|
log(`Implementation iteration ${i}/${maxIterations}`);
|
|
34
32
|
|
|
35
|
-
const tokens = buildTokens(ctx);
|
|
33
|
+
const tokens = buildTokens(ctx, { REVIEW_FEEDBACK: reviewFeedback });
|
|
36
34
|
const promptFile = resolveTemplate(TEMPLATES.implement, tokens, ctx.issueDir);
|
|
37
35
|
|
|
38
36
|
const result = await runClaude({
|
|
39
37
|
promptFile,
|
|
40
|
-
permissionMode: "acceptEdits",
|
|
41
38
|
maxTurns: getConfig().maxTurns,
|
|
42
39
|
});
|
|
43
40
|
|
|
@@ -48,10 +45,6 @@ export async function stepImplement(ctx: IssueContext): Promise<boolean> {
|
|
|
48
45
|
|
|
49
46
|
if (fileExists(completedPath)) {
|
|
50
47
|
log(`Implementation complete after ${i} iteration(s)`);
|
|
51
|
-
await commitArtifacts(
|
|
52
|
-
ctx,
|
|
53
|
-
`chore(auto-claude): implementation complete for ${ctx.repo}#${ctx.number}`,
|
|
54
|
-
);
|
|
55
48
|
return true;
|
|
56
49
|
}
|
|
57
50
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
4
|
+
import { ensureBranch, runStepWithArtifact } from "../utils.js";
|
|
5
|
+
import type { IssueContext } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
export async function stepPlan(ctx: IssueContext): Promise<boolean> {
|
|
8
|
+
await ensureBranch(ctx.branch);
|
|
9
|
+
|
|
10
|
+
return runStepWithArtifact({
|
|
11
|
+
stepName: STEP_LABELS.plan,
|
|
12
|
+
ctx,
|
|
13
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.plan),
|
|
14
|
+
templateName: TEMPLATES.plan,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function stepSimplify(ctx: IssueContext): Promise<boolean> {
|
|
19
|
+
return runStepWithArtifact({
|
|
20
|
+
stepName: STEP_LABELS.simplify,
|
|
21
|
+
ctx,
|
|
22
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.simplifySummary),
|
|
23
|
+
templateName: TEMPLATES.simplify,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function stepReview(ctx: IssueContext): Promise<boolean> {
|
|
28
|
+
return runStepWithArtifact({
|
|
29
|
+
stepName: STEP_LABELS.review,
|
|
30
|
+
ctx,
|
|
31
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.review),
|
|
32
|
+
templateName: TEMPLATES.review,
|
|
33
|
+
});
|
|
34
|
+
}
|