@towles/tool 0.0.53 → 0.0.55

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.
Files changed (43) hide show
  1. package/README.md +82 -72
  2. package/package.json +8 -7
  3. package/src/commands/auto-claude.ts +219 -0
  4. package/src/commands/doctor.ts +1 -34
  5. package/src/config/settings.ts +0 -10
  6. package/src/lib/auto-claude/config.test.ts +53 -0
  7. package/src/lib/auto-claude/config.ts +68 -0
  8. package/src/lib/auto-claude/index.ts +14 -0
  9. package/src/lib/auto-claude/pipeline.test.ts +14 -0
  10. package/src/lib/auto-claude/pipeline.ts +64 -0
  11. package/src/lib/auto-claude/prompt-templates/01-prompt-research.md +28 -0
  12. package/src/lib/auto-claude/prompt-templates/02-prompt-plan.md +28 -0
  13. package/src/lib/auto-claude/prompt-templates/03-prompt-plan-annotations.md +21 -0
  14. package/src/lib/auto-claude/prompt-templates/04-prompt-plan-implementation.md +33 -0
  15. package/src/lib/auto-claude/prompt-templates/05-prompt-implement.md +31 -0
  16. package/src/lib/auto-claude/prompt-templates/06-prompt-review.md +30 -0
  17. package/src/lib/auto-claude/prompt-templates/07-prompt-refresh.md +39 -0
  18. package/src/lib/auto-claude/prompt-templates/index.test.ts +145 -0
  19. package/src/lib/auto-claude/prompt-templates/index.ts +44 -0
  20. package/src/lib/auto-claude/steps/create-pr.ts +93 -0
  21. package/src/lib/auto-claude/steps/fetch-issues.ts +64 -0
  22. package/src/lib/auto-claude/steps/implement.ts +63 -0
  23. package/src/lib/auto-claude/steps/plan-annotations.ts +54 -0
  24. package/src/lib/auto-claude/steps/plan-implementation.ts +14 -0
  25. package/src/lib/auto-claude/steps/plan.ts +14 -0
  26. package/src/lib/auto-claude/steps/refresh.ts +114 -0
  27. package/src/lib/auto-claude/steps/remove-label.ts +22 -0
  28. package/src/lib/auto-claude/steps/research.ts +21 -0
  29. package/src/lib/auto-claude/steps/review.ts +14 -0
  30. package/src/lib/auto-claude/utils.test.ts +136 -0
  31. package/src/lib/auto-claude/utils.ts +334 -0
  32. package/src/commands/ralph/plan/add.ts +0 -69
  33. package/src/commands/ralph/plan/done.ts +0 -82
  34. package/src/commands/ralph/plan/list.test.ts +0 -48
  35. package/src/commands/ralph/plan/list.ts +0 -100
  36. package/src/commands/ralph/plan/remove.ts +0 -71
  37. package/src/commands/ralph/run.test.ts +0 -607
  38. package/src/commands/ralph/run.ts +0 -362
  39. package/src/commands/ralph/show.ts +0 -88
  40. package/src/lib/ralph/execution.ts +0 -292
  41. package/src/lib/ralph/formatter.ts +0 -240
  42. package/src/lib/ralph/index.ts +0 -4
  43. package/src/lib/ralph/state.ts +0 -201
@@ -0,0 +1,64 @@
1
+ import { join } from "node:path";
2
+
3
+ import { getConfig } from "./config.js";
4
+ import { ARTIFACTS, PIPELINE_STEPS } from "./prompt-templates/index.js";
5
+ import type { StepName } from "./prompt-templates/index.js";
6
+ import { stepCreatePR } from "./steps/create-pr.js";
7
+ import { stepImplement } from "./steps/implement.js";
8
+ import { stepPlanAnnotations } from "./steps/plan-annotations.js";
9
+ import { stepPlanImplementation } from "./steps/plan-implementation.js";
10
+ import { stepPlan } from "./steps/plan.js";
11
+ import { stepRemoveLabel } from "./steps/remove-label.js";
12
+ import { stepResearch } from "./steps/research.js";
13
+ import { stepReview } from "./steps/review.js";
14
+ import { ensureDir, fileExists, git, log, writeFile } from "./utils.js";
15
+ import type { IssueContext } from "./utils.js";
16
+
17
+ const STEP_RUNNERS: Record<StepName, (ctx: IssueContext) => Promise<boolean>> = {
18
+ research: stepResearch,
19
+ plan: stepPlan,
20
+ "plan-annotations": stepPlanAnnotations,
21
+ "plan-implementation": stepPlanImplementation,
22
+ implement: stepImplement,
23
+ review: stepReview,
24
+ "create-pr": stepCreatePR,
25
+ "remove-label": stepRemoveLabel,
26
+ };
27
+
28
+ export { type StepName, STEP_NAMES } from "./prompt-templates/index.js";
29
+
30
+ export async function runPipeline(ctx: IssueContext, untilStep?: StepName): Promise<void> {
31
+ log(`Pipeline starting for ${ctx.repo}#${ctx.number}: ${ctx.title}`);
32
+
33
+ ensureDir(ctx.issueDir);
34
+ const ramblingsPath = join(ctx.issueDir, ARTIFACTS.initialRamblings);
35
+ if (!fileExists(ramblingsPath)) {
36
+ const content = `# ${ctx.title}\n\n> ${ctx.repo}#${ctx.number}\n\n${ctx.body ?? ""}`;
37
+ writeFile(ramblingsPath, content);
38
+ log("Saved initial-ramblings.md");
39
+ }
40
+
41
+ for (const step of PIPELINE_STEPS) {
42
+ const runner = STEP_RUNNERS[step.name];
43
+ const success = await runner(ctx);
44
+
45
+ if (!success) {
46
+ log(`Pipeline stopped at "${step.name}" for ${ctx.repo}#${ctx.number}`);
47
+ await checkoutMain();
48
+ return;
49
+ }
50
+
51
+ if (untilStep && step.name === untilStep) {
52
+ log(`Pipeline paused after "${step.name}" (--until ${untilStep})`);
53
+ await checkoutMain();
54
+ return;
55
+ }
56
+ }
57
+
58
+ log(`Pipeline complete for ${ctx.repo}#${ctx.number}`);
59
+ await checkoutMain();
60
+ }
61
+
62
+ async function checkoutMain(): Promise<void> {
63
+ await git(["checkout", getConfig().mainBranch]).catch(() => {});
64
+ }
@@ -0,0 +1,28 @@
1
+ You are a senior developer researching a codebase to prepare for implementing a GitHub issue.
2
+
3
+ ## Your task
4
+
5
+ Read the issue description in @{{ISSUE_DIR}}/initial-ramblings.md and then **research the codebase in depth** to understand what would be involved in implementing it.
6
+
7
+ **CRITICAL RULES:**
8
+
9
+ - Do **NOT** implement the issue. Do not create, modify, or delete any project source files.
10
+ - Your **ONLY** deliverable is writing the file @{{ISSUE_DIR}}/research.md.
11
+ - If the issue seems trivial, research it anyway — document the relevant files, patterns, and context.
12
+
13
+ ## Where to look
14
+
15
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`. Start your investigation there but explore any related files across the monorepo.
16
+
17
+ Read every relevant file in full. Understand how the system works deeply — its architecture, data flow, and all its specificities. Do not skim. Do not stop researching until you have a thorough understanding of every part of the codebase that this issue touches.
18
+
19
+ ## What to write in @{{ISSUE_DIR}}/research.md
20
+
21
+ 1. **Relevant files** — every file that would need to be read or modified, with brief descriptions of what each does
22
+ 2. **Existing patterns** — how similar features are currently implemented in this codebase (naming conventions, folder structure, component patterns, API patterns)
23
+ 3. **Dependencies** — libraries, utilities, shared code, and services that are relevant
24
+ 4. **Potential impact areas** — what else might break or need updating (tests, types, imports, configs)
25
+ 5. **Edge cases and constraints** — anything tricky that the implementation should watch out for
26
+ 6. **Reference implementations** — if there's a similar feature already built, document it as a reference
27
+
28
+ Be thorough. Keep researching until you have complete understanding — missing information here means a worse plan later.
@@ -0,0 +1,28 @@
1
+ You are a senior developer planning the implementation for a GitHub issue.
2
+
3
+ Read the issue in @{{ISSUE_DIR}}/initial-ramblings.md and the research in @{{ISSUE_DIR}}/research.md.
4
+
5
+ **CRITICAL RULES:**
6
+
7
+ - Do **NOT** implement the issue. Do not create, modify, or delete any project source files.
8
+ - Your **ONLY** deliverable is writing the file @{{ISSUE_DIR}}/plan.md.
9
+ - Read the actual source files before suggesting changes. Base the plan on what the code actually does, not assumptions.
10
+
11
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`.
12
+
13
+ Write @{{ISSUE_DIR}}/plan.md containing:
14
+
15
+ 1. **Summary** — what we're building and why (1-2 paragraphs)
16
+ 2. **Approach** — the high-level technical approach chosen
17
+ 3. **Architectural decisions** — any significant choices made and why (e.g., which component pattern, state management approach, API structure)
18
+ 4. **Key code snippets** — include concrete code examples showing the important parts of the implementation (function signatures, component structure, schema changes, etc.)
19
+ 5. **Scope boundaries** — what is explicitly out of scope to keep the change focused
20
+ 6. **Risks** — anything that could go wrong or needs special attention during implementation
21
+ 7. **Alternative approaches** — a brief section listing other valid ways to solve this problem. For each alternative, include: the approach name, a one-liner on how it works, and why the chosen approach was preferred. Consider industry best practices, common patterns, and obvious alternatives. This section is for PR reviewers only — it will NOT be used in the implementation plan.
22
+
23
+ **Design principles to follow:**
24
+
25
+ - Fixing a known issue later instead of now is not simplicity — if the plan touches an area with a known bug, address it.
26
+ - Adding a second primitive for something we already have a primitive for is not simplicity — reuse existing abstractions.
27
+
28
+ Keep it concise and focused on decisions, not on repeating the research.
@@ -0,0 +1,21 @@
1
+ You are a senior developer revising a plan based on reviewer feedback.
2
+
3
+ Read the current plan in @{{ISSUE_DIR}}/plan.md and the reviewer's annotations in @{{ISSUE_DIR}}/plan-annotations.md.
4
+
5
+ The original issue is described in @{{ISSUE_DIR}}/initial-ramblings.md and the research is in @{{ISSUE_DIR}}/research.md.
6
+
7
+ **CRITICAL RULES:**
8
+
9
+ - Do **NOT** implement the issue. Do not create, modify, or delete any project source files.
10
+ - Your **ONLY** deliverable is updating @{{ISSUE_DIR}}/plan.md to address the annotations.
11
+
12
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`.
13
+
14
+ ## Instructions
15
+
16
+ - Read every annotation carefully.
17
+ - Address all the notes — update the plan to incorporate the feedback.
18
+ - If an annotation asks a question, answer it in the plan.
19
+ - If an annotation suggests a different approach, evaluate it and update the plan accordingly.
20
+ - If an annotation points out something missing, add it.
21
+ - Keep the same structure and formatting of the plan, just improve its content.
@@ -0,0 +1,33 @@
1
+ You are breaking down a plan into a detailed, ordered implementation checklist.
2
+
3
+ Read the issue in @{{ISSUE_DIR}}/initial-ramblings.md, the research in @{{ISSUE_DIR}}/research.md, and the plan in @{{ISSUE_DIR}}/plan.md.
4
+
5
+ **CRITICAL RULES:**
6
+
7
+ - Do **NOT** implement the issue. Do not create, modify, or delete any project source files.
8
+ - Your **ONLY** deliverable is writing the file @{{ISSUE_DIR}}/plan-implementation.md.
9
+
10
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`.
11
+
12
+ Write @{{ISSUE_DIR}}/plan-implementation.md with:
13
+
14
+ - An **ordered task list using markdown checkboxes** (`- [ ]` for each task)
15
+ - Each task should be small enough to implement in one focused session
16
+ - For each task:
17
+ - **Files to modify** — specific file paths
18
+ - **What to change** — concrete description of the changes (not vague like "update the component")
19
+ - **Acceptance criteria** — how to verify this task is done correctly
20
+ - Tasks should be ordered so each builds on the previous (no forward dependencies)
21
+ - Include any necessary setup tasks (new files to create, dependencies to add, etc.)
22
+ - The final task should always be "verify everything works together"
23
+
24
+ IMPORTANT: Every task MUST use the `- [ ]` checkbox format. Example:
25
+
26
+ ```
27
+ - [ ] **Task 1: Create the API endpoint**
28
+ - Files: `src/server/routes/foo.ts`
29
+ - Changes: Add GET /api/foo endpoint that returns...
30
+ - Acceptance: Endpoint responds with 200 and correct shape
31
+ ```
32
+
33
+ Be specific enough that a developer could follow this checklist without needing to re-read the research or plan.
@@ -0,0 +1,31 @@
1
+ You are an implementation agent. Your job is to follow the checklist in @{{ISSUE_DIR}}/plan-implementation.md task by task.
2
+
3
+ The original issue is described in @{{ISSUE_DIR}}/initial-ramblings.md — this is background context only. Do NOT decide on your own whether the issue is "done". Your ONLY source of truth is the checklist.
4
+
5
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`.
6
+
7
+ ## How to work
8
+
9
+ 1. Read @{{ISSUE_DIR}}/plan-implementation.md — Find the highest priority (`- [ ]`) task to work. This should be the one YOU decide has the highest priority. Not necessarily the first one
10
+ 2. Execute that task (edit files, run commands, whatever the task says)
11
+ 3. After completing it, update @{{ISSUE_DIR}}/plan-implementation.md to change `- [ ]` to `- [x]` for that task
12
+ 4. Make a git commit ex: `feat(scope): description` or `fix(scope): description`
13
+ 5. Move to the next unchecked task. Repeat until all tasks are done or you run out of turns.
14
+
15
+ Do NOT push to remote — the pipeline handles that.
16
+ Do NOT stop until all tasks and phases are completed.
17
+
18
+ ## When ALL checkboxes are `- [x]`
19
+
20
+ Write @{{ISSUE_DIR}}/completed-summary.md with a brief summary of everything that was implemented. This file signals completion — do NOT create it if ANY tasks remain unchecked.
21
+
22
+ ## Code quality rules
23
+
24
+ - Do not add unnecessary comments or jsdocs to code you write.
25
+ - Do not use `any` or `unknown` types — use proper typing.
26
+ - Follow existing codebase patterns and conventions (check nearby files for reference).
27
+ - Do NOT skip tasks just because the end result already looks correct.
28
+ - Follow the checklist literally — if a task says "commit", make a commit. If it says "verify", run the verification.
29
+ - If you encounter something unexpected, use your best judgment and proceed.
30
+ - Fixing a known issue later instead of now is not simplicity — if you see a bug or problem in the area you're working on, fix it.
31
+ - Adding a second primitive for something we already have a primitive for is not simplicity — reuse existing abstractions instead of creating parallel ones (exceptions might exist, but don't use it as an easy way out).
@@ -0,0 +1,30 @@
1
+ You are reviewing code changes for the issue described in @{{ISSUE_DIR}}/initial-ramblings.md. The implementation plan in @{{ISSUE_DIR}}/plan-implementation.md describes what should have been done.
2
+
3
+ Review the changes by running `git diff {{MAIN_BRANCH}}...HEAD` and check for:
4
+
5
+ 1. **Correctness** — do the changes actually implement what the plan describes?
6
+ 2. **Missing imports** — are all imports present and correct?
7
+ 3. **Type errors** — any obvious TypeScript issues?
8
+ 4. **Unused code** — variables, imports, or functions that were added but never used?
9
+ 5. **Pattern consistency** — do the changes follow the existing codebase patterns?
10
+ 6. **Security issues** — any command injection, XSS, SQL injection, or other vulnerabilities?
11
+ 7. **Edge cases** — anything that could break under unusual input or conditions?
12
+ 8. **Incomplete work** — TODO comments, placeholder values, or unfinished implementations?
13
+
14
+ If you find issues:
15
+
16
+ - Fix them directly (edit the files)
17
+ - Make a separate commit for the fixes: `fix(scope): review fixes for issue #N`
18
+
19
+ Write your review summary to @{{ISSUE_DIR}}/review.md with:
20
+
21
+ - **Status**: PASS or PASS WITH FIXES
22
+ - **Issues found** (if any) — what was wrong and what you fixed
23
+ - **Confidence level** — how confident you are the implementation is correct (high/medium/low)
24
+ - **Notes** — anything the PR reviewer should pay attention to
25
+ - **Recommended follow-ups** — only include this section if you discovered genuinely valuable insights during the review. These should be high-signal recommendations, not minor noise. Examples of what qualifies:
26
+ - An existing primitive/utility that almost does what's needed but requires small changes to be reusable across the codebase
27
+ - A pattern inconsistency across the codebase that this change exposed
28
+ - A related improvement that would significantly benefit from the deep context gained during this issue
29
+ - Technical debt that directly affects the area touched by this change
30
+ For each follow-up, write it as a potential issue title + 1-2 sentence description of what and why. Omit this section entirely if there's nothing worth flagging.
@@ -0,0 +1,39 @@
1
+ You are updating an existing auto-claude branch to be compatible with the latest {{MAIN_BRANCH}}.
2
+
3
+ ## Context
4
+
5
+ Read these files to understand what this PR is doing:
6
+
7
+ - Original issue: @{{ISSUE_DIR}}/initial-ramblings.md
8
+ - Implementation plan: @{{ISSUE_DIR}}/plan-implementation.md
9
+ - What was implemented: @{{ISSUE_DIR}}/completed-summary.md
10
+
11
+ ## Your task
12
+
13
+ 1. Run `git diff {{MAIN_BRANCH}}...HEAD` to see what this PR currently changes vs {{MAIN_BRANCH}}
14
+ 2. Run `git log {{MAIN_BRANCH}}..HEAD --oneline` to see the PR's commit history
15
+ 3. Run `git diff HEAD...{{MAIN_BRANCH}}` to see what {{MAIN_BRANCH}} has changed since this branch diverged
16
+ 4. Analyze whether the PR's changes are still valid:
17
+ - Do imports still resolve? Have moved/renamed files been accounted for?
18
+ - Do APIs/types used by the PR still exist and have the same signatures?
19
+ - Are there conflicts with new code on {{MAIN_BRANCH}} that touches the same areas?
20
+ 5. If you find issues:
21
+ - Fix the code directly to work with current {{MAIN_BRANCH}}
22
+ - Run type-check (`pnpm type-check`) if applicable
23
+ - Commit fixes: `fix(scope): adapt to {{MAIN_BRANCH}} changes`
24
+ 6. If everything looks fine, note that no changes were needed.
25
+
26
+ The code for this project lives primarily at `{{SCOPE_PATH}}/`.
27
+
28
+ **CRITICAL RULES:**
29
+
30
+ - Do NOT re-implement the feature. Only fix what's broken due to {{MAIN_BRANCH}} changes.
31
+ - If {{MAIN_BRANCH}} has changed so fundamentally that the PR's approach is no longer viable (e.g., the module was refactored, APIs were replaced entirely), do NOT attempt to force a fix. Report `NEEDS-ATTENTION` with a clear explanation of what changed and why the current implementation can't be salvaged with minor fixes.
32
+ - Do NOT push to remote — the pipeline handles that.
33
+ - Do NOT modify the plan or research files.
34
+
35
+ Write a summary of what you did to @{{ISSUE_DIR}}/refresh-summary.md with:
36
+
37
+ - **Status**: UP-TO-DATE, ADAPTED, or NEEDS-ATTENTION
38
+ - **Changes made** (if any) — what broke and how you fixed it
39
+ - **Risk areas** — anything a reviewer should double-check
@@ -0,0 +1,145 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { describe, expect, it } from "vitest";
6
+
7
+ import { ARTIFACTS, PIPELINE_STEPS, STEP_LABELS, STEP_NAMES, TEMPLATES } from "./index";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+
11
+ describe("TEMPLATES", () => {
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
+ ]);
22
+ });
23
+
24
+ it("every template file should exist on disk", () => {
25
+ for (const [key, filename] of Object.entries(TEMPLATES)) {
26
+ const fullPath = join(__dirname, filename);
27
+ expect(existsSync(fullPath), `TEMPLATES.${key} → ${filename} missing`).toBe(true);
28
+ }
29
+ });
30
+
31
+ it("filenames should follow XX-prompt-*.md pattern", () => {
32
+ for (const filename of Object.values(TEMPLATES)) {
33
+ expect(filename).toMatch(/^\d{2}-prompt-.+\.md$/);
34
+ }
35
+ });
36
+
37
+ it("filenames should be in ascending numeric order", () => {
38
+ const numbers = Object.values(TEMPLATES).map((f) => Number.parseInt(f.slice(0, 2), 10));
39
+ for (let i = 1; i < numbers.length; i++) {
40
+ expect(numbers[i]).toBeGreaterThan(numbers[i - 1]);
41
+ }
42
+ });
43
+ });
44
+
45
+ describe("PIPELINE_STEPS", () => {
46
+ it("should have 8 steps", () => {
47
+ expect(PIPELINE_STEPS).toHaveLength(8);
48
+ });
49
+
50
+ it("each step should have order, name, and label", () => {
51
+ for (const step of PIPELINE_STEPS) {
52
+ expect(step).toHaveProperty("order");
53
+ expect(step).toHaveProperty("name");
54
+ expect(step).toHaveProperty("label");
55
+ expect(typeof step.order).toBe("number");
56
+ expect(typeof step.name).toBe("string");
57
+ expect(typeof step.label).toBe("string");
58
+ }
59
+ });
60
+
61
+ it("order should be sequential starting from 1", () => {
62
+ for (let i = 0; i < PIPELINE_STEPS.length; i++) {
63
+ expect(PIPELINE_STEPS[i].order).toBe(i + 1);
64
+ }
65
+ });
66
+
67
+ 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
+ ]);
78
+ });
79
+
80
+ it("labels should include the order number", () => {
81
+ for (const step of PIPELINE_STEPS) {
82
+ const prefix = String(step.order).padStart(2, "0");
83
+ expect(step.label.startsWith(prefix), `${step.label} should start with ${prefix}`).toBe(true);
84
+ }
85
+ });
86
+ });
87
+
88
+ describe("STEP_NAMES", () => {
89
+ it("should be derived from PIPELINE_STEPS", () => {
90
+ expect(STEP_NAMES).toEqual(PIPELINE_STEPS.map((s) => s.name));
91
+ });
92
+ });
93
+
94
+ describe("STEP_LABELS", () => {
95
+ it("should have camelCase keys for all pipeline steps plus refresh", () => {
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
+ ]);
107
+ });
108
+
109
+ it("pipeline labels should have numeric prefixes", () => {
110
+ const pipelineLabels = { ...STEP_LABELS };
111
+ delete pipelineLabels.refresh;
112
+
113
+ for (const label of Object.values(pipelineLabels)) {
114
+ expect(label).toMatch(/^\d{2}-/);
115
+ }
116
+ });
117
+
118
+ it("should match labels from PIPELINE_STEPS", () => {
119
+ for (const step of PIPELINE_STEPS) {
120
+ const values = Object.values(STEP_LABELS);
121
+ expect(values).toContain(step.label);
122
+ }
123
+ });
124
+ });
125
+
126
+ describe("ARTIFACTS", () => {
127
+ it("should have all expected keys", () => {
128
+ expect(Object.keys(ARTIFACTS)).toEqual([
129
+ "initialRamblings",
130
+ "research",
131
+ "plan",
132
+ "planAnnotations",
133
+ "planAnnotationsAddressed",
134
+ "planImplementation",
135
+ "completedSummary",
136
+ "review",
137
+ ]);
138
+ });
139
+
140
+ it("all values should be .md filenames", () => {
141
+ for (const filename of Object.values(ARTIFACTS)) {
142
+ expect(filename).toMatch(/^[\w-]+\.md$/);
143
+ }
144
+ });
145
+ });
@@ -0,0 +1,44 @@
1
+ export const TEMPLATES = {
2
+ research: "01-prompt-research.md",
3
+ plan: "02-prompt-plan.md",
4
+ planAnnotations: "03-prompt-plan-annotations.md",
5
+ planImplementation: "04-prompt-plan-implementation.md",
6
+ implement: "05-prompt-implement.md",
7
+ review: "06-prompt-review.md",
8
+ refresh: "07-prompt-refresh.md",
9
+ } as const;
10
+
11
+ export const PIPELINE_STEPS = [
12
+ { order: 1, name: "research", label: "01-Research" },
13
+ { order: 2, name: "plan", label: "02-Plan" },
14
+ { order: 3, name: "plan-annotations", label: "03-Plan-Annotations" },
15
+ { order: 4, name: "plan-implementation", label: "04-Plan-Implementation" },
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" },
20
+ ] as const;
21
+
22
+ export type StepName = (typeof PIPELINE_STEPS)[number]["name"];
23
+ export const STEP_NAMES = PIPELINE_STEPS.map((s) => s.name);
24
+
25
+ function toCamelCase(s: string): string {
26
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
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>;
34
+
35
+ export const ARTIFACTS = {
36
+ initialRamblings: "initial-ramblings.md",
37
+ research: "research.md",
38
+ plan: "plan.md",
39
+ planAnnotations: "plan-annotations.md",
40
+ planAnnotationsAddressed: "plan-annotations-addressed.md",
41
+ planImplementation: "plan-implementation.md",
42
+ completedSummary: "completed-summary.md",
43
+ review: "review.md",
44
+ } as const;
@@ -0,0 +1,93 @@
1
+ import { join } from "node:path";
2
+
3
+ import consola from "consola";
4
+
5
+ import { getConfig } from "../config.js";
6
+ import { ARTIFACTS, STEP_LABELS } from "../prompt-templates/index.js";
7
+ import { fileExists, ghRaw, git, log, logStep, readFile } from "../utils.js";
8
+ import type { IssueContext } from "../utils.js";
9
+
10
+ export async function stepCreatePR(ctx: IssueContext): Promise<boolean> {
11
+ const cfg = getConfig();
12
+
13
+ if (await hasOpenPR(ctx.branch)) {
14
+ logStep(STEP_LABELS.createPr, ctx, true);
15
+ return true;
16
+ }
17
+
18
+ logStep(STEP_LABELS.createPr, ctx);
19
+
20
+ await git(["push", "-u", cfg.remote, ctx.branch]);
21
+
22
+ const reviewPath = join(ctx.issueDir, ARTIFACTS.review);
23
+ const reviewSummary = fileExists(reviewPath)
24
+ ? readFile(reviewPath).slice(0, 2000)
25
+ : "No review summary available.";
26
+
27
+ const body = [
28
+ "## Summary",
29
+ "",
30
+ `Automated implementation for: **${ctx.title}**`,
31
+ "",
32
+ `Closes #${ctx.number}`,
33
+ "",
34
+ "## Pipeline Artifacts",
35
+ "",
36
+ `- Research: \`.auto-claude/issue-${ctx.number}/research.md\``,
37
+ `- Plan: \`.auto-claude/issue-${ctx.number}/plan.md\``,
38
+ `- Implementation Plan: \`.auto-claude/issue-${ctx.number}/plan-implementation.md\``,
39
+ `- Review: \`.auto-claude/issue-${ctx.number}/review.md\``,
40
+ "",
41
+ "## Review Summary",
42
+ "",
43
+ reviewSummary,
44
+ "",
45
+ "---",
46
+ "Generated by auto-claude pipeline",
47
+ ].join("\n");
48
+
49
+ const prUrl = await ghRaw([
50
+ "pr",
51
+ "create",
52
+ "--repo",
53
+ cfg.repo,
54
+ "--head",
55
+ ctx.branch,
56
+ "--title",
57
+ ctx.title,
58
+ "--body",
59
+ body,
60
+ "--label",
61
+ cfg.triggerLabel,
62
+ ]);
63
+
64
+ if (prUrl) {
65
+ log(`PR created: ${prUrl}`);
66
+ } else {
67
+ consola.error("Failed to create PR");
68
+ return false;
69
+ }
70
+
71
+ return true;
72
+ }
73
+
74
+ async function hasOpenPR(branch: string): Promise<boolean> {
75
+ const out = await ghRaw([
76
+ "pr",
77
+ "list",
78
+ "--repo",
79
+ getConfig().repo,
80
+ "--head",
81
+ branch,
82
+ "--state",
83
+ "open",
84
+ "--json",
85
+ "number",
86
+ ]);
87
+ try {
88
+ const prs = JSON.parse(out);
89
+ return Array.isArray(prs) && prs.length > 0;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
@@ -0,0 +1,64 @@
1
+ import { getConfig } from "../config.js";
2
+ import { buildIssueContext, gh, log } from "../utils.js";
3
+ import type { IssueContext } from "../utils.js";
4
+
5
+ interface GhIssue {
6
+ number: number;
7
+ title: string;
8
+ body: string;
9
+ labels: { name: string }[];
10
+ }
11
+
12
+ export async function fetchIssues(limit?: number): Promise<IssueContext[]> {
13
+ const cfg = getConfig();
14
+
15
+ log(`Scanning ${cfg.repo} for issues labeled "${cfg.triggerLabel}"...`);
16
+
17
+ let issues: GhIssue[];
18
+ try {
19
+ issues = await gh<GhIssue[]>([
20
+ "issue",
21
+ "list",
22
+ "--repo",
23
+ cfg.repo,
24
+ "--label",
25
+ cfg.triggerLabel,
26
+ "--state",
27
+ "open",
28
+ "--json",
29
+ "number,title,body,labels",
30
+ ]);
31
+ } catch (e) {
32
+ log(`Warning: could not fetch issues from ${cfg.repo}: ${e}`);
33
+ return [];
34
+ }
35
+
36
+ if (issues.length === 0) {
37
+ log("No issues found.");
38
+ return [];
39
+ }
40
+
41
+ log(`Found ${issues.length} issue(s).`);
42
+
43
+ const selected = limit != null ? issues.slice(0, limit) : issues;
44
+ return selected.map((issue) => buildIssueContext(issue, cfg.repo, cfg.scopePath));
45
+ }
46
+
47
+ export async function fetchIssue(issueNumber: number): Promise<IssueContext | undefined> {
48
+ const cfg = getConfig();
49
+
50
+ try {
51
+ const issue = await gh<GhIssue>([
52
+ "issue",
53
+ "view",
54
+ String(issueNumber),
55
+ "--repo",
56
+ cfg.repo,
57
+ "--json",
58
+ "number,title,body,labels",
59
+ ]);
60
+ return buildIssueContext(issue, cfg.repo, cfg.scopePath);
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }