@dfsramos/foreman 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/agent/adapters/agy.js +96 -0
- package/dist/agent/adapters/claude.js +152 -0
- package/dist/agent/adapters/codex.js +107 -0
- package/dist/agent/adapters/opencode.js +91 -0
- package/dist/agent/config.js +169 -0
- package/dist/agent/process.js +230 -0
- package/dist/agent/prompt.js +184 -0
- package/dist/agent/registry.js +57 -0
- package/dist/agent/types.js +12 -0
- package/dist/cli-args.js +70 -0
- package/dist/cli.js +184 -0
- package/dist/config.js +102 -0
- package/dist/daemon.js +109 -0
- package/dist/db.js +467 -0
- package/dist/git/taskstore.js +224 -0
- package/dist/git/worktree.js +248 -0
- package/dist/imports/connections.js +57 -0
- package/dist/imports/destination.js +2 -0
- package/dist/imports/linear.js +40 -0
- package/dist/imports/registry.js +4 -0
- package/dist/imports/types.js +1 -0
- package/dist/kaneo/client.js +47 -0
- package/dist/kaneo/issues.js +138 -0
- package/dist/kaneo/workspace.js +24 -0
- package/dist/linear/browse.js +55 -0
- package/dist/linear/client.js +48 -0
- package/dist/linear/issues.js +99 -0
- package/dist/linear/workspace.js +30 -0
- package/dist/paths.js +12 -0
- package/dist/pidfile.js +35 -0
- package/dist/projects.js +211 -0
- package/dist/review/decide.js +186 -0
- package/dist/review/loop.js +114 -0
- package/dist/runner.js +1281 -0
- package/dist/server/app.js +344 -0
- package/dist/server/imports.js +182 -0
- package/dist/server/tasks.js +77 -0
- package/dist/sources/branch.js +17 -0
- package/dist/sources/git-import.js +60 -0
- package/dist/sources/git.js +430 -0
- package/dist/sources/kaneo.js +156 -0
- package/dist/sources/lease.js +38 -0
- package/dist/sources/linear.js +186 -0
- package/dist/sources/options.js +26 -0
- package/dist/sources/registry.js +6 -0
- package/dist/sources/taskfile.js +190 -0
- package/dist/sources/types.js +1 -0
- package/package.json +63 -4
- package/public/agents.js +435 -0
- package/public/app.js +1200 -0
- package/public/imports.js +481 -0
- package/public/index.html +19 -0
- package/public/projects.js +481 -0
- package/public/style.css +1614 -0
- package/public/tasks.js +253 -0
- package/public/ui.js +165 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { CI_MAX_RERUNS, decideReview, trackCi } from './decide.js';
|
|
2
|
+
/**
|
|
3
|
+
* Review progress for an issue: what the tracker holds wins over the local record, so that rounds
|
|
4
|
+
* handled on another machine are believed. Before its first review round, everything since its
|
|
5
|
+
* last run counts as new.
|
|
6
|
+
*/
|
|
7
|
+
export async function progressOf(db, source, task) {
|
|
8
|
+
const held = await source.loadProgress?.(task);
|
|
9
|
+
return held ?? db.getReview(task.id) ?? { issueId: task.id, handledUntil: db.lastRunFinishedAt(task.id) ?? '', rounds: 0 };
|
|
10
|
+
}
|
|
11
|
+
/** Records review progress locally and then in the tracker, when it carries any, so another machine can resume from it. */
|
|
12
|
+
export async function saveProgress(db, source, progress) {
|
|
13
|
+
db.saveReview(progress);
|
|
14
|
+
await source.saveProgress?.(progress);
|
|
15
|
+
}
|
|
16
|
+
/** The pull request's CI, when the project waits for it and the pull request is open; null otherwise. */
|
|
17
|
+
export async function ciOf(git, project, pr) {
|
|
18
|
+
return project.waitForCi && pr?.state === 'OPEN' ? git.ciStatus(project.repoPath, pr.number) : null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hands an issue to a person over something about its pull request's head that no agent can settle,
|
|
22
|
+
* moving it to the approval state if it is still in review. A new push starts the head afresh.
|
|
23
|
+
*/
|
|
24
|
+
export async function handOverCi(db, source, task, stage, pr, needs) {
|
|
25
|
+
const moved = stage === 'review' ? ` Moved to "${source.describe().approval}"` : '';
|
|
26
|
+
await source.comment(task, `Foreman: the pull request needs a person: ${needs}.${moved}: ${pr.url}`).catch(() => { });
|
|
27
|
+
if (stage === 'review')
|
|
28
|
+
await source.approve(task);
|
|
29
|
+
await saveProgress(db, source, { ...(await progressOf(db, source, task)), ciHead: pr.headSha, ciHandled: true });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Looks at the pull request of every issue in review or awaiting approval: finishes the merged
|
|
33
|
+
* ones, moves plainly approved ones on, acts on their CI, and returns the rest of the new feedback
|
|
34
|
+
* for an agent. Issues in `busy` already have a run and are left alone.
|
|
35
|
+
*/
|
|
36
|
+
export async function checkReviews(deps, project, source, busy) {
|
|
37
|
+
const inReview = await source.fetchInReview();
|
|
38
|
+
const awaitingApproval = await source.fetchAwaitingApproval();
|
|
39
|
+
const stages = [
|
|
40
|
+
['review', inReview],
|
|
41
|
+
['approval', awaitingApproval],
|
|
42
|
+
];
|
|
43
|
+
const work = [];
|
|
44
|
+
const errors = [];
|
|
45
|
+
for (const [stage, tasks] of stages) {
|
|
46
|
+
for (const task of tasks) {
|
|
47
|
+
if (busy.has(task.id))
|
|
48
|
+
continue;
|
|
49
|
+
const pr = await deps.git.pullRequest(project.repoPath, task.branchName);
|
|
50
|
+
let ci;
|
|
51
|
+
try {
|
|
52
|
+
ci = await ciOf(deps.git, project, pr);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
errors.push(`Could not read the checks of ${task.identifier}: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let progress = await progressOf(deps.db, source, task);
|
|
59
|
+
const tracked = pr ? trackCi(progress, pr, ci, deps.now()) : null;
|
|
60
|
+
if (tracked) {
|
|
61
|
+
progress = { ...progress, ...tracked };
|
|
62
|
+
await saveProgress(deps.db, source, progress);
|
|
63
|
+
}
|
|
64
|
+
const action = decideReview({ pr, progress, stage, ignoreAuthors: project.reviewIgnoreAuthors, now: deps.now(), mode: project.reviewMode, ci });
|
|
65
|
+
switch (action.kind) {
|
|
66
|
+
case 'none':
|
|
67
|
+
break;
|
|
68
|
+
case 'finish':
|
|
69
|
+
await source.finish(task);
|
|
70
|
+
await source.comment(task, `Foreman: pull request merged (${action.pr.url}). Moved to "${source.describe().finished}".`).catch(() => { });
|
|
71
|
+
break;
|
|
72
|
+
case 'approve':
|
|
73
|
+
if (action.moveIssue) {
|
|
74
|
+
await source.approve(task);
|
|
75
|
+
await source
|
|
76
|
+
.comment(task, `Foreman: the pull request was approved. Moved to "${source.describe().approval}"; a person now merges it: ${action.pr.url}`)
|
|
77
|
+
.catch(() => { });
|
|
78
|
+
}
|
|
79
|
+
await saveProgress(deps.db, source, { ...(await progressOf(deps.db, source, task)), handledUntil: action.handledUntil, approvalHead: null });
|
|
80
|
+
break;
|
|
81
|
+
case 'hold':
|
|
82
|
+
await saveProgress(deps.db, source, { ...progress, handledUntil: action.handledUntil, approvalHead: action.pr.headSha });
|
|
83
|
+
await source
|
|
84
|
+
.comment(task, `Foreman: the pull request was approved; waiting for its checks to pass before moving it to "${source.describe().approval}": ${action.pr.url}`)
|
|
85
|
+
.catch(() => { });
|
|
86
|
+
break;
|
|
87
|
+
case 'rerun':
|
|
88
|
+
try {
|
|
89
|
+
for (const runId of action.runIds)
|
|
90
|
+
await deps.git.rerunFailed(project.repoPath, runId);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
await handOverCi(deps.db, source, task, stage, action.pr, `checks that could not be rerun (${action.checks.join(', ')}): ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
await saveProgress(deps.db, source, { ...progress, ciReruns: (progress.ciReruns ?? 0) + 1 });
|
|
97
|
+
await source
|
|
98
|
+
.comment(task, `Foreman: reran ${action.checks.join(', ')} (rerun ${(progress.ciReruns ?? 0) + 1} of ${CI_MAX_RERUNS}): GitHub Actions stopped them before they could judge the code.`)
|
|
99
|
+
.catch(() => { });
|
|
100
|
+
break;
|
|
101
|
+
case 'hand_over':
|
|
102
|
+
await handOverCi(deps.db, source, task, stage, action.pr, action.needs);
|
|
103
|
+
break;
|
|
104
|
+
case 'round':
|
|
105
|
+
work.push({ task, pr: action.pr, feedback: action.feedback, conflict: action.conflict, stage, ...(action.ciFailures ? { ciFailures: action.ciFailures } : {}) });
|
|
106
|
+
break;
|
|
107
|
+
case 'agent_review':
|
|
108
|
+
work.push({ task, pr: action.pr, feedback: [], conflict: false, stage, agentReview: true });
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { inReview: inReview.length, awaitingApproval: awaitingApproval.length, work, errors };
|
|
114
|
+
}
|