@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.
- package/README.md +82 -72
- package/package.json +8 -7
- package/src/commands/auto-claude.ts +219 -0
- package/src/commands/doctor.ts +1 -34
- package/src/config/settings.ts +0 -10
- package/src/lib/auto-claude/config.test.ts +53 -0
- package/src/lib/auto-claude/config.ts +68 -0
- package/src/lib/auto-claude/index.ts +14 -0
- package/src/lib/auto-claude/pipeline.test.ts +14 -0
- package/src/lib/auto-claude/pipeline.ts +64 -0
- package/src/lib/auto-claude/prompt-templates/01-prompt-research.md +28 -0
- package/src/lib/auto-claude/prompt-templates/02-prompt-plan.md +28 -0
- package/src/lib/auto-claude/prompt-templates/03-prompt-plan-annotations.md +21 -0
- package/src/lib/auto-claude/prompt-templates/04-prompt-plan-implementation.md +33 -0
- package/src/lib/auto-claude/prompt-templates/05-prompt-implement.md +31 -0
- package/src/lib/auto-claude/prompt-templates/06-prompt-review.md +30 -0
- package/src/lib/auto-claude/prompt-templates/07-prompt-refresh.md +39 -0
- package/src/lib/auto-claude/prompt-templates/index.test.ts +145 -0
- package/src/lib/auto-claude/prompt-templates/index.ts +44 -0
- package/src/lib/auto-claude/steps/create-pr.ts +93 -0
- package/src/lib/auto-claude/steps/fetch-issues.ts +64 -0
- package/src/lib/auto-claude/steps/implement.ts +63 -0
- package/src/lib/auto-claude/steps/plan-annotations.ts +54 -0
- package/src/lib/auto-claude/steps/plan-implementation.ts +14 -0
- package/src/lib/auto-claude/steps/plan.ts +14 -0
- package/src/lib/auto-claude/steps/refresh.ts +114 -0
- package/src/lib/auto-claude/steps/remove-label.ts +22 -0
- package/src/lib/auto-claude/steps/research.ts +21 -0
- package/src/lib/auto-claude/steps/review.ts +14 -0
- package/src/lib/auto-claude/utils.test.ts +136 -0
- package/src/lib/auto-claude/utils.ts +334 -0
- package/src/commands/ralph/plan/add.ts +0 -69
- package/src/commands/ralph/plan/done.ts +0 -82
- package/src/commands/ralph/plan/list.test.ts +0 -48
- package/src/commands/ralph/plan/list.ts +0 -100
- package/src/commands/ralph/plan/remove.ts +0 -71
- package/src/commands/ralph/run.test.ts +0 -607
- package/src/commands/ralph/run.ts +0 -362
- package/src/commands/ralph/show.ts +0 -88
- package/src/lib/ralph/execution.ts +0 -292
- package/src/lib/ralph/formatter.ts +0 -240
- package/src/lib/ralph/index.ts +0 -4
- package/src/lib/ralph/state.ts +0 -201
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
|
|
5
|
+
import { getConfig } from "../config.js";
|
|
6
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
7
|
+
import {
|
|
8
|
+
buildTokens,
|
|
9
|
+
commitArtifacts,
|
|
10
|
+
fileExists,
|
|
11
|
+
git,
|
|
12
|
+
log,
|
|
13
|
+
logStep,
|
|
14
|
+
resolveTemplate,
|
|
15
|
+
runClaude,
|
|
16
|
+
} from "../utils.js";
|
|
17
|
+
import type { IssueContext } from "../utils.js";
|
|
18
|
+
|
|
19
|
+
export async function stepImplement(ctx: IssueContext): Promise<boolean> {
|
|
20
|
+
const completedPath = join(ctx.issueDir, ARTIFACTS.completedSummary);
|
|
21
|
+
const maxIterations = getConfig().maxImplementIterations;
|
|
22
|
+
|
|
23
|
+
if (fileExists(completedPath)) {
|
|
24
|
+
logStep(STEP_LABELS.implement, ctx, true);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
logStep(STEP_LABELS.implement, ctx);
|
|
29
|
+
|
|
30
|
+
await git(["checkout", ctx.branch]);
|
|
31
|
+
|
|
32
|
+
for (let i = 1; i <= maxIterations; i++) {
|
|
33
|
+
log(`Implementation iteration ${i}/${maxIterations}`);
|
|
34
|
+
|
|
35
|
+
const tokens = buildTokens(ctx);
|
|
36
|
+
const promptFile = resolveTemplate(TEMPLATES.implement, tokens, ctx.issueDir);
|
|
37
|
+
|
|
38
|
+
const result = await runClaude({
|
|
39
|
+
promptFile,
|
|
40
|
+
permissionMode: "acceptEdits",
|
|
41
|
+
maxTurns: getConfig().maxTurns,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (result.is_error) {
|
|
45
|
+
consola.error(`Implement iteration ${i} failed: ${result.result}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (fileExists(completedPath)) {
|
|
50
|
+
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
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
log(`Iteration ${i} finished but completed-summary.md not yet created — tasks remain`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
consola.error(`Implementation did not complete after ${maxIterations} iterations`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { renameSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
|
|
6
|
+
import { getConfig } from "../config.js";
|
|
7
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
8
|
+
import {
|
|
9
|
+
buildTokens,
|
|
10
|
+
commitArtifacts,
|
|
11
|
+
fileExists,
|
|
12
|
+
log,
|
|
13
|
+
logStep,
|
|
14
|
+
resolveTemplate,
|
|
15
|
+
runClaude,
|
|
16
|
+
} from "../utils.js";
|
|
17
|
+
import type { IssueContext } from "../utils.js";
|
|
18
|
+
|
|
19
|
+
export async function stepPlanAnnotations(ctx: IssueContext): Promise<boolean> {
|
|
20
|
+
const annotationsPath = join(ctx.issueDir, ARTIFACTS.planAnnotations);
|
|
21
|
+
const addressedPath = join(ctx.issueDir, ARTIFACTS.planAnnotationsAddressed);
|
|
22
|
+
|
|
23
|
+
if (!fileExists(annotationsPath)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (fileExists(addressedPath)) {
|
|
28
|
+
logStep(STEP_LABELS.planAnnotations, ctx, true);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logStep(STEP_LABELS.planAnnotations, ctx);
|
|
33
|
+
log("Found plan-annotations.md — addressing reviewer notes");
|
|
34
|
+
|
|
35
|
+
const tokens = buildTokens(ctx);
|
|
36
|
+
const promptFile = resolveTemplate(TEMPLATES.planAnnotations, tokens, ctx.issueDir);
|
|
37
|
+
|
|
38
|
+
const result = await runClaude({
|
|
39
|
+
promptFile,
|
|
40
|
+
permissionMode: "acceptEdits",
|
|
41
|
+
maxTurns: getConfig().maxTurns,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (result.is_error) {
|
|
45
|
+
consola.error(`Plan-Annotations step failed: ${result.result}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
renameSync(annotationsPath, addressedPath);
|
|
50
|
+
log("Annotations addressed — renamed to plan-annotations-addressed.md");
|
|
51
|
+
|
|
52
|
+
await commitArtifacts(ctx, `chore(auto-claude): plan annotations for ${ctx.repo}#${ctx.number}`);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
4
|
+
import { runStepWithArtifact } from "../utils.js";
|
|
5
|
+
import type { IssueContext } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
export async function stepPlanImplementation(ctx: IssueContext): Promise<boolean> {
|
|
8
|
+
return runStepWithArtifact({
|
|
9
|
+
stepName: STEP_LABELS.planImplementation,
|
|
10
|
+
ctx,
|
|
11
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.planImplementation),
|
|
12
|
+
templateName: TEMPLATES.planImplementation,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
4
|
+
import { runStepWithArtifact } from "../utils.js";
|
|
5
|
+
import type { IssueContext } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
export async function stepPlan(ctx: IssueContext): Promise<boolean> {
|
|
8
|
+
return runStepWithArtifact({
|
|
9
|
+
stepName: STEP_LABELS.plan,
|
|
10
|
+
ctx,
|
|
11
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.plan),
|
|
12
|
+
templateName: TEMPLATES.plan,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
|
|
6
|
+
import { getConfig } from "../config.js";
|
|
7
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
8
|
+
import {
|
|
9
|
+
buildTokens,
|
|
10
|
+
commitArtifacts,
|
|
11
|
+
execSafe,
|
|
12
|
+
fileExists,
|
|
13
|
+
git,
|
|
14
|
+
log,
|
|
15
|
+
logStep,
|
|
16
|
+
resolveTemplate,
|
|
17
|
+
runClaude,
|
|
18
|
+
} from "../utils.js";
|
|
19
|
+
import type { IssueContext } from "../utils.js";
|
|
20
|
+
|
|
21
|
+
export async function stepRefresh(ctx: IssueContext): Promise<boolean> {
|
|
22
|
+
logStep(STEP_LABELS.refresh, ctx);
|
|
23
|
+
const { mainBranch, remote } = getConfig();
|
|
24
|
+
|
|
25
|
+
const branchList = await git(["branch", "--list", ctx.branch]);
|
|
26
|
+
if (!branchList.includes(ctx.branch.split("/").pop()!)) {
|
|
27
|
+
try {
|
|
28
|
+
await git(["fetch", remote, ctx.branch]);
|
|
29
|
+
await git(["checkout", ctx.branch]);
|
|
30
|
+
} catch {
|
|
31
|
+
log(`Branch ${ctx.branch} does not exist locally or remotely.`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await git(["checkout", mainBranch]);
|
|
37
|
+
await git(["pull", remote, mainBranch]);
|
|
38
|
+
await git(["checkout", ctx.branch]);
|
|
39
|
+
|
|
40
|
+
if (await isBranchUpToDate(mainBranch)) {
|
|
41
|
+
log(`Branch is already up-to-date with ${mainBranch}.`);
|
|
42
|
+
} else {
|
|
43
|
+
await rebaseOrMerge(mainBranch);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tokens = buildTokens(ctx);
|
|
47
|
+
const promptFile = resolveTemplate(TEMPLATES.refresh, tokens, ctx.issueDir);
|
|
48
|
+
const result = await runClaude({
|
|
49
|
+
promptFile,
|
|
50
|
+
permissionMode: "acceptEdits",
|
|
51
|
+
maxTurns: getConfig().maxTurns,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (result.is_error) {
|
|
55
|
+
consola.error(`Refresh step failed: ${result.result}`);
|
|
56
|
+
await git(["checkout", mainBranch]).catch(() => {});
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await commitArtifacts(ctx, `chore(auto-claude): refresh for ${ctx.repo}#${ctx.number}`);
|
|
61
|
+
await invalidateStaleArtifacts(ctx);
|
|
62
|
+
|
|
63
|
+
await git(["push", "--force-with-lease", "-u", remote, ctx.branch]);
|
|
64
|
+
await git(["checkout", mainBranch]).catch(() => {});
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function isBranchUpToDate(mainBranch: string): Promise<boolean> {
|
|
70
|
+
try {
|
|
71
|
+
await git(["merge-base", "--is-ancestor", mainBranch, "HEAD"]);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function rebaseOrMerge(mainBranch: string): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
await git(["rebase", mainBranch]);
|
|
81
|
+
} catch {
|
|
82
|
+
await git(["rebase", "--abort"]).catch(() => {});
|
|
83
|
+
try {
|
|
84
|
+
await git(["merge", mainBranch, "--no-edit"]);
|
|
85
|
+
} catch {
|
|
86
|
+
const conflicts = await execSafe("git", ["diff", "--name-only", "--diff-filter=U"]);
|
|
87
|
+
if (conflicts.stdout.length > 0) {
|
|
88
|
+
await git(["merge", "--abort"]);
|
|
89
|
+
throw new Error(`Merge conflicts detected in: ${conflicts.stdout}`);
|
|
90
|
+
}
|
|
91
|
+
await git(["add", "."]);
|
|
92
|
+
await git(["commit", "--no-edit"]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function invalidateStaleArtifacts(ctx: IssueContext): Promise<void> {
|
|
98
|
+
const paths = [
|
|
99
|
+
join(ctx.issueDir, ARTIFACTS.review),
|
|
100
|
+
join(ctx.issueDir, ARTIFACTS.completedSummary),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const removed = paths.filter((p) => {
|
|
104
|
+
if (fileExists(p)) {
|
|
105
|
+
rmSync(p);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (removed.length > 0) {
|
|
112
|
+
await commitArtifacts(ctx, "chore(auto-claude): invalidate stale artifacts after refresh");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
import { STEP_LABELS } from "../prompt-templates/index.js";
|
|
3
|
+
import { ghRaw, log, logStep } from "../utils.js";
|
|
4
|
+
import type { IssueContext } from "../utils.js";
|
|
5
|
+
|
|
6
|
+
export async function stepRemoveLabel(ctx: IssueContext): Promise<boolean> {
|
|
7
|
+
logStep(STEP_LABELS.removeLabel, ctx);
|
|
8
|
+
|
|
9
|
+
const cfg = getConfig();
|
|
10
|
+
await ghRaw([
|
|
11
|
+
"issue",
|
|
12
|
+
"edit",
|
|
13
|
+
String(ctx.number),
|
|
14
|
+
"--repo",
|
|
15
|
+
ctx.repo,
|
|
16
|
+
"--remove-label",
|
|
17
|
+
cfg.triggerLabel,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
log(`Removed "${cfg.triggerLabel}" label from ${ctx.repo}#${ctx.number}`);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
4
|
+
import { ensureBranch, fileExists, readFile, runStepWithArtifact } from "../utils.js";
|
|
5
|
+
import type { IssueContext } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
function isValidResearch(path: string): boolean {
|
|
8
|
+
return fileExists(path) && readFile(path).length > 200;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function stepResearch(ctx: IssueContext): Promise<boolean> {
|
|
12
|
+
await ensureBranch(ctx.branch);
|
|
13
|
+
|
|
14
|
+
return runStepWithArtifact({
|
|
15
|
+
stepName: STEP_LABELS.research,
|
|
16
|
+
ctx,
|
|
17
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.research),
|
|
18
|
+
templateName: TEMPLATES.research,
|
|
19
|
+
artifactValidator: isValidResearch,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { ARTIFACTS, STEP_LABELS, TEMPLATES } from "../prompt-templates/index.js";
|
|
4
|
+
import { runStepWithArtifact } from "../utils.js";
|
|
5
|
+
import type { IssueContext } from "../utils.js";
|
|
6
|
+
|
|
7
|
+
export async function stepReview(ctx: IssueContext): Promise<boolean> {
|
|
8
|
+
return runStepWithArtifact({
|
|
9
|
+
stepName: STEP_LABELS.review,
|
|
10
|
+
ctx,
|
|
11
|
+
artifactPath: join(ctx.issueDir, ARTIFACTS.review),
|
|
12
|
+
templateName: TEMPLATES.review,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { initConfig } from "./config";
|
|
8
|
+
import {
|
|
9
|
+
buildContextFromArtifacts,
|
|
10
|
+
buildIssueContext,
|
|
11
|
+
buildTokens,
|
|
12
|
+
resolveTemplate,
|
|
13
|
+
} from "./utils";
|
|
14
|
+
|
|
15
|
+
// Initialize config once for tests that need getConfig()
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("buildIssueContext", () => {
|
|
21
|
+
it("should build context with correct fields", () => {
|
|
22
|
+
const ctx = buildIssueContext(
|
|
23
|
+
{ number: 42, title: "Fix the bug", body: "Something is broken" },
|
|
24
|
+
"owner/repo",
|
|
25
|
+
"src/",
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(ctx.number).toBe(42);
|
|
29
|
+
expect(ctx.title).toBe("Fix the bug");
|
|
30
|
+
expect(ctx.body).toBe("Something is broken");
|
|
31
|
+
expect(ctx.repo).toBe("owner/repo");
|
|
32
|
+
expect(ctx.scopePath).toBe("src/");
|
|
33
|
+
expect(ctx.issueDirRel).toBe(".auto-claude/issue-42");
|
|
34
|
+
expect(ctx.issueDir).toContain(".auto-claude/issue-42");
|
|
35
|
+
expect(ctx.branch).toBe("auto-claude/issue-42");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should derive branch name from issue number", () => {
|
|
39
|
+
const ctx = buildIssueContext({ number: 7, title: "t", body: "" }, "r", ".");
|
|
40
|
+
expect(ctx.branch).toBe("auto-claude/issue-7");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("buildTokens", () => {
|
|
45
|
+
it("should produce expected token keys", () => {
|
|
46
|
+
const ctx = buildIssueContext({ number: 1, title: "t", body: "" }, "test/repo", "lib/");
|
|
47
|
+
const tokens = buildTokens(ctx);
|
|
48
|
+
|
|
49
|
+
expect(tokens.SCOPE_PATH).toBe("lib/");
|
|
50
|
+
expect(tokens.ISSUE_DIR).toBe(".auto-claude/issue-1");
|
|
51
|
+
expect(tokens.MAIN_BRANCH).toBe("main");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("resolveTemplate", () => {
|
|
56
|
+
let tmpDir: string;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
tmpDir = mkdtempSync(join(tmpdir(), "auto-claude-test-"));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should replace token placeholders and write resolved file", () => {
|
|
67
|
+
// Use an actual template from the templates dir
|
|
68
|
+
const tokens = { SCOPE_PATH: "src/", ISSUE_DIR: ".auto-claude/issue-99", MAIN_BRANCH: "main" };
|
|
69
|
+
const issueDir = join(tmpDir, "issue-99");
|
|
70
|
+
mkdirSync(issueDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
const result = resolveTemplate("01-prompt-research.md", tokens, issueDir);
|
|
73
|
+
|
|
74
|
+
// Should return a relative path
|
|
75
|
+
expect(result).toContain("issue-99");
|
|
76
|
+
expect(result).toContain("01-prompt-research.md");
|
|
77
|
+
|
|
78
|
+
// Resolved file should exist and have tokens replaced
|
|
79
|
+
const content = readFileSync(join(issueDir, "01-prompt-research.md"), "utf-8");
|
|
80
|
+
expect(content).toContain("src/");
|
|
81
|
+
expect(content).toContain(".auto-claude/issue-99");
|
|
82
|
+
expect(content).not.toContain("{{SCOPE_PATH}}");
|
|
83
|
+
expect(content).not.toContain("{{ISSUE_DIR}}");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("buildContextFromArtifacts", () => {
|
|
88
|
+
let tmpDir: string;
|
|
89
|
+
let originalCwd: string;
|
|
90
|
+
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
originalCwd = process.cwd();
|
|
93
|
+
tmpDir = mkdtempSync(join(tmpdir(), "auto-claude-artifacts-"));
|
|
94
|
+
process.chdir(tmpDir);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterAll(() => {
|
|
98
|
+
process.chdir(originalCwd);
|
|
99
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should throw when no artifacts exist", () => {
|
|
103
|
+
expect(() => buildContextFromArtifacts(999)).toThrow("No artifacts found");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should parse title and body from initial-ramblings.md", async () => {
|
|
107
|
+
// Re-init config in the temp dir context
|
|
108
|
+
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
109
|
+
|
|
110
|
+
const issueDir = join(tmpDir, ".auto-claude/issue-77");
|
|
111
|
+
mkdirSync(issueDir, { recursive: true });
|
|
112
|
+
writeFileSync(
|
|
113
|
+
join(issueDir, "initial-ramblings.md"),
|
|
114
|
+
"# My Great Feature\n\n> test/repo#77\n\nThis is the body of the issue.\nWith multiple lines.",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const ctx = buildContextFromArtifacts(77);
|
|
118
|
+
|
|
119
|
+
expect(ctx.number).toBe(77);
|
|
120
|
+
expect(ctx.title).toBe("My Great Feature");
|
|
121
|
+
expect(ctx.body).toContain("This is the body of the issue.");
|
|
122
|
+
expect(ctx.repo).toBe("test/repo");
|
|
123
|
+
expect(ctx.branch).toBe("auto-claude/issue-77");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should fallback title when heading is missing", async () => {
|
|
127
|
+
await initConfig({ repo: "test/repo", mainBranch: "main" });
|
|
128
|
+
|
|
129
|
+
const issueDir = join(tmpDir, ".auto-claude/issue-88");
|
|
130
|
+
mkdirSync(issueDir, { recursive: true });
|
|
131
|
+
writeFileSync(join(issueDir, "initial-ramblings.md"), "No heading here\n\njust text");
|
|
132
|
+
|
|
133
|
+
const ctx = buildContextFromArtifacts(88);
|
|
134
|
+
expect(ctx.title).toBe("Issue #88");
|
|
135
|
+
});
|
|
136
|
+
});
|