cubefarm 0.3.10 → 0.3.11
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/dist/assets/ToyWorld-Db6Gj9A-.js +2 -0
- package/dist/assets/index-DZng_ER1.js +4710 -0
- package/dist/assets/{rapier-BkzkQl77.js → rapier-aQSokaY9.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-server/index.js +124 -23
- package/dist-server/index.js.map +3 -3
- package/package.json +1 -1
- package/dist/assets/ToyWorld-CgbZTjI5.js +0 -2
- package/dist/assets/index-DxPWlxj7.js +0 -4331
package/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DZng_ER1.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-qBB5_EtY.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/dist-server/index.js
CHANGED
|
@@ -164,8 +164,8 @@ async function issueState(fullName, number) {
|
|
|
164
164
|
return null;
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
-
async function closeIssue(fullName, number, comment) {
|
|
168
|
-
await gh(["issue", "close", String(number), "-R", fullName, "--reason",
|
|
167
|
+
async function closeIssue(fullName, number, comment, reason2 = "completed") {
|
|
168
|
+
await gh(["issue", "close", String(number), "-R", fullName, "--reason", reason2, "--comment", comment]);
|
|
169
169
|
}
|
|
170
170
|
async function editIssue(fullName, number, edit) {
|
|
171
171
|
const args = ["issue", "edit", String(number), "-R", fullName];
|
|
@@ -188,6 +188,9 @@ async function mergePull(fullName, number, method, headSha) {
|
|
|
188
188
|
async function updateBranch(fullName, number) {
|
|
189
189
|
await gh(["pr", "update-branch", String(number), "-R", fullName], { timeoutMs: 6e4 });
|
|
190
190
|
}
|
|
191
|
+
async function rerunFailedJobs(fullName, runIds) {
|
|
192
|
+
for (const id of runIds) await gh(["run", "rerun", String(id), "--failed", "-R", fullName], { timeoutMs: 6e4 });
|
|
193
|
+
}
|
|
191
194
|
async function closePull(fullName, number) {
|
|
192
195
|
await gh(["pr", "close", String(number), "-R", fullName]);
|
|
193
196
|
}
|
|
@@ -893,6 +896,8 @@ function describeOfficeTool(action, input) {
|
|
|
893
896
|
return `\u{1F44B} propose_let_go ${String(input.agent_id ?? "")}`;
|
|
894
897
|
case "file_issue":
|
|
895
898
|
return `\u{1F4DD} file_issue "${clip(String(input.title ?? ""), 70)}"${floor}${input.specialty ? ` \xB7 ${input.specialty}` : ""}`;
|
|
899
|
+
case "close_issue":
|
|
900
|
+
return `\u{1F5C2}\uFE0F close_issue #${String(input.number ?? "?")}${floor}`;
|
|
896
901
|
case "route_issue":
|
|
897
902
|
return `\u{1F500} route_issue #${String(input.number ?? "?")}${floor}${input.specialty !== void 0 ? ` \xB7 ${input.specialty || "no specialty"}` : ""}${Array.isArray(input.depends_on) ? ` \xB7 depends on ${input.depends_on.map((n) => `#${n}`).join(", ") || "nothing"}` : ""}`;
|
|
898
903
|
}
|
|
@@ -2899,6 +2904,7 @@ var realBackend = {
|
|
|
2899
2904
|
closeIssue,
|
|
2900
2905
|
mergePull,
|
|
2901
2906
|
updateBranch,
|
|
2907
|
+
rerunFailedJobs,
|
|
2902
2908
|
closePull,
|
|
2903
2909
|
prForBranch,
|
|
2904
2910
|
prDetails,
|
|
@@ -2943,13 +2949,14 @@ var bareRepos = /* @__PURE__ */ new Set();
|
|
|
2943
2949
|
var mergedSinceSync = /* @__PURE__ */ new Map();
|
|
2944
2950
|
var closedIssues = /* @__PURE__ */ new Set();
|
|
2945
2951
|
var fakeSha = () => crypto6.randomBytes(20).toString("hex");
|
|
2952
|
+
var runSeq = 1e3;
|
|
2946
2953
|
function runChecks(pr, fail = Math.random() < 0.2) {
|
|
2947
2954
|
Object.assign(pr, { checks: "pending", pendingChecks: ["CI / build", "Vercel"], failedChecks: [] });
|
|
2948
2955
|
setTimeout(() => {
|
|
2949
2956
|
Object.assign(pr, {
|
|
2950
2957
|
checks: fail ? "failing" : "passing",
|
|
2951
2958
|
pendingChecks: [],
|
|
2952
|
-
failedChecks: fail ? [{ name: "CI / build", url: `${pr.url}/
|
|
2959
|
+
failedChecks: fail ? [{ name: "CI / build", url: `${pr.url.replace(/\/pull\/\d+$/, "")}/actions/runs/${++runSeq}/job/1` }] : []
|
|
2953
2960
|
});
|
|
2954
2961
|
}, 12e3 + Math.random() * 1e4);
|
|
2955
2962
|
}
|
|
@@ -3346,6 +3353,11 @@ function createDemoBackend() {
|
|
|
3346
3353
|
Object.assign(pr, { headSha: fakeSha(), mergeState: "CLEAN" });
|
|
3347
3354
|
runChecks(pr, false);
|
|
3348
3355
|
},
|
|
3356
|
+
rerunFailedJobs: async (fullName, runIds) => {
|
|
3357
|
+
for (const pr of repos.get(fullName)?.pulls ?? []) {
|
|
3358
|
+
if (pr.failedChecks.some((c) => runIds.some((id) => c.url?.includes(`/actions/runs/${id}/`)))) runChecks(pr, false);
|
|
3359
|
+
}
|
|
3360
|
+
},
|
|
3349
3361
|
closePull: async (fullName, number) => {
|
|
3350
3362
|
const pr = repos.get(fullName)?.pulls.find((p) => p.number === number);
|
|
3351
3363
|
if (pr) pr.state = "CLOSED";
|
|
@@ -3728,6 +3740,13 @@ Acceptance criteria:
|
|
|
3728
3740
|
async chat(text) {
|
|
3729
3741
|
const s2 = await status();
|
|
3730
3742
|
await think("Reading your message.");
|
|
3743
|
+
const close = text.match(/\bclose #(\d+)[\s,:;.-]*(.*)/i);
|
|
3744
|
+
const closeFloor = close && (s2.floors.find((f) => f.backlog.some((i) => i.number === Number(close[1]))) ?? s2.floors[0]);
|
|
3745
|
+
if (close && closeFloor) {
|
|
3746
|
+
const reason2 = close[2].trim() || "No longer wanted.";
|
|
3747
|
+
const out = await use("close_issue", { floor: closeFloor.floor, number: Number(close[1]), reason: reason2 });
|
|
3748
|
+
return out.startsWith("Refused") ? `I couldn't close #${close[1]}: ${out.replace(/^Refused: /, "")}` : `Done: ${out} I left "${short(reason2, 80)}" on it as a comment.`;
|
|
3749
|
+
}
|
|
3731
3750
|
const people = s2.floors.reduce((n, f) => n + f.team.length, 0);
|
|
3732
3751
|
const issues = s2.floors.reduce((n, f) => n + f.backlog.length, 0);
|
|
3733
3752
|
const pending2 = s2.pendingProposals.length;
|
|
@@ -3851,6 +3870,7 @@ import { z } from "zod";
|
|
|
3851
3870
|
var MAX_MERGE_FIXES = 3;
|
|
3852
3871
|
var CHECKS_ALERT_MS = 30 * 6e4;
|
|
3853
3872
|
var MERGE_RETRY_MS = 10 * 6e4;
|
|
3873
|
+
var RERUN_GRACE_MS = 2 * 6e4;
|
|
3854
3874
|
function mergeStep(pr, rec, now2, opts) {
|
|
3855
3875
|
if (pr.isDraft) return { do: "wait", note: "a draft: waiting", set: {} };
|
|
3856
3876
|
if (!opts.detailed && (pr.mergeable === "UNKNOWN" || pr.mergeState === "UNKNOWN")) return { do: "details", set: {} };
|
|
@@ -3861,8 +3881,12 @@ function mergeStep(pr, rec, now2, opts) {
|
|
|
3861
3881
|
if (pr.mergeable === "CONFLICTING" || pr.mergeState === "DIRTY") {
|
|
3862
3882
|
return { do: "send-back", reason: "conflict", instructions: `It conflicts with ${opts.base}.`, needsHuman, set };
|
|
3863
3883
|
}
|
|
3884
|
+
const rerun = rec.rerunSha === pr.headSha;
|
|
3864
3885
|
if (pr.checks === "failing") {
|
|
3865
3886
|
const instructions = pr.failedChecks.map((c) => `- ${c.name}${c.url ? `: ${c.url}` : ""}`).join("\n");
|
|
3887
|
+
const runIds = failedRunIds(pr.failedChecks);
|
|
3888
|
+
if (!rerun && runIds.length) return { do: "rerun", runIds, reason: "checks", instructions, needsHuman, set };
|
|
3889
|
+
if (rerun && rec.rerunAt != null && now2 - rec.rerunAt < RERUN_GRACE_MS) return { do: "wait", note: "re-running a failed check", set };
|
|
3866
3890
|
return { do: "send-back", reason: "checks", instructions, needsHuman, set };
|
|
3867
3891
|
}
|
|
3868
3892
|
if (pr.checks === "pending") {
|
|
@@ -3870,7 +3894,8 @@ function mergeStep(pr, rec, now2, opts) {
|
|
|
3870
3894
|
if (rec.pendingSince == null) set.pendingSince = since;
|
|
3871
3895
|
const alert = now2 - since > CHECKS_ALERT_MS && !rec.alerted;
|
|
3872
3896
|
if (alert) set.alerted = true;
|
|
3873
|
-
|
|
3897
|
+
const running = pr.pendingChecks.join(", ") || "running";
|
|
3898
|
+
return { do: "wait", note: rerun ? `re-running a failed check: ${running}` : `waiting for checks: ${running}`, alert, set };
|
|
3874
3899
|
}
|
|
3875
3900
|
set.pendingSince = null;
|
|
3876
3901
|
if (pr.mergeable === "UNKNOWN") return { do: "wait", note: "GitHub is checking it can merge", set };
|
|
@@ -3878,6 +3903,10 @@ function mergeStep(pr, rec, now2, opts) {
|
|
|
3878
3903
|
if (rec.mergeRetryAt && now2 < rec.mergeRetryAt) return { do: "wait", set };
|
|
3879
3904
|
return { do: "merge", set };
|
|
3880
3905
|
}
|
|
3906
|
+
function failedRunIds(failed2) {
|
|
3907
|
+
const ids = failed2.map((c) => c.url?.match(/\/actions\/runs\/(\d+)/)?.[1]).filter((id) => !!id);
|
|
3908
|
+
return [...new Set(ids)].map(Number);
|
|
3909
|
+
}
|
|
3881
3910
|
|
|
3882
3911
|
// server/qaOutcome.ts
|
|
3883
3912
|
var MAX_QA_ROUNDS = 3;
|
|
@@ -4083,6 +4112,16 @@ function createOfficeTools(h) {
|
|
|
4083
4112
|
depends_on: z2.array(z2.number().int().positive()).max(10).optional().describe("Issues it waits for; [] for none. Not for an issue in progress.")
|
|
4084
4113
|
},
|
|
4085
4114
|
(a) => run2(() => h.routeIssue(a))
|
|
4115
|
+
),
|
|
4116
|
+
tool(
|
|
4117
|
+
"close_issue",
|
|
4118
|
+
'Close an open issue that is superseded or no longer wanted, as "not planned", with your reason as a comment. Not while an open pull request closes it. Nothing is deleted.',
|
|
4119
|
+
{
|
|
4120
|
+
floor: z2.number().int(),
|
|
4121
|
+
number: z2.number().int().positive().describe("The issue number"),
|
|
4122
|
+
reason: z2.string().min(1).max(1e3).describe('Posted as a comment, e.g. "Superseded by #152."')
|
|
4123
|
+
},
|
|
4124
|
+
(a) => run2(() => h.closeIssue(a))
|
|
4086
4125
|
)
|
|
4087
4126
|
];
|
|
4088
4127
|
const server2 = createSdkMcpServer({ name: "office", version: "1.0.0", tools: defs });
|
|
@@ -4112,7 +4151,9 @@ function ceoSystemPrompt(o) {
|
|
|
4112
4151
|
"Your job is to run the company, not to write code:",
|
|
4113
4152
|
"- Understand each project: what it is, its stack, how far along it is, and what kind of people it needs. Projects differ a lot. A static marketing site, a 3D browser game and a REST API need different specialists and different QA.",
|
|
4114
4153
|
`- Shape each floor's team. Propose specialists with a specific title and a job description written for this project. Keep teams lean: agents on the same coding agent share one subscription's usage limits${o.sessionLimit ? ` and at most ${o.sessionLimit} sessions run at once` : ""}, so a floor rarely needs more than ${o.teamCap} people. When the manager asks for a bigger team, follow that, up to the floor's free seats (seats in company_status). Propose letting people go when a floor is clearly overstaffed or a specialty is no longer needed.`,
|
|
4115
|
-
"- Plan the work: turn a floor's brief into
|
|
4154
|
+
"- Plan the work: turn a floor's brief into well-specified GitHub issues with acceptance criteria. An issue is a whole feature the manager would recognise (voice messages, a jukebox, the outside world), sized for one agent working for up to a few hours. Agents have large context windows and handle long jobs. Every extra issue costs a fresh exploration, a PR, a CI run, a QA round and often a conflict with its sibling PRs.",
|
|
4155
|
+
"- Split a feature only when its parts are truly independent AND touch different files, or when one risky foundation part should land and be tested first. Never split a feature just to give idle developers something to do: parallel work comes from different features side by side. Unrelated small fixes are still their own issues.",
|
|
4156
|
+
"- Route each issue to a specialty. The office hands issues out itself: a free specialist gets first pick of their specialty, and otherwise any free developer takes the next issue that can start, so a specialty is a preference, not a lock.",
|
|
4116
4157
|
"- Write each floor's QA brief: what QA testers must check for this kind of project (for a 3D game: the canvas renders, controls respond, frame rate is smooth; for a website: links, phone layout, accessibility; for an API: status codes, validation, error cases).",
|
|
4117
4158
|
"",
|
|
4118
4159
|
"How you work:",
|
|
@@ -4127,7 +4168,8 @@ function ceoSystemPrompt(o) {
|
|
|
4127
4168
|
'- Titles are specific ("Three.js graphics engineer", not "Developer"). A specialty is a short lowercase slug ("graphics", "gameplay", "frontend", "backend", "content", "a11y", "devops"). Only route an issue to a specialty that someone on the floor has, or that you are proposing to hire.',
|
|
4128
4169
|
"- Before proposing a hire, check the floor and the pending proposals for someone who already covers it. If the manager declined a similar proposal (recentDecisions), do not propose it again unless something has changed, and say what.",
|
|
4129
4170
|
`- ${o.hiring === "auto" ? "Hiring is on auto: proposals within the team cap are approved immediately, so be deliberate." : "The manager approves every hire, so explain each reason in a sentence or two they can decide on."}`,
|
|
4130
|
-
`- Issues:
|
|
4171
|
+
`- Issues: QA is usually the scarcer resource. When PRs queue for QA (capacity.prsAwaitingQa in company_status), file fewer, bigger issues, not more. Most briefs need 1 to 4 issues. Write "Depends on #N" only when an issue truly cannot start until #N's code is merged, because it waits until #N is closed. Keep dependency chains to two steps at most. The office starts the issues that hold up others first. Do not duplicate open issues: fix an existing issue's specialty or dependencies with route_issue. File at most 12 issues per job, or per message from the manager.`,
|
|
4172
|
+
"- Close an issue that is superseded or no longer wanted with close_issue, not by making it wait for another issue.",
|
|
4131
4173
|
"- When company.usage in company_status says pacing or paused, Claude's usage is running low and the office is finishing open work first: file only what is needed next, not a whole milestone.",
|
|
4132
4174
|
"- Your final message goes straight to the manager's phone. Keep it short and plain: what you found, what you proposed, what you filed, and any question you need answered. No headings, no tables."
|
|
4133
4175
|
].join("\n");
|
|
@@ -4153,7 +4195,7 @@ ${floor.backlog === 0 ? "The backlog is empty: plan the first milestone as issue
|
|
|
4153
4195
|
"",
|
|
4154
4196
|
"Plan the next milestone toward it:",
|
|
4155
4197
|
`- Read the current code and the ${floor.backlog} open issues first, so you build on what exists and do not duplicate anything.`,
|
|
4156
|
-
"-
|
|
4198
|
+
"- One issue per whole feature. Split a feature only when its parts are independent and touch different files, or a risky foundation part should land first. Only for an empty or nearly empty repository does a skeleton issue come first, with the others depending on it.",
|
|
4157
4199
|
"- File the issues, each routed to a specialty.",
|
|
4158
4200
|
"- Make sure the floor has the specialists those issues need; propose hires if not.",
|
|
4159
4201
|
"- Update the floor profile and QA brief if the brief changes what the project is."
|
|
@@ -4162,7 +4204,7 @@ ${floor.backlog === 0 ? "The backlog is empty: plan the first milestone as issue
|
|
|
4162
4204
|
return [
|
|
4163
4205
|
"Periodic review of the company. For every floor, look at:",
|
|
4164
4206
|
"- floors without a profile or QA brief: study them and write one",
|
|
4165
|
-
"- backlog against the team (capacity):
|
|
4207
|
+
"- backlog against the team (capacity): long dependency chains or a specialty with a long queue (fix those with route_issue), or PRs piling up in QA (then plan fewer, bigger issues). Idle developers are not a reason to slice features: they cover QA.",
|
|
4166
4208
|
"- pull requests stuck in QA or marked as needing a human",
|
|
4167
4209
|
"- floors with a brief and an empty backlog: plan the next milestone",
|
|
4168
4210
|
"Propose hires or let-gos only when clearly justified. If nothing needs doing, reply with one short sentence saying so."
|
|
@@ -4247,13 +4289,28 @@ function planRoute(r) {
|
|
|
4247
4289
|
const loop = deps.find((d) => reaches(d, r.number, waits));
|
|
4248
4290
|
if (loop !== void 0) throw new Error(`#${loop} already waits for #${r.number}, directly or through other issues, so that would be a cycle.`);
|
|
4249
4291
|
const depth = waitsDepth(r.number, waits) + (holdUps(after).get(r.number)?.chain ?? 0);
|
|
4250
|
-
if (depth > 2) throw new Error(`That makes a dependency chain ${depth} steps deep through #${r.number}. Keep chains to 2 steps at most:
|
|
4292
|
+
if (depth > 2) throw new Error(`That makes a dependency chain ${depth} steps deep through #${r.number}. Keep chains to 2 steps at most: fold the dependent pieces into one issue instead of splitting further.`);
|
|
4251
4293
|
if (body2 !== issue2.body) plan.body = body2;
|
|
4252
4294
|
done.push(deps.length ? `depends on ${deps.map((d) => `#${d}`).join(", ")}` : "no dependencies");
|
|
4253
4295
|
}
|
|
4254
4296
|
plan.summary = `#${r.number} on floor ${r.floor}: ${done.join(", ")}.`;
|
|
4255
4297
|
return plan;
|
|
4256
4298
|
}
|
|
4299
|
+
function floorCapacity(f) {
|
|
4300
|
+
const open = new Set(f.issues.map((i) => i.number));
|
|
4301
|
+
const prs = new Set(f.openPrs);
|
|
4302
|
+
return {
|
|
4303
|
+
issuesWaitingOnOthers: f.issues.filter((i) => !f.inProgress(i.number) && blockers(i.body, open).length > 0).length,
|
|
4304
|
+
longestDependencyChain: Math.max(0, ...[...holdUps(f.issues).values()].map((w) => w.chain)),
|
|
4305
|
+
prsAwaitingQa: f.qa.filter((q) => prs.has(q.prNumber) && (q.status === "queued" || q.status === "testing")).length
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4308
|
+
function checkCloseIssue(r) {
|
|
4309
|
+
if (r.state === "CLOSED") throw new HttpError(404, `#${r.number} on floor ${r.floor} is already closed.`);
|
|
4310
|
+
if (r.state !== "OPEN") throw new HttpError(404, `There is no open issue #${r.number} on floor ${r.floor}.`);
|
|
4311
|
+
const pr = r.pulls.find((p) => p.state === "OPEN" && p.closesIssues.includes(r.number));
|
|
4312
|
+
if (pr) throw new HttpError(409, `PR #${pr.number} closes #${r.number}. Close or finish that pull request first.`);
|
|
4313
|
+
}
|
|
4257
4314
|
function reaches(from, to, waits, seen = /* @__PURE__ */ new Set()) {
|
|
4258
4315
|
if (from === to) return true;
|
|
4259
4316
|
if (seen.has(from)) return false;
|
|
@@ -4275,10 +4332,18 @@ async function setUpDesk(term, steps) {
|
|
|
4275
4332
|
|
|
4276
4333
|
// server/fixOutcome.ts
|
|
4277
4334
|
var MAX_FIX_FAILURES = 2;
|
|
4278
|
-
function fixOutcome(rec, head, minutes, counts = true) {
|
|
4335
|
+
function fixOutcome(rec, head, minutes, counts = true, checks = null) {
|
|
4279
4336
|
const pr = rec.prNumber;
|
|
4280
4337
|
const before = rec.fixReason === "conflict" ? rec.passedSha ?? rec.testedSha : rec.testedSha;
|
|
4281
|
-
|
|
4338
|
+
const unpushed = rec.fixReason !== "checks" && head && before && head === before;
|
|
4339
|
+
if (unpushed && rec.fixReason !== "conflict" && rec.qaChecks === "failing" && (checks === "passing" || checks === "pending")) {
|
|
4340
|
+
return {
|
|
4341
|
+
set: { status: "queued", round: rec.round + 1, retests: rec.retests + 1, sessionFailures: 0 },
|
|
4342
|
+
log: { kind: "done", text: `\u2714 PR #${pr}'s failed checks were re-run in ${minutes}m. Back to QA.` },
|
|
4343
|
+
pushed: true
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
if (unpushed) {
|
|
4282
4347
|
const sessionFailures = rec.sessionFailures + (counts ? 1 : 0);
|
|
4283
4348
|
const needsHuman = sessionFailures >= MAX_FIX_FAILURES;
|
|
4284
4349
|
return {
|
|
@@ -4638,7 +4703,7 @@ ${repo.qaBrief}`] : [],
|
|
|
4638
4703
|
testStep2 ?? "3. Install dependencies if needed, then run the project's test suite, linters, type checks and build (whichever exist).",
|
|
4639
4704
|
repo.browserTesting ? `4. If the project has a UI, start it in the background on port ${port} (reserved for you) and exercise the change in a real browser with the Playwright tools: navigate, click, type, resize to a phone size, try edge cases, and check the console for errors. Take a screenshot with browser_take_screenshot (no filename) of every important state: the screenshots are attached to the PR as evidence. Stop the server afterwards.` : "4. Exercise the changed behaviour directly (run the program, call the API, write a quick script).",
|
|
4640
4705
|
"5. You may write throwaway scripts to probe behaviour, but do not commit them.",
|
|
4641
|
-
"A merge conflict with the default branch is not a fail: judge the change itself (on a throwaway merge if you need newer work) and pass it if it's good. The office sends conflicting PRs back for a merge fix, then you re-test.",
|
|
4706
|
+
"A merge conflict with the default branch is not a fail, nor is a red check unrelated to this change (a flake or an outage: say why): judge the change itself (on a throwaway merge if you need newer work) and pass it if it's good. The office sends conflicting PRs back for a merge fix, then you re-test, and re-runs a failed check before anyone fixes it.",
|
|
4642
4707
|
"",
|
|
4643
4708
|
"Rules: do not modify the code under test, do not commit, push, comment on, review or merge anything on GitHub. The office posts your report on the pull request. Finish with the structured QA report: verdict, summary, the checks you performed, the commands you ran and one caption per screenshot."
|
|
4644
4709
|
].join("\n");
|
|
@@ -5104,7 +5169,8 @@ var Swarm = class {
|
|
|
5104
5169
|
proposeHire: (a) => this.proposeHire(a),
|
|
5105
5170
|
proposeLetGo: (a) => this.proposeLetGo(a),
|
|
5106
5171
|
fileIssue: (a) => this.fileIssue(a),
|
|
5107
|
-
routeIssue: (a) => this.routeIssue(a)
|
|
5172
|
+
routeIssue: (a) => this.routeIssue(a),
|
|
5173
|
+
closeIssue: (a) => this.closeIssue(a)
|
|
5108
5174
|
});
|
|
5109
5175
|
}
|
|
5110
5176
|
ceoIssues = new IssueCap(MAX_ISSUES_PER_JOB);
|
|
@@ -5180,6 +5246,9 @@ var Swarm = class {
|
|
|
5180
5246
|
pendingSince: q.pendingSince ?? null,
|
|
5181
5247
|
mergeRetryAt: q.mergeRetryAt ?? null,
|
|
5182
5248
|
alerted: q.alerted ?? false,
|
|
5249
|
+
rerunSha: q.rerunSha ?? null,
|
|
5250
|
+
rerunAt: q.rerunAt ?? null,
|
|
5251
|
+
qaChecks: q.qaChecks ?? null,
|
|
5183
5252
|
mergeNote: null
|
|
5184
5253
|
})),
|
|
5185
5254
|
requests: loaded.requests ?? [],
|
|
@@ -5680,6 +5749,16 @@ var Swarm = class {
|
|
|
5680
5749
|
setTimeout(() => this.schedule(), 200);
|
|
5681
5750
|
return false;
|
|
5682
5751
|
}
|
|
5752
|
+
if (step.do === "rerun") {
|
|
5753
|
+
try {
|
|
5754
|
+
await this.backend.rerunFailedJobs(repo.fullName, step.runIds);
|
|
5755
|
+
} catch (err) {
|
|
5756
|
+
console.warn(`could not re-run the failed checks of ${repo.fullName}#${pr.number}`, err);
|
|
5757
|
+
return this.sendBack(repo, rec, step.reason, step.instructions, step.needsHuman);
|
|
5758
|
+
}
|
|
5759
|
+
this.setQa(rec, { rerunSha: pr.headSha, rerunAt: Date.now(), pendingSince: null, alerted: false, mergeNote: "re-running a failed check" });
|
|
5760
|
+
return false;
|
|
5761
|
+
}
|
|
5683
5762
|
if (step.do === "send-back") return this.sendBack(repo, rec, step.reason, step.instructions, step.needsHuman);
|
|
5684
5763
|
if (step.do === "wait") {
|
|
5685
5764
|
if (step.alert) {
|
|
@@ -6425,7 +6504,10 @@ Note from the manager: ${note}` : ""
|
|
|
6425
6504
|
retests: 0,
|
|
6426
6505
|
pendingSince: null,
|
|
6427
6506
|
mergeRetryAt: null,
|
|
6428
|
-
alerted: false
|
|
6507
|
+
alerted: false,
|
|
6508
|
+
rerunSha: null,
|
|
6509
|
+
rerunAt: null,
|
|
6510
|
+
qaChecks: null
|
|
6429
6511
|
};
|
|
6430
6512
|
this.state.qa.push(rec);
|
|
6431
6513
|
this.setQa(rec, {});
|
|
@@ -6554,7 +6636,7 @@ ${issue2.body.trim() || "(no description)"}` : ""
|
|
|
6554
6636
|
this.appendLog(a, [{ kind: pass ? "done" : "error", text: `${pass ? "\u2705 QA passed" : "\u274C QA failed"} PR #${a.prNumber} \xB7 ${report.checks.length} checks \xB7 ${rt.shots.length} screenshots` }]);
|
|
6555
6637
|
let commentUrl = null;
|
|
6556
6638
|
if (rec) {
|
|
6557
|
-
const pull = !pass
|
|
6639
|
+
const pull = !pass ? await this.backend.prDetails(repo.fullName, rec.prNumber).catch(() => null) : null;
|
|
6558
6640
|
const next = qaOutcome(pass, rec, pull);
|
|
6559
6641
|
try {
|
|
6560
6642
|
this.appendLog(a, [{ kind: "system", text: "\u{1F4CE} Uploading evidence and posting the QA report on the PR\u2026" }]);
|
|
@@ -6579,7 +6661,8 @@ ${issue2.body.trim() || "(no description)"}` : ""
|
|
|
6579
6661
|
mergeNote: null,
|
|
6580
6662
|
pendingSince: null,
|
|
6581
6663
|
mergeRetryAt: null,
|
|
6582
|
-
alerted: false
|
|
6664
|
+
alerted: false,
|
|
6665
|
+
qaChecks: pull?.checks ?? null
|
|
6583
6666
|
});
|
|
6584
6667
|
if (next === "conflict") this.sendBack(repo, rec, "conflict", conflictFixInstructions(fixInstructions, repo.defaultBranch), false);
|
|
6585
6668
|
this.toast(
|
|
@@ -6690,7 +6773,7 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
6690
6773
|
}
|
|
6691
6774
|
async onFixFinished(a, repo, result) {
|
|
6692
6775
|
const rec = this.state.qa.find((q) => q.repoId === repo.id && q.prNumber === a.prNumber);
|
|
6693
|
-
const
|
|
6776
|
+
const current = rec && result.ok && a.status !== "stopped" && rec.fixReason !== "checks" ? await this.prHead(a, repo, rec.prNumber) : null;
|
|
6694
6777
|
if (a.status === "stopped") {
|
|
6695
6778
|
if (rec?.status === "fixing") this.setQa(rec, { status: "failed" });
|
|
6696
6779
|
return;
|
|
@@ -6706,7 +6789,7 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
6706
6789
|
this.appendLog(a, [{ kind: "done", text: `\u2714 Fix pushed for PR #${a.prNumber} in ${this.minutes(a)}m. Back to QA.` }]);
|
|
6707
6790
|
return;
|
|
6708
6791
|
}
|
|
6709
|
-
const step = fixOutcome(rec,
|
|
6792
|
+
const step = fixOutcome(rec, current?.headSha ?? null, this.minutes(a), !this.limited(), current?.checks ?? null);
|
|
6710
6793
|
if (!step.pushed) {
|
|
6711
6794
|
const needsHuman = step.set.status === "needs-human";
|
|
6712
6795
|
a.status = "error";
|
|
@@ -6724,13 +6807,13 @@ Full report with screenshots: ${rec.commentUrl}` : "",
|
|
|
6724
6807
|
this.setQa(rec, { ...step.set, devSessionId: a.sessionId ?? rec.devSessionId });
|
|
6725
6808
|
this.toast(
|
|
6726
6809
|
"info",
|
|
6727
|
-
step.set.status === "passed" ? `${a.name} fixed PR #${rec.prNumber}; it merges once it passes again` : `${a.name} pushed fixes for PR #${rec.prNumber}; QA round ${rec.round} is queued`
|
|
6810
|
+
step.set.status === "passed" ? `${a.name} fixed PR #${rec.prNumber}; it merges once it passes again` : step.set.retests ? `${a.name} re-ran the failed checks on PR #${rec.prNumber}; QA re-tests it` : `${a.name} pushed fixes for PR #${rec.prNumber}; QA round ${rec.round} is queued`
|
|
6728
6811
|
);
|
|
6729
6812
|
}
|
|
6730
|
-
/** The PR's head commit now; null (with a warning in the agent's log) when GitHub can't be asked. */
|
|
6813
|
+
/** The PR's head commit and checks now; null (with a warning in the agent's log) when GitHub can't be asked. */
|
|
6731
6814
|
async prHead(a, repo, prNumber) {
|
|
6732
6815
|
try {
|
|
6733
|
-
return
|
|
6816
|
+
return await this.backend.prDetails(repo.fullName, prNumber);
|
|
6734
6817
|
} catch (err) {
|
|
6735
6818
|
console.warn(`could not read the head of ${repo.fullName}#${prNumber}`, err);
|
|
6736
6819
|
this.appendLog(a, [{ kind: "system", text: `\u26A0 Could not check PR #${prNumber} for new commits (${oneLine(err)}); taking the fix at its word.` }]);
|
|
@@ -7488,8 +7571,12 @@ ${t}`);
|
|
|
7488
7571
|
developers: this.state.agents.filter((a) => a.repoId === r.id && a.role === "dev").length,
|
|
7489
7572
|
developersFree: this.available(r, "dev").length,
|
|
7490
7573
|
issuesReadyToStart: this.readyIssues(r).length,
|
|
7491
|
-
|
|
7492
|
-
|
|
7574
|
+
...floorCapacity({
|
|
7575
|
+
issues: rt.issues,
|
|
7576
|
+
inProgress: (n) => this.issueTaken(r, n),
|
|
7577
|
+
openPrs: rt.pulls.filter((p) => p.state === "OPEN").map((p) => p.number),
|
|
7578
|
+
qa: this.state.qa.filter((q) => q.repoId === r.id)
|
|
7579
|
+
})
|
|
7493
7580
|
},
|
|
7494
7581
|
team: this.state.agents.filter((a) => a.repoId === r.id).map((a) => ({
|
|
7495
7582
|
id: a.id,
|
|
@@ -7720,6 +7807,20 @@ _Filed by ${this.ceo().name}, the cubefarm CEO._`;
|
|
|
7720
7807
|
}
|
|
7721
7808
|
return plan.summary;
|
|
7722
7809
|
}
|
|
7810
|
+
/** Close an open issue as not planned, with the CEO's reason as its comment (see checkCloseIssue for what is refused). */
|
|
7811
|
+
async closeIssue(x) {
|
|
7812
|
+
const repo = this.floorRepo(x.floor);
|
|
7813
|
+
const n = Number(x.number);
|
|
7814
|
+
const rt = this.repoRt.get(repo.id);
|
|
7815
|
+
const reason2 = String(x.reason ?? "").trim().slice(0, 1e3);
|
|
7816
|
+
if (!reason2) throw new HttpError(400, "Say why the issue is closing: the reason is posted on it as a comment.");
|
|
7817
|
+
const state = rt?.issues.some((i) => i.number === n) ? "OPEN" : await this.backend.issueState(repo.fullName, n).catch(() => null);
|
|
7818
|
+
checkCloseIssue({ floor: repo.floor, number: n, state, pulls: rt?.pulls ?? [] });
|
|
7819
|
+
await this.backend.closeIssue(repo.fullName, n, reason2, "not planned");
|
|
7820
|
+
await this.syncRepo(repo.id);
|
|
7821
|
+
this.postMessage("office", `\u{1F5C2}\uFE0F ${this.ceo().name} closed #${n} on floor ${repo.floor}: ${reason2.split("\n")[0].slice(0, 200)}`);
|
|
7822
|
+
return `Closed #${n} on floor ${repo.floor} as not planned.`;
|
|
7823
|
+
}
|
|
7723
7824
|
};
|
|
7724
7825
|
|
|
7725
7826
|
// server/index.ts
|