@jwillert/forgeflow 0.1.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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +119 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.js +198 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/lock.d.ts +4 -0
- package/dist/cli/lock.js +55 -0
- package/dist/cli/lock.js.map +1 -0
- package/dist/cli/templates/Dockerfile +38 -0
- package/dist/cli/templates/build-image.sh +11 -0
- package/dist/core/capabilities.d.ts +75 -0
- package/dist/core/capabilities.js +2 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/config.d.ts +38 -0
- package/dist/core/config.js +25 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/engine.d.ts +44 -0
- package/dist/core/engine.js +225 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/state.d.ts +42 -0
- package/dist/core/state.js +8 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/workflow.d.ts +72 -0
- package/dist/core/workflow.js +38 -0
- package/dist/core/workflow.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/process/index.d.ts +14 -0
- package/dist/process/index.js +46 -0
- package/dist/process/index.js.map +1 -0
- package/dist/providers/github/index.d.ts +81 -0
- package/dist/providers/github/index.js +271 -0
- package/dist/providers/github/index.js.map +1 -0
- package/dist/providers/gitlab/index.d.ts +79 -0
- package/dist/providers/gitlab/index.js +263 -0
- package/dist/providers/gitlab/index.js.map +1 -0
- package/dist/state/sqlite/index.d.ts +2 -0
- package/dist/state/sqlite/index.js +138 -0
- package/dist/state/sqlite/index.js.map +1 -0
- package/dist/workflows/implement.d.ts +14 -0
- package/dist/workflows/implement.js +154 -0
- package/dist/workflows/implement.js.map +1 -0
- package/dist/workflows/index.d.ts +9 -0
- package/dist/workflows/index.js +5 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/prompts/implement.md +43 -0
- package/dist/workflows/prompts/review/extraction.md +21 -0
- package/dist/workflows/prompts/review/prompt.md +49 -0
- package/dist/workflows/prompts/update-branch/extraction.md +13 -0
- package/dist/workflows/prompts/update-branch/prompt.md +41 -0
- package/dist/workflows/review.d.ts +14 -0
- package/dist/workflows/review.js +134 -0
- package/dist/workflows/review.js.map +1 -0
- package/dist/workflows/shared/git.d.ts +17 -0
- package/dist/workflows/shared/git.js +28 -0
- package/dist/workflows/shared/git.js.map +1 -0
- package/dist/workflows/shared/lifecycle.d.ts +21 -0
- package/dist/workflows/shared/lifecycle.js +29 -0
- package/dist/workflows/shared/lifecycle.js.map +1 -0
- package/dist/workflows/shared/review-output.d.ts +44 -0
- package/dist/workflows/shared/review-output.js +100 -0
- package/dist/workflows/shared/review-output.js.map +1 -0
- package/dist/workflows/shared/sandcastle.d.ts +26 -0
- package/dist/workflows/shared/sandcastle.js +112 -0
- package/dist/workflows/shared/sandcastle.js.map +1 -0
- package/dist/workflows/shared/schema.d.ts +4 -0
- package/dist/workflows/shared/schema.js +17 -0
- package/dist/workflows/shared/schema.js.map +1 -0
- package/dist/workflows/update-branch.d.ts +12 -0
- package/dist/workflows/update-branch.js +148 -0
- package/dist/workflows/update-branch.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CodeReviewHost, WorkReader } from "../../core/capabilities.js";
|
|
2
|
+
import type { WorkTargetRef } from "../../core/types.js";
|
|
3
|
+
export interface InlineComment {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly line: number;
|
|
6
|
+
readonly body: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ThreadReply {
|
|
9
|
+
readonly threadId: string;
|
|
10
|
+
readonly body: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ReviewOutput {
|
|
13
|
+
readonly summary: string;
|
|
14
|
+
readonly inlineComments: InlineComment[];
|
|
15
|
+
readonly replies: ThreadReply[];
|
|
16
|
+
}
|
|
17
|
+
export interface UpdateBranchOutput {
|
|
18
|
+
readonly comment: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function validateReviewOutput(value: unknown): ReviewOutput;
|
|
21
|
+
export declare function validateUpdateBranchOutput(value: unknown): UpdateBranchOutput;
|
|
22
|
+
export declare function parseDiffLines(diff: string): Map<string, Set<number>>;
|
|
23
|
+
export declare const filterInlineComments: (comments: readonly InlineComment[], diffLines: Map<string, Set<number>>) => InlineComment[];
|
|
24
|
+
export declare const filterReplies: (replies: readonly ThreadReply[], validReplyIds: Set<string>) => ThreadReply[];
|
|
25
|
+
/**
|
|
26
|
+
* Builds the review/update-branch prompt context for a change request (GitHub PR
|
|
27
|
+
* or GitLab MR) using only forge-agnostic capabilities — no provider-specific CLI.
|
|
28
|
+
*/
|
|
29
|
+
export declare function fetchChangeRequestContext(input: {
|
|
30
|
+
target: WorkTargetRef;
|
|
31
|
+
workReader: WorkReader;
|
|
32
|
+
codeReviewHost: CodeReviewHost;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
prTitle: string;
|
|
36
|
+
issueNumber: string;
|
|
37
|
+
issueTitle: string;
|
|
38
|
+
linkedIssue: string;
|
|
39
|
+
targetBranch: string;
|
|
40
|
+
diff: string;
|
|
41
|
+
prCommentsJson: string;
|
|
42
|
+
diffLines: Map<string, Set<number>>;
|
|
43
|
+
validReplyIds: Set<string>;
|
|
44
|
+
}>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { git } from "./git.js";
|
|
2
|
+
import { asArray, asOptionalString, asRecord, asString } from "./schema.js";
|
|
3
|
+
const parseLine = (value, record) => {
|
|
4
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0)
|
|
5
|
+
return value;
|
|
6
|
+
const firstLine = asOptionalString(record.lineRange)?.match(/\d+/)?.[0];
|
|
7
|
+
if (firstLine)
|
|
8
|
+
return Number(firstLine);
|
|
9
|
+
throw new Error("line must be a positive integer or lineRange must start with a line number");
|
|
10
|
+
};
|
|
11
|
+
const parseInlineComment = (value) => {
|
|
12
|
+
const record = asRecord(value, "inline comment");
|
|
13
|
+
return {
|
|
14
|
+
path: asString(record.path ?? record.file, "inline comment path"),
|
|
15
|
+
line: parseLine(record.line, record),
|
|
16
|
+
body: asString(record.body ?? record.comment, "inline comment body"),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const parseReply = (value) => {
|
|
20
|
+
const record = asRecord(value, "reply");
|
|
21
|
+
return { threadId: asString(record.threadId ?? record.commentId, "reply threadId"), body: asString(record.body ?? record.comment, "reply body") };
|
|
22
|
+
};
|
|
23
|
+
export function validateReviewOutput(value) {
|
|
24
|
+
const record = asRecord(value, "review output");
|
|
25
|
+
return {
|
|
26
|
+
summary: asString(record.summary, "summary"),
|
|
27
|
+
inlineComments: asArray(record.inlineComments ?? [], "inlineComments").map(parseInlineComment),
|
|
28
|
+
replies: asArray(record.replies ?? [], "replies").map(parseReply),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function validateUpdateBranchOutput(value) {
|
|
32
|
+
const record = asRecord(value, "update-branch output");
|
|
33
|
+
return { comment: asString(record.comment, "comment") };
|
|
34
|
+
}
|
|
35
|
+
export function parseDiffLines(diff) {
|
|
36
|
+
const files = new Map();
|
|
37
|
+
let currentFile;
|
|
38
|
+
let newLine = 0;
|
|
39
|
+
for (const line of diff.split("\n")) {
|
|
40
|
+
if (line.startsWith("+++ b/")) {
|
|
41
|
+
currentFile = line.slice("+++ b/".length);
|
|
42
|
+
if (!files.has(currentFile))
|
|
43
|
+
files.set(currentFile, new Set());
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!currentFile)
|
|
47
|
+
continue;
|
|
48
|
+
const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
49
|
+
if (hunk) {
|
|
50
|
+
newLine = Number(hunk[1]);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
54
|
+
files.get(currentFile)?.add(newLine);
|
|
55
|
+
newLine++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (line.startsWith(" ") || line === "") {
|
|
59
|
+
files.get(currentFile)?.add(newLine);
|
|
60
|
+
newLine++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
export const filterInlineComments = (comments, diffLines) => comments.filter(comment => diffLines.get(comment.path)?.has(comment.line) ?? false);
|
|
66
|
+
export const filterReplies = (replies, validReplyIds) => replies.filter(reply => validReplyIds.has(reply.threadId));
|
|
67
|
+
/**
|
|
68
|
+
* Builds the review/update-branch prompt context for a change request (GitHub PR
|
|
69
|
+
* or GitLab MR) using only forge-agnostic capabilities — no provider-specific CLI.
|
|
70
|
+
*/
|
|
71
|
+
export async function fetchChangeRequestContext(input) {
|
|
72
|
+
const { target, workReader, codeReviewHost, cwd } = input;
|
|
73
|
+
const details = await codeReviewHost.getChangeRequestDetails(target);
|
|
74
|
+
const issueNumber = details.linkedIssueId ?? "";
|
|
75
|
+
const issueTarget = { provider: target.provider, repo: target.repo, project: target.project, kind: "issue", id: issueNumber };
|
|
76
|
+
const issueSnapshot = issueNumber ? await workReader.getTarget(issueTarget).catch(() => undefined) : undefined;
|
|
77
|
+
const issueComments = issueNumber ? await codeReviewHost.listGeneralComments(issueTarget).catch(() => []) : [];
|
|
78
|
+
const linkedIssue = issueSnapshot
|
|
79
|
+
? [`# ${issueSnapshot.title}\n\n${issueSnapshot.body ?? ""}`, ...issueComments.map(c => `---\n${c.author ?? "unknown"}: ${c.body}`)].join("\n\n")
|
|
80
|
+
: "(no linked issue found)";
|
|
81
|
+
const generalComments = await codeReviewHost.listGeneralComments(target);
|
|
82
|
+
const reviewThreads = await codeReviewHost.listReviewThreads(target);
|
|
83
|
+
const prComments = {
|
|
84
|
+
comments: generalComments.map(c => ({ author: c.author ?? "unknown", body: c.body, createdAt: c.createdAt })),
|
|
85
|
+
review_threads: reviewThreads.map(t => ({ id: t.id, path: t.path, line: t.line, comments: t.comments.map(c => ({ author: c.author ?? "unknown", body: c.body })) })),
|
|
86
|
+
};
|
|
87
|
+
const diff = await git(["diff", `origin/${details.targetBranch}...HEAD`], { cwd }).catch(() => git(["diff", `origin/${details.targetBranch}..HEAD`], { cwd }));
|
|
88
|
+
return {
|
|
89
|
+
prTitle: details.title,
|
|
90
|
+
issueNumber,
|
|
91
|
+
issueTitle: issueSnapshot?.title ?? "",
|
|
92
|
+
linkedIssue,
|
|
93
|
+
targetBranch: details.targetBranch,
|
|
94
|
+
diff,
|
|
95
|
+
prCommentsJson: JSON.stringify(prComments, null, 2),
|
|
96
|
+
diffLines: parseDiffLines(diff),
|
|
97
|
+
validReplyIds: new Set(reviewThreads.map(t => t.id)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=review-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-output.js","sourceRoot":"","sources":["../../../src/workflows/shared/review-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as sandcastle from "@ai-hero/sandcastle";
|
|
2
|
+
export type AgentThinking = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
3
|
+
export interface SandboxDefaults {
|
|
4
|
+
readonly sandboxImage?: string;
|
|
5
|
+
readonly model?: string;
|
|
6
|
+
readonly thinking?: AgentThinking;
|
|
7
|
+
}
|
|
8
|
+
export declare function piAgent(defaults?: SandboxDefaults): ReturnType<typeof sandcastle.pi>;
|
|
9
|
+
export declare function podmanSandboxProvider(defaults?: SandboxDefaults): sandcastle.SandboxProvider;
|
|
10
|
+
export declare function createPodmanSandbox(input: SandboxDefaults & {
|
|
11
|
+
branch: string;
|
|
12
|
+
preflightCommand?: string;
|
|
13
|
+
preflightTimeoutMs?: number;
|
|
14
|
+
}): Promise<sandcastle.Sandbox>;
|
|
15
|
+
export declare function runSandboxWithExtraction<T>(input: SandboxDefaults & {
|
|
16
|
+
sandbox: Awaited<ReturnType<typeof createPodmanSandbox>>;
|
|
17
|
+
name: string;
|
|
18
|
+
promptFile: string;
|
|
19
|
+
promptArgs: Record<string, string>;
|
|
20
|
+
extractionPrompt: string;
|
|
21
|
+
validate: (value: unknown) => T;
|
|
22
|
+
idleTimeoutSeconds?: number;
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
}): Promise<Awaited<ReturnType<Awaited<ReturnType<typeof createPodmanSandbox>>["run"]>> & {
|
|
25
|
+
output: T;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as sandcastle from "@ai-hero/sandcastle";
|
|
2
|
+
import { podman } from "@ai-hero/sandcastle/sandboxes/podman";
|
|
3
|
+
export function piAgent(defaults = {}) {
|
|
4
|
+
const model = defaults.model ?? process.env.PI_MODEL ?? "openai-codex/gpt-5.5";
|
|
5
|
+
const thinking = defaults.thinking ?? process.env.PI_THINKING ?? "low";
|
|
6
|
+
return sandcastle.pi(model, { thinking });
|
|
7
|
+
}
|
|
8
|
+
export function podmanSandboxProvider(defaults = {}) {
|
|
9
|
+
return podman({
|
|
10
|
+
imageName: defaults.sandboxImage ?? process.env.FORGEFLOW_SANDBOX_IMAGE ?? "forgeflow-agent",
|
|
11
|
+
mounts: [
|
|
12
|
+
{
|
|
13
|
+
hostPath: "~/.pi/agent",
|
|
14
|
+
sandboxPath: "/home/agent/.pi/agent",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export async function createPodmanSandbox(input) {
|
|
20
|
+
return await sandcastle.createSandbox({
|
|
21
|
+
sandbox: podmanSandboxProvider(input),
|
|
22
|
+
branch: input.branch,
|
|
23
|
+
hooks: input.preflightCommand
|
|
24
|
+
? {
|
|
25
|
+
sandbox: {
|
|
26
|
+
onSandboxReady: [
|
|
27
|
+
{
|
|
28
|
+
command: input.preflightCommand,
|
|
29
|
+
timeoutMs: input.preflightTimeoutMs ?? 600_000,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: undefined,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function stripCodeFence(text) {
|
|
38
|
+
const fenced = text.trim().match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
|
|
39
|
+
return fenced ? fenced[1] : text;
|
|
40
|
+
}
|
|
41
|
+
function extractBalancedJson(text) {
|
|
42
|
+
const start = text.indexOf("{");
|
|
43
|
+
if (start === -1)
|
|
44
|
+
return undefined;
|
|
45
|
+
let depth = 0;
|
|
46
|
+
for (let i = start; i < text.length; i++) {
|
|
47
|
+
if (text[i] === "{")
|
|
48
|
+
depth++;
|
|
49
|
+
else if (text[i] === "}") {
|
|
50
|
+
depth--;
|
|
51
|
+
if (depth === 0)
|
|
52
|
+
return text.slice(start, i + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function parseStructuredJson(raw) {
|
|
58
|
+
const unfenced = stripCodeFence(raw);
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(unfenced);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const balanced = extractBalancedJson(unfenced);
|
|
64
|
+
if (balanced === undefined)
|
|
65
|
+
throw error;
|
|
66
|
+
return JSON.parse(balanced);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function runSandboxWithExtraction(input) {
|
|
70
|
+
const produce = await input.sandbox.run({
|
|
71
|
+
name: input.name,
|
|
72
|
+
agent: piAgent(input),
|
|
73
|
+
promptFile: input.promptFile,
|
|
74
|
+
promptArgs: input.promptArgs,
|
|
75
|
+
idleTimeoutSeconds: input.idleTimeoutSeconds ?? 900,
|
|
76
|
+
});
|
|
77
|
+
const sessionId = produce.iterations.at(-1)?.sessionId;
|
|
78
|
+
if (!sessionId)
|
|
79
|
+
throw new Error("Cannot extract structured output because the produce run had no session id.");
|
|
80
|
+
const maxRetries = input.maxRetries ?? 2;
|
|
81
|
+
let resumeFrom = sessionId;
|
|
82
|
+
let prompt = input.extractionPrompt;
|
|
83
|
+
for (let attempt = 0;; attempt++) {
|
|
84
|
+
const extraction = await input.sandbox.run({
|
|
85
|
+
name: attempt === 0 ? `${input.name} (extract)` : `${input.name} (extract retry ${attempt})`,
|
|
86
|
+
agent: piAgent(input),
|
|
87
|
+
prompt,
|
|
88
|
+
resumeSession: resumeFrom,
|
|
89
|
+
idleTimeoutSeconds: 300,
|
|
90
|
+
});
|
|
91
|
+
const match = extraction.stdout.match(/<output>\s*([\s\S]*?)\s*<\/output>/);
|
|
92
|
+
const outcome = match
|
|
93
|
+
? (() => { try {
|
|
94
|
+
return { ok: true, value: input.validate(parseStructuredJson(match[1])) };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return { ok: false, error };
|
|
98
|
+
} })()
|
|
99
|
+
: { ok: false, error: new Error("no <output> block found") };
|
|
100
|
+
if (outcome.ok)
|
|
101
|
+
return { ...produce, output: outcome.value };
|
|
102
|
+
const extractionSessionId = extraction.iterations.at(-1)?.sessionId;
|
|
103
|
+
if (attempt >= maxRetries || !extractionSessionId) {
|
|
104
|
+
const reason = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
105
|
+
throw new Error(`Extraction did not produce a valid <output> block after ${attempt + 1} attempt(s): ${reason}\n\n${extraction.stdout}`);
|
|
106
|
+
}
|
|
107
|
+
resumeFrom = extractionSessionId;
|
|
108
|
+
const reason = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
109
|
+
prompt = `Your last response's <output> block could not be parsed: ${reason}\n\nRe-emit ONLY a corrected <output> block containing valid JSON matching the requested schema. Do not wrap it in a markdown code fence and do not include any other text.`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=sandcastle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandcastle.js","sourceRoot":"","sources":["../../../src/workflows/shared/sandcastle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const asRecord: (value: unknown, label: string) => Record<string, unknown>;
|
|
2
|
+
export declare const asString: (value: unknown, label: string) => string;
|
|
3
|
+
export declare const asOptionalString: (value: unknown) => string | undefined;
|
|
4
|
+
export declare const asArray: (value: unknown, label: string) => unknown[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const asRecord = (value, label) => {
|
|
2
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
3
|
+
throw new Error(`${label} must be an object`);
|
|
4
|
+
return value;
|
|
5
|
+
};
|
|
6
|
+
export const asString = (value, label) => {
|
|
7
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
8
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
export const asOptionalString = (value) => typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
12
|
+
export const asArray = (value, label) => {
|
|
13
|
+
if (!Array.isArray(value))
|
|
14
|
+
throw new Error(`${label} must be an array`);
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/workflows/shared/schema.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,KAAa,EAA2B,EAAE;IACjF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAA;IACtH,OAAO,KAAgC,CAAA;AACzC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,KAAa,EAAU,EAAE;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,6BAA6B,CAAC,CAAA;IAClH,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAsB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAEhJ,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,KAAa,EAAa,EAAE;IAClE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,mBAAmB,CAAC,CAAA;IACvE,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Workflow } from "../core/workflow.js";
|
|
2
|
+
import { type SandboxDefaults } from "./shared/sandcastle.js";
|
|
3
|
+
export interface UpdateBranchWorkflowOptions extends SandboxDefaults {
|
|
4
|
+
readonly id?: string;
|
|
5
|
+
readonly triggerLabel?: string;
|
|
6
|
+
readonly blockedLabel?: string;
|
|
7
|
+
readonly inProgressLabel?: string;
|
|
8
|
+
readonly promptFile?: string;
|
|
9
|
+
readonly extractionPrompt?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function createUpdateBranchWorkflow(options?: UpdateBranchWorkflowOptions): Workflow;
|
|
12
|
+
export declare const updateBranch: Workflow;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { dirname, join } from "node:path";
|
|
54
|
+
import { fileURLToPath } from "node:url";
|
|
55
|
+
import { readFileSync } from "node:fs";
|
|
56
|
+
import { defineWorkflow, Match, isChangeRequestKind, labelAdded } from "../core/workflow.js";
|
|
57
|
+
import { pushBranch } from "./shared/git.js";
|
|
58
|
+
import { createPodmanSandbox, piAgent, runSandboxWithExtraction } from "./shared/sandcastle.js";
|
|
59
|
+
import { validateUpdateBranchOutput } from "./shared/review-output.js";
|
|
60
|
+
import { runWithAgentLifecycle } from "./shared/lifecycle.js";
|
|
61
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
62
|
+
const defaultPromptFile = join(here, "prompts/update-branch/prompt.md");
|
|
63
|
+
const defaultExtractionPrompt = readFileSync(join(here, "prompts/update-branch/extraction.md"), "utf8");
|
|
64
|
+
export function createUpdateBranchWorkflow(options = {}) {
|
|
65
|
+
const triggerLabel = options.triggerLabel ?? "agent:update-branch";
|
|
66
|
+
const promptFile = options.promptFile ?? defaultPromptFile;
|
|
67
|
+
const extractionPrompt = options.extractionPrompt ?? defaultExtractionPrompt;
|
|
68
|
+
return defineWorkflow(options.id ?? "update-branch", {
|
|
69
|
+
match: ({ event }) => {
|
|
70
|
+
if (!labelAdded(event, triggerLabel))
|
|
71
|
+
return Match.ignore();
|
|
72
|
+
if (!isChangeRequestKind(event.workTarget.kind))
|
|
73
|
+
return Match.ignore();
|
|
74
|
+
return Match.accept();
|
|
75
|
+
},
|
|
76
|
+
run: async ({ command, workTracker, codeReviewHost }) => runWithAgentLifecycle({ workTracker, target: command.workTarget, labels: { trigger: triggerLabel, blocked: options.blockedLabel, inProgress: options.inProgressLabel } }, async () => {
|
|
77
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
78
|
+
try {
|
|
79
|
+
const target = command.workTarget;
|
|
80
|
+
const details = await codeReviewHost.getChangeRequestDetails(target);
|
|
81
|
+
if (details.isCrossRepository)
|
|
82
|
+
throw new Error("Refused to update a cross-repository change request.");
|
|
83
|
+
const sandbox = __addDisposableResource(env_1, await createPodmanSandbox({ ...options, branch: details.sourceBranch }), true);
|
|
84
|
+
await sandbox.exec(`git fetch origin ${details.targetBranch}`);
|
|
85
|
+
const mergeBase = (await sandbox.exec(`git merge-base HEAD origin/${details.targetBranch}`)).stdout.trim();
|
|
86
|
+
const baseSha = (await sandbox.exec(`git rev-parse origin/${details.targetBranch}`)).stdout.trim();
|
|
87
|
+
let comment = "";
|
|
88
|
+
let shouldPush = false;
|
|
89
|
+
if (mergeBase === baseSha) {
|
|
90
|
+
comment = `\`${triggerLabel}\`: branch is already up to date with \`origin/${details.targetBranch}\`. No merge needed.`;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const merge = await sandbox.exec(`git merge origin/${details.targetBranch} --no-edit`);
|
|
94
|
+
if (merge.exitCode === 0) {
|
|
95
|
+
comment = `\`${triggerLabel}\`: merged \`origin/${details.targetBranch}\` (\`${baseSha.slice(0, 7)}\`) into \`${details.sourceBranch}\` cleanly — no conflicts.`;
|
|
96
|
+
shouldPush = true;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const conflicts = (await sandbox.exec("git diff --name-only --diff-filter=U")).stdout.trim();
|
|
100
|
+
if (!conflicts)
|
|
101
|
+
throw new Error("git merge failed but no conflicts were reported.");
|
|
102
|
+
const result = await runSandboxWithExtraction({
|
|
103
|
+
...options,
|
|
104
|
+
sandbox,
|
|
105
|
+
name: `update-branch-${target.kind}-${target.id}`,
|
|
106
|
+
promptFile,
|
|
107
|
+
extractionPrompt,
|
|
108
|
+
validate: validateUpdateBranchOutput,
|
|
109
|
+
promptArgs: {
|
|
110
|
+
PR_NUMBER: target.id,
|
|
111
|
+
BRANCH: details.sourceBranch,
|
|
112
|
+
BASE_REF: details.targetBranch,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
const unresolved = (await sandbox.exec("git diff --name-only --diff-filter=U")).stdout.trim();
|
|
116
|
+
if (unresolved)
|
|
117
|
+
throw new Error(`Agent left unresolved conflicts in:\n${unresolved}`);
|
|
118
|
+
if (result.commits.length === 0) {
|
|
119
|
+
await sandbox.run({
|
|
120
|
+
name: `commit-update-branch-${target.id}`,
|
|
121
|
+
agent: piAgent(options),
|
|
122
|
+
prompt: `Stage all changes and create one conventional commit for merging origin/${details.targetBranch} into ${details.sourceBranch}. Do not make further code changes.`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
comment = result.output.comment;
|
|
126
|
+
shouldPush = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (shouldPush)
|
|
130
|
+
await pushBranch({ branch: details.sourceBranch, forceWithLease: `refs/heads/${details.sourceBranch}:${details.sourceSha}` });
|
|
131
|
+
if (comment)
|
|
132
|
+
await workTracker.addComment(target, comment);
|
|
133
|
+
return { summary: comment || "Update branch completed." };
|
|
134
|
+
}
|
|
135
|
+
catch (e_1) {
|
|
136
|
+
env_1.error = e_1;
|
|
137
|
+
env_1.hasError = true;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
const result_1 = __disposeResources(env_1);
|
|
141
|
+
if (result_1)
|
|
142
|
+
await result_1;
|
|
143
|
+
}
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
export const updateBranch = createUpdateBranchWorkflow();
|
|
148
|
+
//# sourceMappingURL=update-branch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-branch.js","sourceRoot":"","sources":["../../src/workflows/update-branch.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jwillert/forgeflow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent workflow gateway for code forges and code agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.test.*",
|
|
13
|
+
"!dist/core/testing"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"forgeflow": "dist/cli/index.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./dist/index.js",
|
|
20
|
+
"./github": "./dist/providers/github/index.js",
|
|
21
|
+
"./gitlab": "./dist/providers/gitlab/index.js",
|
|
22
|
+
"./sqlite": "./dist/state/sqlite/index.js",
|
|
23
|
+
"./process": "./dist/process/index.js",
|
|
24
|
+
"./workflows": "./dist/workflows/index.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
28
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
|
+
"test": "node --test dist",
|
|
30
|
+
"forgeflow:once": "node dist/cli/index.js run --config .forgeflow/forgeflow.config.ts",
|
|
31
|
+
"forgeflow:drain": "node dist/cli/index.js drain --config .forgeflow/forgeflow.config.ts",
|
|
32
|
+
"prepare": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@ai-hero/sandcastle": "^0.12.0",
|
|
36
|
+
"better-sqlite3": "^11.7.0",
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
|
+
"dotenv": "^17.4.2",
|
|
39
|
+
"tsx": "^4.19.2"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
43
|
+
"@types/node": "^22.10.2",
|
|
44
|
+
"typescript": "^5.7.2"
|
|
45
|
+
}
|
|
46
|
+
}
|