@tablation/crew 0.54.3 → 0.54.4
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/cli.js +98 -38
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3404,6 +3404,69 @@ function validateContract(c) {
|
|
|
3404
3404
|
}
|
|
3405
3405
|
var EVENT_LOG_MISSING = "the Issues table has no event log \u2014 crew writes its audit trail there (CREW-1574). In the app, open the Issues table's Data & lifecycle tab and turn on Event log, then rerun.";
|
|
3406
3406
|
|
|
3407
|
+
// src/callout.ts
|
|
3408
|
+
var calloutHeader = (kind, label) => `> [!${kind}]
|
|
3409
|
+
> **Crew** \u2014 ${label}`;
|
|
3410
|
+
var calloutLabelLine = (label) => `> **Crew** \u2014 ${label}`;
|
|
3411
|
+
|
|
3412
|
+
// src/gate-red.ts
|
|
3413
|
+
var GATE_RED_MARKER = calloutHeader("CAUTION", "Test Gate Red");
|
|
3414
|
+
var shaLine = (g) => `Head \`${g.sha.slice(0, 8)}\`.`;
|
|
3415
|
+
var GATE_RED_AFTER = 3;
|
|
3416
|
+
var lastLines = (output, n) => output.replace(/\u001b\[[0-9;]*m/g, "").trimEnd().split("\n").slice(-n).join("\n");
|
|
3417
|
+
function gateRedTickets(merged, merges) {
|
|
3418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3419
|
+
const out = [];
|
|
3420
|
+
for (const c of [...merged, ...merges.filter((m) => m.skipReason === "already-merged")]) {
|
|
3421
|
+
if (seen.has(c.ticket.id)) continue;
|
|
3422
|
+
seen.add(c.ticket.id);
|
|
3423
|
+
out.push(c.ticket);
|
|
3424
|
+
}
|
|
3425
|
+
return out;
|
|
3426
|
+
}
|
|
3427
|
+
function gateRedComment(g) {
|
|
3428
|
+
return `${GATE_RED_MARKER}
|
|
3429
|
+
> ${shaLine(g)}
|
|
3430
|
+
|
|
3431
|
+
**This ticket is merged on the base, but the release test gate is red \u2014 nothing has shipped.**
|
|
3432
|
+
|
|
3433
|
+
The \`test\` hook (\`${g.hook}\`) has failed ${g.count} cycles running on the same head (\`${g.sha.slice(0, 8)}\`). The ticket's commit is on the base and will ship once the gate passes; it is not stranded, and re-setting it to \`verified\` will not help. Last lines of the hook's output:
|
|
3434
|
+
|
|
3435
|
+
\`\`\`
|
|
3436
|
+
${g.tail}
|
|
3437
|
+
\`\`\`
|
|
3438
|
+
`;
|
|
3439
|
+
}
|
|
3440
|
+
var alreadyReported = (ticket, g, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "gate_red" && (e.message ?? "").includes(shaLine(g)));
|
|
3441
|
+
|
|
3442
|
+
// src/test-evidence.ts
|
|
3443
|
+
var MAX_FAIL_LINES = 20;
|
|
3444
|
+
var ANSI = /\u001b\[[0-9;]*m/g;
|
|
3445
|
+
var FAIL_LINE = /^\s*(FAIL\b|●\s|not ok\b)/;
|
|
3446
|
+
var ERROR_LINE = /\b(?:[A-Z]\w*)?Error\b[:\s]/;
|
|
3447
|
+
function failureSummary(output, tailLines = 15) {
|
|
3448
|
+
const lines = output.replace(ANSI, "").trimEnd().split("\n");
|
|
3449
|
+
const names = [];
|
|
3450
|
+
for (const l of lines) {
|
|
3451
|
+
if (FAIL_LINE.test(l) && !names.includes(l.trim())) names.push(l.trim());
|
|
3452
|
+
if (names.length >= MAX_FAIL_LINES) break;
|
|
3453
|
+
}
|
|
3454
|
+
const at = lines.findIndex((l) => ERROR_LINE.test(l));
|
|
3455
|
+
if (names.length === 0 && at < 0) return lastLines(output, tailLines);
|
|
3456
|
+
const parts = [...names];
|
|
3457
|
+
if (at >= 0) parts.push("", ...lines.slice(at, at + 4).map((l) => l.trimEnd()));
|
|
3458
|
+
return parts.join("\n").trim();
|
|
3459
|
+
}
|
|
3460
|
+
var headLine = (head) => `Candidate head \`${head.slice(0, 8)}\`.`;
|
|
3461
|
+
function describeComposition(c) {
|
|
3462
|
+
const rows = c.branches.length ? c.branches.map((b) => `- ${b.key} \`${b.branch}\` @ \`${b.tip.slice(0, 8)}\``).join("\n") : "- (none merged this cycle)";
|
|
3463
|
+
return `${headLine(c.head)} Base before merging \`${c.base.slice(0, 8)}\`. Merged branches:
|
|
3464
|
+
|
|
3465
|
+
${rows}
|
|
3466
|
+
|
|
3467
|
+
The gate runs on the base plus every verified branch above, not on the base alone: reproduce on that composition.`;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3407
3470
|
// src/shell.ts
|
|
3408
3471
|
import { spawn } from "child_process";
|
|
3409
3472
|
import { platform } from "process";
|
|
@@ -3515,7 +3578,13 @@ function describeRelease(o, route) {
|
|
|
3515
3578
|
return {
|
|
3516
3579
|
level: "fail",
|
|
3517
3580
|
headline: `${route}: release blocked \u2014 tests failed`,
|
|
3518
|
-
detail:
|
|
3581
|
+
detail: withHookTail(
|
|
3582
|
+
o.composition ? `the target stays on the previous release
|
|
3583
|
+
|
|
3584
|
+
${describeComposition(o.composition)}` : "the target stays on the previous release",
|
|
3585
|
+
o.hookFailure
|
|
3586
|
+
),
|
|
3587
|
+
...o.composition ? { head: o.composition.head } : {}
|
|
3519
3588
|
};
|
|
3520
3589
|
}
|
|
3521
3590
|
if (o.stopped === "setup failed") {
|
|
@@ -6497,11 +6566,6 @@ function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
|
6497
6566
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
6498
6567
|
import { join as join11 } from "path";
|
|
6499
6568
|
|
|
6500
|
-
// src/callout.ts
|
|
6501
|
-
var calloutHeader = (kind, label) => `> [!${kind}]
|
|
6502
|
-
> **Crew** \u2014 ${label}`;
|
|
6503
|
-
var calloutLabelLine = (label) => `> **Crew** \u2014 ${label}`;
|
|
6504
|
-
|
|
6505
6569
|
// src/stalled.ts
|
|
6506
6570
|
var STALLED_AFTER_CYCLES = 30;
|
|
6507
6571
|
var CYCLE_MS = 120 * 1e3;
|
|
@@ -7229,36 +7293,6 @@ async function applyStrandedVerified(writer, s, contract, operatorId, memberId,
|
|
|
7229
7293
|
}
|
|
7230
7294
|
}
|
|
7231
7295
|
|
|
7232
|
-
// src/gate-red.ts
|
|
7233
|
-
var GATE_RED_MARKER = calloutHeader("CAUTION", "Test Gate Red");
|
|
7234
|
-
var shaLine = (g) => `Head \`${g.sha.slice(0, 8)}\`.`;
|
|
7235
|
-
var GATE_RED_AFTER = 3;
|
|
7236
|
-
var lastLines = (output, n) => output.replace(/\u001b\[[0-9;]*m/g, "").trimEnd().split("\n").slice(-n).join("\n");
|
|
7237
|
-
function gateRedTickets(merged, merges) {
|
|
7238
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7239
|
-
const out = [];
|
|
7240
|
-
for (const c of [...merged, ...merges.filter((m) => m.skipReason === "already-merged")]) {
|
|
7241
|
-
if (seen.has(c.ticket.id)) continue;
|
|
7242
|
-
seen.add(c.ticket.id);
|
|
7243
|
-
out.push(c.ticket);
|
|
7244
|
-
}
|
|
7245
|
-
return out;
|
|
7246
|
-
}
|
|
7247
|
-
function gateRedComment(g) {
|
|
7248
|
-
return `${GATE_RED_MARKER}
|
|
7249
|
-
> ${shaLine(g)}
|
|
7250
|
-
|
|
7251
|
-
**This ticket is merged on the base, but the release test gate is red \u2014 nothing has shipped.**
|
|
7252
|
-
|
|
7253
|
-
The \`test\` hook (\`${g.hook}\`) has failed ${g.count} cycles running on the same head (\`${g.sha.slice(0, 8)}\`). The ticket's commit is on the base and will ship once the gate passes; it is not stranded, and re-setting it to \`verified\` will not help. Last lines of the hook's output:
|
|
7254
|
-
|
|
7255
|
-
\`\`\`
|
|
7256
|
-
${g.tail}
|
|
7257
|
-
\`\`\`
|
|
7258
|
-
`;
|
|
7259
|
-
}
|
|
7260
|
-
var alreadyReported = (ticket, g, events) => events.some((e) => e.record_id === ticket.id && e.event_type === "gate_red" && (e.message ?? "").includes(shaLine(g)));
|
|
7261
|
-
|
|
7262
7296
|
// src/agent.ts
|
|
7263
7297
|
import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
|
|
7264
7298
|
import { readFileSync as readFileSync12, existsSync as existsSync14, statSync as statSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, createWriteStream, openSync as openSync3, closeSync as closeSync3, readSync } from "fs";
|
|
@@ -10693,7 +10727,21 @@ async function mergeAndRelease(o, decision, fresh, releaseBranch, isolated = fal
|
|
|
10693
10727
|
const sha = headSha(o.cwd);
|
|
10694
10728
|
const count = o.state?.noteTestGateFailed?.(sha) ?? 1;
|
|
10695
10729
|
const gateRed = count >= GATE_RED_AFTER && !o.state?.testGateReported?.(sha) ? { sha, count, hook: hookLabel(o.repo, "test"), tail: lastLines(r.output, 15) } : void 0;
|
|
10696
|
-
|
|
10730
|
+
const composition = {
|
|
10731
|
+
head: sha,
|
|
10732
|
+
base: preMerge,
|
|
10733
|
+
branches: merged.filter((c) => c.branch).map((c) => ({
|
|
10734
|
+
key: c.ticket.issue_tag ?? c.ticket.issue_id,
|
|
10735
|
+
branch: c.branch,
|
|
10736
|
+
tip: resolve2(o.cwd, c.branch) ?? ""
|
|
10737
|
+
}))
|
|
10738
|
+
};
|
|
10739
|
+
const hookFailure2 = {
|
|
10740
|
+
hook: "test",
|
|
10741
|
+
label: hookLabel(o.repo, "test"),
|
|
10742
|
+
tail: failureSummary(r.output, HOOK_FAILURE_TAIL_LINES)
|
|
10743
|
+
};
|
|
10744
|
+
return { merged, conflicts, unbuildable, deployed: false, stopped: "tests failed", decision, gateRed, hookFailure: hookFailure2, composition };
|
|
10697
10745
|
}
|
|
10698
10746
|
o.state?.clearTestGateFailed?.();
|
|
10699
10747
|
}
|
|
@@ -10865,9 +10913,15 @@ async function applyFailureAlert(writer, n, route, openTickets, memberId, log, d
|
|
|
10865
10913
|
return { kind: "commented", issueId: label };
|
|
10866
10914
|
}
|
|
10867
10915
|
try {
|
|
10916
|
+
let body = n.detail ?? n.headline;
|
|
10917
|
+
if (n.head && writer.events) {
|
|
10918
|
+
const line = headLine(n.head);
|
|
10919
|
+
const shown = (await writer.events("failure_alert")).some((e) => e.record_id === existing.id && (e.message ?? "").includes(line));
|
|
10920
|
+
if (shown) body = `${n.headline} \u2014 ${line}`;
|
|
10921
|
+
}
|
|
10868
10922
|
await writer.postEvent(existing.id, `${FAILURE_ALERT_MARKER}
|
|
10869
10923
|
|
|
10870
|
-
Still failing: ${
|
|
10924
|
+
Still failing: ${body}`, memberId, "failure_alert");
|
|
10871
10925
|
log.warn(`release failure recurring \u2014 commented on ${label}`, { step: "release" });
|
|
10872
10926
|
return { kind: "commented", issueId: label };
|
|
10873
10927
|
} catch (e) {
|
|
@@ -10889,6 +10943,12 @@ Still failing: ${n.detail ?? n.headline}`, memberId, "failure_alert");
|
|
|
10889
10943
|
...placement.project_id ? { project_id: placement.project_id } : {}
|
|
10890
10944
|
}, memberId);
|
|
10891
10945
|
const label = created.issue_id ? displayKey(created) : created.id;
|
|
10946
|
+
if (n.head) {
|
|
10947
|
+
await writer.postEvent(created.id, `${FAILURE_ALERT_MARKER}
|
|
10948
|
+
|
|
10949
|
+
Filed with the evidence above. ${headLine(n.head)}`, memberId, "failure_alert").catch(() => {
|
|
10950
|
+
});
|
|
10951
|
+
}
|
|
10892
10952
|
log.warn(`release failure \u2014 filed ${label}`, { step: "release" });
|
|
10893
10953
|
return { kind: "filed", issueId: label };
|
|
10894
10954
|
} catch (e) {
|
package/package.json
CHANGED