@tablation/crew 0.6.1 → 0.7.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/dist/cli.js
CHANGED
|
@@ -824,6 +824,7 @@ function parseResolved(raw, m, where) {
|
|
|
824
824
|
operator: m.req(raw.operator, `${where}.resolved.operator`),
|
|
825
825
|
holds: holds2,
|
|
826
826
|
contract: raw.contract ?? void 0,
|
|
827
|
+
...typeof raw.releaseSeat === "string" && raw.releaseSeat ? { releaseSeat: raw.releaseSeat } : {},
|
|
827
828
|
...Object.keys(agentPersonas).length > 0 ? { agentPersonas } : {},
|
|
828
829
|
...Object.keys(agentSkills).length > 0 ? { agentSkills } : {}
|
|
829
830
|
};
|
|
@@ -1083,6 +1084,10 @@ async function hydrateApiKeys(routes, getStore = getSessionStore) {
|
|
|
1083
1084
|
}
|
|
1084
1085
|
}
|
|
1085
1086
|
}
|
|
1087
|
+
function releaseSeat(rt) {
|
|
1088
|
+
const r = rt.resolved;
|
|
1089
|
+
return r?.releaseSeat ?? r?.seats.dev ?? r?.seats.qa ?? "";
|
|
1090
|
+
}
|
|
1086
1091
|
function configuredMembers(rt) {
|
|
1087
1092
|
const r = rt.resolved;
|
|
1088
1093
|
if (!r) throw new ConfigError(`route "${rt.route}" has no resolved ids \u2014 run \`crew connect\``);
|
|
@@ -1675,7 +1680,9 @@ var DEFAULT_CONTRACT = {
|
|
|
1675
1680
|
updatedAt: "updated_at",
|
|
1676
1681
|
needsDesign: "needs_design",
|
|
1677
1682
|
reportType: "report_type",
|
|
1678
|
-
epic: "epic_id"
|
|
1683
|
+
epic: "epic_id",
|
|
1684
|
+
author: "filed_by_id",
|
|
1685
|
+
ship: "ship_id"
|
|
1679
1686
|
},
|
|
1680
1687
|
comments: {
|
|
1681
1688
|
parent: "ticket_id",
|
|
@@ -1683,7 +1690,8 @@ var DEFAULT_CONTRACT = {
|
|
|
1683
1690
|
author: "team_member_id",
|
|
1684
1691
|
kind: "kind",
|
|
1685
1692
|
eventKind: "event",
|
|
1686
|
-
createdAt: "created_at"
|
|
1693
|
+
createdAt: "created_at",
|
|
1694
|
+
ship: "ship_id"
|
|
1687
1695
|
},
|
|
1688
1696
|
statuses: {
|
|
1689
1697
|
open: ["new", "accepted", "blocked", "in_progress", "needs_info", "fixed", "qa", "verified"],
|
|
@@ -2471,6 +2479,12 @@ var Tracker = class {
|
|
|
2471
2479
|
client;
|
|
2472
2480
|
models;
|
|
2473
2481
|
userAgent;
|
|
2482
|
+
shipName;
|
|
2483
|
+
/**
|
|
2484
|
+
* The once-per-process authorship probe (CREW-1371), memoised as a promise
|
|
2485
|
+
* so concurrent first writes share one round of field-metadata reads.
|
|
2486
|
+
*/
|
|
2487
|
+
authorship;
|
|
2474
2488
|
stateDir;
|
|
2475
2489
|
constructor(route, ship) {
|
|
2476
2490
|
this.route = route;
|
|
@@ -2482,6 +2496,7 @@ var Tracker = class {
|
|
|
2482
2496
|
this.models = route.resolved.models;
|
|
2483
2497
|
this.contract = resolveContract(route.contract);
|
|
2484
2498
|
this.userAgent = ship.userAgent;
|
|
2499
|
+
this.shipName = ship.name;
|
|
2485
2500
|
this.stateDir = ship.stateDir;
|
|
2486
2501
|
this.client = new TablationClient({
|
|
2487
2502
|
baseUrl: `${route.baseUrl}/api`,
|
|
@@ -2834,13 +2849,60 @@ var Tracker = class {
|
|
|
2834
2849
|
* someone else" wake signal.
|
|
2835
2850
|
*/
|
|
2836
2851
|
async postEvent(ticketId, body, memberId) {
|
|
2852
|
+
const auth = await this.authorshipProbe();
|
|
2837
2853
|
await this.client.records.create(this.models.comments, {
|
|
2838
2854
|
ticket_id: ticketId,
|
|
2839
2855
|
body,
|
|
2840
2856
|
team_member_id: memberId,
|
|
2841
|
-
kind: "event"
|
|
2857
|
+
kind: "event",
|
|
2858
|
+
...auth.commentShip && auth.shipId ? { [this.contract.comments.ship]: auth.shipId } : {}
|
|
2842
2859
|
});
|
|
2843
2860
|
}
|
|
2861
|
+
/**
|
|
2862
|
+
* The authorship probe (CREW-1371), read once per process: which optional
|
|
2863
|
+
* `filed_by_id`/`ship_id` columns exist on Issues and Comments, and this
|
|
2864
|
+
* ship's own Ships row id. Memoised as a promise so concurrent first writes
|
|
2865
|
+
* share one round of metadata reads. A table whose metadata cannot be read
|
|
2866
|
+
* reads as "column absent", the same way `connect.ts`'s `owner_id` probe does.
|
|
2867
|
+
*/
|
|
2868
|
+
authorshipProbe() {
|
|
2869
|
+
this.authorship ??= this.probeAuthorship();
|
|
2870
|
+
return this.authorship;
|
|
2871
|
+
}
|
|
2872
|
+
async probeAuthorship() {
|
|
2873
|
+
const [issueFields, commentFields, shipRow] = await Promise.all([
|
|
2874
|
+
this.columnNames(this.models.issues),
|
|
2875
|
+
this.columnNames(this.models.comments),
|
|
2876
|
+
// No name (a caller that only beats or patches, never stamps) means no row to find.
|
|
2877
|
+
this.shipName ? this.myShipRow(this.shipName) : Promise.resolve(void 0)
|
|
2878
|
+
]);
|
|
2879
|
+
return {
|
|
2880
|
+
issueAuthor: issueFields.has(this.contract.columns.author),
|
|
2881
|
+
issueShip: issueFields.has(this.contract.columns.ship),
|
|
2882
|
+
commentShip: commentFields.has(this.contract.comments.ship),
|
|
2883
|
+
shipId: shipRow?.id ?? null
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
async columnNames(modelId) {
|
|
2887
|
+
try {
|
|
2888
|
+
const model = await this.client.dataModels.get(modelId);
|
|
2889
|
+
return new Set(model.fields.map((f) => f.columnName));
|
|
2890
|
+
} catch {
|
|
2891
|
+
return /* @__PURE__ */ new Set();
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
/**
|
|
2895
|
+
* The authorship columns this workspace is missing, as `table.column` names
|
|
2896
|
+
* for `crew doctor` to report (CREW-1371). Empty on a fully updated install.
|
|
2897
|
+
*/
|
|
2898
|
+
async missingAuthorshipColumns() {
|
|
2899
|
+
const auth = await this.authorshipProbe();
|
|
2900
|
+
const missing = [];
|
|
2901
|
+
if (!auth.issueAuthor) missing.push(`Issues.${this.contract.columns.author}`);
|
|
2902
|
+
if (!auth.issueShip) missing.push(`Issues.${this.contract.columns.ship}`);
|
|
2903
|
+
if (!auth.commentShip) missing.push(`Comments.${this.contract.comments.ship}`);
|
|
2904
|
+
return missing;
|
|
2905
|
+
}
|
|
2844
2906
|
/**
|
|
2845
2907
|
* The public `/v/<slug>/r/<token>` link for one ticket (ISSUE-928) —
|
|
2846
2908
|
* fetch-or-create via `POST /views/:viewId/record-links`, so a repeated
|
|
@@ -2882,8 +2944,13 @@ var Tracker = class {
|
|
|
2882
2944
|
* not among them (`postEvent` above hardcodes its own fields the same
|
|
2883
2945
|
* way).
|
|
2884
2946
|
*/
|
|
2885
|
-
async fileTicket(fields) {
|
|
2886
|
-
|
|
2947
|
+
async fileTicket(fields, memberId) {
|
|
2948
|
+
const auth = await this.authorshipProbe();
|
|
2949
|
+
return this.client.records.create(this.models.issues, {
|
|
2950
|
+
...fields,
|
|
2951
|
+
...auth.issueAuthor && memberId ? { [this.contract.columns.author]: memberId } : {},
|
|
2952
|
+
...auth.issueShip && auth.shipId ? { [this.contract.columns.ship]: auth.shipId } : {}
|
|
2953
|
+
});
|
|
2887
2954
|
}
|
|
2888
2955
|
};
|
|
2889
2956
|
|
|
@@ -2933,7 +3000,8 @@ function rosterMarkdown(roster, meId) {
|
|
|
2933
3000
|
`You are **${selfName}**. Address your shipmates by name in ticket`,
|
|
2934
3001
|
"comments the way you would a colleague; add the role in parentheses only",
|
|
2935
3002
|
"where a reader would otherwise not know which seat you mean. Your own",
|
|
2936
|
-
"row id is what you write to `assignee_id` and `team_member_id
|
|
3003
|
+
"row id is what you write to `assignee_id` and `team_member_id`, and to",
|
|
3004
|
+
"`filed_by_id` on a ticket you file (where the Environment section names it).",
|
|
2937
3005
|
"",
|
|
2938
3006
|
"| seat | name | Crew row id |",
|
|
2939
3007
|
"|---|---|---|"
|
|
@@ -3390,20 +3458,36 @@ function attentionTransitions(previous, current) {
|
|
|
3390
3458
|
function displayKey2(t) {
|
|
3391
3459
|
return t.issue_tag ?? t.issue_id ?? "?";
|
|
3392
3460
|
}
|
|
3393
|
-
function author(c,
|
|
3461
|
+
function author(c, i) {
|
|
3462
|
+
const { me, roster } = i;
|
|
3394
3463
|
if (c.team_member_id === me) return "you";
|
|
3395
3464
|
if (c.team_member_id === null || c.team_member_id === void 0) {
|
|
3396
3465
|
const byIdentity = memberByIdentity(roster, c.created_by_id);
|
|
3397
3466
|
if (byIdentity) return byIdentity.id === me ? "you" : crewLabel(byIdentity) ?? byIdentity.name;
|
|
3398
3467
|
return `${c.reporter_name ?? "anon"} (no identity)`;
|
|
3399
3468
|
}
|
|
3400
|
-
return crewLabel(roster.get(c.team_member_id)) ?? "someone off this ship";
|
|
3469
|
+
return (crewLabel(roster.get(c.team_member_id)) ?? "someone off this ship") + shipSuffix(c, i);
|
|
3470
|
+
}
|
|
3471
|
+
function shipSuffix(c, i) {
|
|
3472
|
+
if (!c.ship_id || c.ship_id === i.myShipId) return "";
|
|
3473
|
+
const name = i.ships?.find((s) => s.id === c.ship_id)?.name;
|
|
3474
|
+
return name ? ` (${name})` : "";
|
|
3475
|
+
}
|
|
3476
|
+
function filedBy(t, i) {
|
|
3477
|
+
const member = (t.filed_by_id ? i.roster.get(t.filed_by_id) : void 0) ?? memberByIdentity(i.roster, t.created_by_id);
|
|
3478
|
+
if (member) {
|
|
3479
|
+
if (member.id === i.me) return "you";
|
|
3480
|
+
const label = crewLabel(member) ?? member.name;
|
|
3481
|
+
const ship = t.ship_id && t.ship_id !== i.myShipId ? i.ships?.find((s) => s.id === t.ship_id)?.name : void 0;
|
|
3482
|
+
return ship ? `${label} (${ship})` : label;
|
|
3483
|
+
}
|
|
3484
|
+
return t.reporter_name ? `${t.reporter_name} (unverified)` : "\u2014";
|
|
3401
3485
|
}
|
|
3402
3486
|
function lastComment(i, ticketId) {
|
|
3403
3487
|
const forTicket = i.comments.filter((c) => c.ticket_id === ticketId).sort((a, b) => a.created_at < b.created_at ? -1 : a.created_at > b.created_at ? 1 : 0);
|
|
3404
3488
|
const last = forTicket[forTicket.length - 1];
|
|
3405
3489
|
if (!last) return "\u2014";
|
|
3406
|
-
return `${last.created_at.slice(0, 16)}Z ${author(last, i
|
|
3490
|
+
return `${last.created_at.slice(0, 16)}Z ${author(last, i)}`;
|
|
3407
3491
|
}
|
|
3408
3492
|
function newFromOthers(i, ticketId) {
|
|
3409
3493
|
return i.comments.filter(
|
|
@@ -3435,14 +3519,14 @@ ${sorted(ts).map(row).join("\n")}
|
|
|
3435
3519
|
`;
|
|
3436
3520
|
}
|
|
3437
3521
|
function buildingDigest(i) {
|
|
3438
|
-
const header = "| ticket | repo | branch | worktree | status | assignee | sev | pri | eff | updated | last comment | new since last poll |\n
|
|
3522
|
+
const header = "| ticket | repo | branch | worktree | status | assignee | filed by | sev | pri | eff | updated | last comment | new since last poll |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|";
|
|
3439
3523
|
const row = (t) => {
|
|
3440
3524
|
const n = newFromOthers(i, t.id);
|
|
3441
3525
|
const branch = i.branchFor ? i.branchFor(t) : "";
|
|
3442
3526
|
const worktree = i.worktreeFor ? i.worktreeFor(t) : "";
|
|
3443
3527
|
const dir = i.dirFor ? i.dirFor(t) : null;
|
|
3444
3528
|
const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
|
|
3445
|
-
return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${t.status} | ${who(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
3529
|
+
return `| ${displayKey2(t)} | ${repo} | ${branch} | ${worktree} | ${t.status} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
3446
3530
|
};
|
|
3447
3531
|
const blockedRow = (t) => `| ${displayKey2(t)} | ${t.status} | ${who(t, i)} | p${effectivePriority(t)} | ${blockers(t, i)} |`;
|
|
3448
3532
|
const blockedTable = (ts) => ts.length === 0 ? "\n_None._\n" : `
|
|
@@ -3474,7 +3558,7 @@ ${sorted(ts).map(blockedRow).join("\n")}
|
|
|
3474
3558
|
]);
|
|
3475
3559
|
}
|
|
3476
3560
|
function qaDigest(i) {
|
|
3477
|
-
const header = "| ticket | repo | status | built by | assignee | sev | pri | eff | branch | worktree | updated | last comment | new since last poll |\n
|
|
3561
|
+
const header = "| ticket | repo | status | built by | assignee | filed by | sev | pri | eff | branch | worktree | updated | last comment | new since last poll |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|";
|
|
3478
3562
|
const branchCell = (t) => {
|
|
3479
3563
|
if (i.dirFor && i.dirFor(t) === null) return "\u2014";
|
|
3480
3564
|
if (!i.existingBranchFor) return "";
|
|
@@ -3489,7 +3573,7 @@ function qaDigest(i) {
|
|
|
3489
3573
|
const n = newFromOthers(i, t.id);
|
|
3490
3574
|
const dir = i.dirFor ? i.dirFor(t) : null;
|
|
3491
3575
|
const repo = i.dirFor ? dir ?? "**NO CHECKOUT**" : "";
|
|
3492
|
-
return `| ${displayKey2(t)} | ${repo} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
3576
|
+
return `| ${displayKey2(t)} | ${repo} | ${t.status} | ${builtBy(t)} | ${who(t, i)} | ${filedBy(t, i)} | ${t.severity ?? "\u2014"} | ${t.priority ?? "\u2014"} | p${effectivePriority(t)} | ${branchCell(t)} | ${worktreeCell(t)} | ${t.updated_at.slice(0, 16)}Z | ${lastComment(i, t.id)} | ${n > 0 ? `**${n} new**` : "\u2014"} |`;
|
|
3493
3577
|
};
|
|
3494
3578
|
return stream([
|
|
3495
3579
|
"## Current queue \u2014 built for you by the poll\n",
|
|
@@ -3952,11 +4036,12 @@ async function decideCycle(o) {
|
|
|
3952
4036
|
const { route, ship, state, emit } = o;
|
|
3953
4037
|
const tracker = new Tracker(route, ship);
|
|
3954
4038
|
emit.enter("poll");
|
|
3955
|
-
const [tickets, comments, crewRows, epicRows] = await Promise.all([
|
|
4039
|
+
const [tickets, comments, crewRows, epicRows, ships] = await Promise.all([
|
|
3956
4040
|
tracker.openTickets(),
|
|
3957
4041
|
tracker.comments(200),
|
|
3958
4042
|
tracker.crewRows(),
|
|
3959
|
-
tracker.epicRows()
|
|
4043
|
+
tracker.epicRows(),
|
|
4044
|
+
tracker.shipRows()
|
|
3960
4045
|
]);
|
|
3961
4046
|
emit.emit(`${tickets.length} open ticket(s), ${comments.length} comment(s)`, {
|
|
3962
4047
|
data: { tickets: tickets.length, comments: comments.length }
|
|
@@ -4069,6 +4154,7 @@ async function decideCycle(o) {
|
|
|
4069
4154
|
tickets,
|
|
4070
4155
|
comments,
|
|
4071
4156
|
roster,
|
|
4157
|
+
ships,
|
|
4072
4158
|
blocked,
|
|
4073
4159
|
info,
|
|
4074
4160
|
sweep,
|
|
@@ -4147,6 +4233,10 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
4147
4233
|
comments: d.comments,
|
|
4148
4234
|
me,
|
|
4149
4235
|
roster: d.roster,
|
|
4236
|
+
// CREW-1371: a comment from another ship is labelled with that ship's
|
|
4237
|
+
// name, so two ships under one API key are distinguishable on the queue.
|
|
4238
|
+
ships: d.ships,
|
|
4239
|
+
myShipId: d.ships.find((s) => (s.name ?? "").trim() === o.ship.name.trim())?.id ?? null,
|
|
4150
4240
|
watermark: d.watermark,
|
|
4151
4241
|
blocked: d.blocked,
|
|
4152
4242
|
blockerInfo: d.info
|
|
@@ -4422,6 +4512,14 @@ import { createHash as createHash2 } from "crypto";
|
|
|
4422
4512
|
// src/environment.ts
|
|
4423
4513
|
import { basename as basename4 } from "path";
|
|
4424
4514
|
var API_KEY_VAR = "CREW_API_KEY";
|
|
4515
|
+
function authorshipBullets(a, contract) {
|
|
4516
|
+
if (!a) return [];
|
|
4517
|
+
return [
|
|
4518
|
+
...a.issueAuthor ? [`- \`${contract.columns.author}\`: your own Crew row id, from "Your crew" above \u2014 who filed it`] : [],
|
|
4519
|
+
...a.issueShip && a.shipId ? [`- \`${contract.columns.ship}\`: \`${a.shipId}\` \u2014 this ship, where it was filed from`] : [],
|
|
4520
|
+
...a.commentShip && a.shipId ? [`- \`${contract.comments.ship}\` on every comment you post: \`${a.shipId}\` \u2014 this ship`] : []
|
|
4521
|
+
];
|
|
4522
|
+
}
|
|
4425
4523
|
var HOOK_PURPOSE = [
|
|
4426
4524
|
["setup", "make a fresh worktree usable \u2014 dependencies, generated clients"],
|
|
4427
4525
|
["isolate", "point this worktree at state of its own; prints KEY=value overrides to export"],
|
|
@@ -4628,6 +4726,7 @@ function renderEnvironment(i) {
|
|
|
4628
4726
|
...only?.id ? [`- \`${contract.columns.repo}\`: \`${only.id}\` (this repository)`] : repos.filter((r) => r.id).map(
|
|
4629
4727
|
(r) => `- \`${contract.columns.repo}\`: \`${r.id}\` \u2014 when the new ticket belongs to ${r.name}`
|
|
4630
4728
|
),
|
|
4729
|
+
...authorshipBullets(i.authorship, contract),
|
|
4631
4730
|
"",
|
|
4632
4731
|
...i.sourceTicket ? [
|
|
4633
4732
|
`Name ${i.sourceTicket} \u2014 the ticket you were working when you noticed this \u2014 in the new`,
|
|
@@ -6128,7 +6227,7 @@ Still failing: ${n.detail ?? n.headline}`, memberId);
|
|
|
6128
6227
|
report_type: "bug",
|
|
6129
6228
|
severity: "s2",
|
|
6130
6229
|
reporter_name: "Crew release"
|
|
6131
|
-
});
|
|
6230
|
+
}, memberId);
|
|
6132
6231
|
const label = created.issue_id ? displayKey(created) : created.id;
|
|
6133
6232
|
log.warn(`release failure \u2014 filed ${label}`, { step: "release" });
|
|
6134
6233
|
return { kind: "filed", issueId: label };
|
|
@@ -7158,7 +7257,19 @@ async function discover(o) {
|
|
|
7158
7257
|
}
|
|
7159
7258
|
const pairId = seatFor(scoped, "pair") || (shipRowId ? seatFor(unclaimed, "pair") : void 0);
|
|
7160
7259
|
if (pairId) out.seats.pair = pairId;
|
|
7161
|
-
|
|
7260
|
+
if (shipRowId) {
|
|
7261
|
+
const releaseId = seatFor(scoped, "release");
|
|
7262
|
+
if (releaseId) out.releaseSeat = releaseId;
|
|
7263
|
+
else {
|
|
7264
|
+
const created = await post2(o, `/data-models/${out.models.crew}/records`, {
|
|
7265
|
+
name: "Release agent",
|
|
7266
|
+
ship_id: shipRowId
|
|
7267
|
+
});
|
|
7268
|
+
out.releaseSeat = created.id;
|
|
7269
|
+
provisioning.push("created Crew row for the release seat");
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7272
|
+
const seatIds = /* @__PURE__ */ new Set([...Object.values(out.seats), ...out.releaseSeat ? [out.releaseSeat] : []]);
|
|
7162
7273
|
out.holds = rows.filter((r) => !seatIds.has(r.id)).map((r) => ({ id: r.id, name: r.name ?? "" }));
|
|
7163
7274
|
if (out.holds.length === 0) {
|
|
7164
7275
|
problems.push("no non-seat Crew rows \u2014 which row is the operator?");
|
|
@@ -8732,7 +8843,8 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
8732
8843
|
}
|
|
8733
8844
|
}
|
|
8734
8845
|
async function buildEnvironment(route, ship, ticket) {
|
|
8735
|
-
const
|
|
8846
|
+
const tracker = new Tracker(route, ship);
|
|
8847
|
+
const contract = tracker.contract;
|
|
8736
8848
|
const repos = reposOf(route).map((t) => {
|
|
8737
8849
|
const id = repoIdForName(route, t.name);
|
|
8738
8850
|
const o = route.repoOverrides[t.name];
|
|
@@ -8747,7 +8859,8 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
8747
8859
|
}, t.dir)
|
|
8748
8860
|
};
|
|
8749
8861
|
});
|
|
8750
|
-
|
|
8862
|
+
const authorship = await tracker.authorshipProbe().catch(() => null);
|
|
8863
|
+
return renderEnvironment({ route, userAgent: ship.userAgent, repos, contract, sourceTicket: ticket, authorship });
|
|
8751
8864
|
}
|
|
8752
8865
|
|
|
8753
8866
|
// src/cli.ts
|
|
@@ -9336,13 +9449,15 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9336
9449
|
return (await resolvedRepos(c)).map((r) => `${r.name} requires ${r.config.platform}`).join(", ");
|
|
9337
9450
|
}
|
|
9338
9451
|
async function environmentFor(c, ticket) {
|
|
9339
|
-
const
|
|
9452
|
+
const tracker = new Tracker(c, cfg.ship);
|
|
9453
|
+
const contract = tracker.contract;
|
|
9340
9454
|
return renderEnvironment({
|
|
9341
9455
|
route: c,
|
|
9342
9456
|
userAgent: cfg.ship.userAgent,
|
|
9343
9457
|
repos: await resolvedRepos(c),
|
|
9344
9458
|
contract,
|
|
9345
|
-
sourceTicket: ticket
|
|
9459
|
+
sourceTicket: ticket,
|
|
9460
|
+
authorship: await tracker.authorshipProbe().catch(() => null)
|
|
9346
9461
|
});
|
|
9347
9462
|
}
|
|
9348
9463
|
const unplaceableReported = /* @__PURE__ */ new Set();
|
|
@@ -9444,7 +9559,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9444
9559
|
try {
|
|
9445
9560
|
const g = outcome.gateRed;
|
|
9446
9561
|
const comments = await tracker.comments();
|
|
9447
|
-
const seat = c
|
|
9562
|
+
const seat = releaseSeat(c);
|
|
9448
9563
|
for (const t of gateRedTickets(outcome.merged, outcome.decision.merges)) {
|
|
9449
9564
|
if (alreadyReported(t, g, comments)) continue;
|
|
9450
9565
|
await tracker.postEvent(t.id, gateRedComment(g), seat);
|
|
@@ -9462,7 +9577,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9462
9577
|
if (news) {
|
|
9463
9578
|
const notified = await notify(c, cfg.ship, news, remit, dryRun);
|
|
9464
9579
|
if (!notified && news.level === "fail") {
|
|
9465
|
-
const memberId = c
|
|
9580
|
+
const memberId = releaseSeat(c);
|
|
9466
9581
|
await applyFailureAlert(tracker, news, scope, all, memberId, remit, dryRun).catch((e) => {
|
|
9467
9582
|
remit.warn(`could not file/update a failure ticket: ${e.message}`, { step: "release" });
|
|
9468
9583
|
});
|
|
@@ -9470,7 +9585,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
9470
9585
|
}
|
|
9471
9586
|
if (outcome.conflicts?.length || outcome.unbuildable?.length) {
|
|
9472
9587
|
const comments = await tracker.comments();
|
|
9473
|
-
const seat = c
|
|
9588
|
+
const seat = releaseSeat(c);
|
|
9474
9589
|
for (const f of outcome.conflicts ?? []) {
|
|
9475
9590
|
try {
|
|
9476
9591
|
const bounce = planConflictBounce(
|
|
@@ -10616,6 +10731,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
10616
10731
|
models: found.models,
|
|
10617
10732
|
seats: found.seats,
|
|
10618
10733
|
holds: found.holds.map((h) => ({ id: h.id, role: h.name })),
|
|
10734
|
+
...found.releaseSeat ? { releaseSeat: found.releaseSeat } : {},
|
|
10619
10735
|
...operator ? { operator } : {},
|
|
10620
10736
|
...contract ? { contract } : {},
|
|
10621
10737
|
...reviewedStatuses.length > 0 ? { reviewedStatuses } : {},
|
|
@@ -11264,6 +11380,14 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
11264
11380
|
`);
|
|
11265
11381
|
}
|
|
11266
11382
|
}
|
|
11383
|
+
const missingAuthorship = await tracker.missingAuthorshipColumns();
|
|
11384
|
+
process.stdout.write(
|
|
11385
|
+
missingAuthorship.length === 0 ? "authorship: filed_by_id, ship_id present\n" : `authorship: missing ${missingAuthorship.join(", ")} \u2014 update the Issues template
|
|
11386
|
+
`
|
|
11387
|
+
);
|
|
11388
|
+
process.stdout.write(
|
|
11389
|
+
route.resolved?.releaseSeat ? "release seat: this ship has its own Release Crew row\n" : "release seat: none \u2014 release comments are credited to the dev seat; run `crew connect` once the Crew table has this ship's row\n"
|
|
11390
|
+
);
|
|
11267
11391
|
const [ships, crewRows] = await Promise.all([tracker.shipRows(), tracker.crewRows()]);
|
|
11268
11392
|
if (ships.length === 0) {
|
|
11269
11393
|
process.stdout.write("ship record: this workspace has no Ships table (fine)\n");
|
package/package.json
CHANGED
|
@@ -619,6 +619,11 @@ filed without them has nowhere to route to — it sits unclaimed by any
|
|
|
619
619
|
per-repo queue until a person notices and fixes it by hand, however
|
|
620
620
|
urgent its priority.
|
|
621
621
|
|
|
622
|
+
If that block also lists `filed_by_id` (your own Crew row) or `ship_id` (the
|
|
623
|
+
ship you are running on), set those too, and `ship_id` on every comment you
|
|
624
|
+
post. They record who filed the ticket and from where. The block lists a column
|
|
625
|
+
only when this workspace has it, so a column it does not name is not yours to set.
|
|
626
|
+
|
|
622
627
|
Name the ticket you were working when you noticed it in the new ticket's
|
|
623
628
|
description. Only put it in the new ticket's `blocked by` column if your
|
|
624
629
|
own ticket genuinely cannot proceed without the new one being fixed
|
|
@@ -600,6 +600,11 @@ filed without them has nowhere to route to — it sits unclaimed by any
|
|
|
600
600
|
per-repo queue until a person notices and fixes it by hand, however
|
|
601
601
|
urgent its priority.
|
|
602
602
|
|
|
603
|
+
If that block also lists `filed_by_id` (your own Crew row) or `ship_id` (the
|
|
604
|
+
ship you are running on), set those too, and `ship_id` on every comment you
|
|
605
|
+
post. They record who filed the ticket and from where. The block lists a column
|
|
606
|
+
only when this workspace has it, so a column it does not name is not yours to set.
|
|
607
|
+
|
|
603
608
|
Name the ticket you were working when you noticed it in the new ticket's
|
|
604
609
|
description. Only put it in the new ticket's `blocked by` column if your
|
|
605
610
|
own ticket genuinely cannot proceed without the new one being fixed
|