@mutmutco/codex-plugin 4.5.87 → 4.5.89
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +168 -56
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +11 -3
package/cli/dist/main.cjs
CHANGED
|
@@ -7632,21 +7632,15 @@ function isHarbourUnmanaged(yamlText) {
|
|
|
7632
7632
|
function strayCronDrifts(workflows) {
|
|
7633
7633
|
return workflows.filter((w) => !isHarbourUnmanaged(w.yamlText)).filter((w) => workflowTriggersSchedule(w.yamlText)).map((w) => strayCronDrift(w.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
7634
7634
|
}
|
|
7635
|
-
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["codex-agent"]);
|
|
7636
|
-
function isHarbourLlmLauncher(executor) {
|
|
7637
|
-
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
7638
|
-
}
|
|
7639
7635
|
function unlauncheredLlmDrift(entry) {
|
|
7640
7636
|
if (entry.llm !== "yes") return null;
|
|
7641
|
-
if (isHarbourLlmLauncher(entry.executor)) return null;
|
|
7642
7637
|
if (entry.executor === "aws-scheduler \u2192 mmi-hub-schedule-dispatcher") return null;
|
|
7643
|
-
const allowed = [...HARBOUR_LLM_LAUNCHERS].join(", ");
|
|
7644
7638
|
return {
|
|
7645
7639
|
class: "unlaunchered-llm",
|
|
7646
7640
|
name: entry.name,
|
|
7647
7641
|
executor: entry.executor,
|
|
7648
|
-
detail:
|
|
7649
|
-
remedy:
|
|
7642
|
+
detail: "llm: yes but no scheduled-LLM executor is supported (Cursor retired, #7779; Codex executor retired, #7821)",
|
|
7643
|
+
remedy: "retire the lane or set it to llm: no until the harbour redesign (#7821; docs/schedules.md rule 3)"
|
|
7650
7644
|
};
|
|
7651
7645
|
}
|
|
7652
7646
|
function unlauncheredLlmDrifts(entries) {
|
|
@@ -7718,8 +7712,6 @@ var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor"
|
|
|
7718
7712
|
var SCHEDULE_EXECUTORS = /* @__PURE__ */ new Set([
|
|
7719
7713
|
"github-actions",
|
|
7720
7714
|
// deterministic workflow — clocked by the harbour, fired via workflow_dispatch
|
|
7721
|
-
"codex-agent",
|
|
7722
|
-
// LLM lane — the harbour's ONLY scheduled-LLM launcher (docs/schedules.md rule 3, #7780)
|
|
7723
7715
|
"lambda",
|
|
7724
7716
|
// fleet function row — requires the `# target: mmi-fleet-*` header (#3205/#3246)
|
|
7725
7717
|
"box systemd",
|
|
@@ -7800,8 +7792,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7800
7792
|
if (llmFromHeader(yamlText) === "unknown") {
|
|
7801
7793
|
throw new Error(`schedules register: ${expectedId} header \`llm: ${h.llm}\` does not classify (yes / no / embeddings) \u2014 an unreadable declaration would register a row whose LLM column lies.`);
|
|
7802
7794
|
}
|
|
7803
|
-
if (llmFromHeader(yamlText) === "yes"
|
|
7804
|
-
throw new Error(`schedules register: ${expectedId} header \`llm: yes\`
|
|
7795
|
+
if (llmFromHeader(yamlText) === "yes") {
|
|
7796
|
+
throw new Error(`schedules register: ${expectedId} header \`llm: yes\` is refused \u2014 no scheduled-LLM executor is supported: Cursor (\`cursor-agent\`) was retired (#7779) and the Codex executor was retired (#7821). Set \`llm: no\` or retire the lane until the harbour redesign.`);
|
|
7805
7797
|
}
|
|
7806
7798
|
const target = header.target;
|
|
7807
7799
|
if (target !== void 0 && h.executor !== "lambda") {
|
|
@@ -7811,7 +7803,7 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7811
7803
|
throw new Error(`schedules register: ${expectedId} header \`target: ${target}\` must be a bare lambda function name in the reserved fleet namespace \`mmi-fleet-*\` (allowed after the prefix: A-Z a-z 0-9 _ -; total max 140). The dispatcher can only invoke mmi-fleet-* functions.`);
|
|
7812
7804
|
}
|
|
7813
7805
|
if (header.model !== void 0) {
|
|
7814
|
-
throw new Error(`schedules register: ${expectedId} header \`model: ${header.model}\` is refused \u2014
|
|
7806
|
+
throw new Error(`schedules register: ${expectedId} header \`model: ${header.model}\` is refused \u2014 a model role key is valid only on an LLM lane, and no scheduled-LLM executor is supported (Cursor retired #7779; Codex executor retired #7821). Remove the line.`);
|
|
7815
7807
|
}
|
|
7816
7808
|
return {
|
|
7817
7809
|
id: expectedId,
|
|
@@ -12396,6 +12388,7 @@ function renderBoardItem(item) {
|
|
|
12396
12388
|
if (claim) {
|
|
12397
12389
|
const who = `${claim.holder}, ${formatClaimAge(claim.ageMs)} ago`;
|
|
12398
12390
|
lines2.push(claim.currentlyClaimed ? `Claimed by: ${who}` : `Last claimed by: ${who} (not currently claimed)`);
|
|
12391
|
+
if (claim.stale) lines2.push(`Claim stamp: STALE, idle ${formatClaimAge(claim.idleMs)}; any live session may claim it`);
|
|
12399
12392
|
}
|
|
12400
12393
|
lines2.push(
|
|
12401
12394
|
`Type: ${item.type ?? "item"}${item.labels.length ? ` \xB7 ${item.labels.join(", ")}` : ""}`,
|
|
@@ -12696,6 +12689,10 @@ async function readBoard(options, deps = {}) {
|
|
|
12696
12689
|
if (options.includeBundleDetails || options.includeAllBodies) {
|
|
12697
12690
|
await attachBundleDetails(report, client, options.allowPartial ?? false, { all: options.includeAllBodies });
|
|
12698
12691
|
}
|
|
12692
|
+
const readComments = claimMarkerCommentsReader(client);
|
|
12693
|
+
for (const scope of ["primary", "secondary"]) {
|
|
12694
|
+
await markStaleClaimStamps(report[scope], readComments, { pullRequests, writable: writableOrUnknown(writable) });
|
|
12695
|
+
}
|
|
12699
12696
|
for (const scope of ["primary", "secondary"]) {
|
|
12700
12697
|
const filtered = await filterDependencyBlockedClaimables(report[scope].claimable, client, {
|
|
12701
12698
|
requireBundledBody: Boolean(options.includeBundleDetails)
|
|
@@ -12704,7 +12701,7 @@ async function readBoard(options, deps = {}) {
|
|
|
12704
12701
|
report.warnings.push(...filtered.warnings);
|
|
12705
12702
|
}
|
|
12706
12703
|
for (const scope of ["primary", "secondary"]) {
|
|
12707
|
-
const demoted = await demoteLiveClaimedClaimables(report[scope], client);
|
|
12704
|
+
const demoted = await demoteLiveClaimedClaimables(report[scope], client, void 0, { fetchComments: readComments });
|
|
12708
12705
|
report.warnings.push(...demoted.warnings);
|
|
12709
12706
|
report.partial = report.partial || demoted.partial;
|
|
12710
12707
|
}
|
|
@@ -12994,7 +12991,7 @@ function renderClaimable(lines2, items) {
|
|
|
12994
12991
|
function renderTaken(lines2, items) {
|
|
12995
12992
|
if (!items.length) return;
|
|
12996
12993
|
lines2.push("Taken");
|
|
12997
|
-
for (const item of items) lines2.push(` ${item.ref} \xB7 ${item.status} \xB7 @${item.assignees.join(", @")}`);
|
|
12994
|
+
for (const item of items) lines2.push(` ${item.ref} \xB7 ${item.status} \xB7 @${item.assignees.join(", @")}${renderClaimLane(item)}`);
|
|
12998
12995
|
}
|
|
12999
12996
|
function renderUnowned(lines2, items) {
|
|
13000
12997
|
if (!items.length) return;
|
|
@@ -13005,7 +13002,11 @@ function renderTitledItem(item) {
|
|
|
13005
13002
|
const pri = item.priority ? ` \xB7 ${item.priority}` : "";
|
|
13006
13003
|
const loop = item.loopState ? ` \xB7 loop:${item.loopState.state}` : "";
|
|
13007
13004
|
const stale = item.loopStaleness?.stale ? ` \xB7 stale:${formatClaimAge(item.loopStaleness.idleMs)}` : "";
|
|
13008
|
-
return `${item.ref} - [${item.type ?? "item"}]${pri}${loop}${stale} ${item.title}`;
|
|
13005
|
+
return `${item.ref} - [${item.type ?? "item"}]${pri}${loop}${stale}${renderClaimLane(item)} ${item.title}`;
|
|
13006
|
+
}
|
|
13007
|
+
function renderClaimLane(item) {
|
|
13008
|
+
if (!item.claim) return "";
|
|
13009
|
+
return item.claim.stale ? ` \xB7 stale:${formatClaimAge(item.claim.idleMs)} (${item.claim.holder})` : ` \xB7 lane ${item.claim.holder}`;
|
|
13009
13010
|
}
|
|
13010
13011
|
function hasItems(buckets) {
|
|
13011
13012
|
return buckets.userOwned.length > 0 || buckets.claimable.length > 0 || buckets.taken.length > 0 || buckets.unownedInFlight.length > 0;
|
|
@@ -13130,6 +13131,7 @@ function deriveLoopStaleness(item, loopState, now = Date.now()) {
|
|
|
13130
13131
|
}
|
|
13131
13132
|
var CLAIM_MARKER_PATTERN = /<!--\s*mmi-claim:\s*(\{[\s\S]*?\})\s*-->/g;
|
|
13132
13133
|
var CLAIM_MARKER_LIVE_MS = 24 * 60 * 6e4;
|
|
13134
|
+
var CLAIM_STAMP_IDLE_MS = 4 * 60 * 6e4;
|
|
13133
13135
|
function isValidClaimMarker(parsed) {
|
|
13134
13136
|
if (!parsed || typeof parsed !== "object") return false;
|
|
13135
13137
|
const marker = parsed;
|
|
@@ -13166,34 +13168,86 @@ function describeClaimMarker(marker) {
|
|
|
13166
13168
|
}
|
|
13167
13169
|
function boardItemClaim(item) {
|
|
13168
13170
|
const marker = item.details ? latestClaimMarker(item.details.comments) : void 0;
|
|
13169
|
-
|
|
13171
|
+
return marker ? claimFromMarker(item, marker) : void 0;
|
|
13172
|
+
}
|
|
13173
|
+
function claimStampStaleness(marker, evidence = {}, now = Date.now()) {
|
|
13174
|
+
const times = [marker.ts, ...evidence.activityAt ?? []].map((t) => t ? Date.parse(t) : Number.NaN);
|
|
13175
|
+
const idleMs = now - Math.max(...times.filter(Number.isFinite));
|
|
13176
|
+
return { stale: !evidence.linkedWork && idleMs >= CLAIM_STAMP_IDLE_MS, idleMs };
|
|
13177
|
+
}
|
|
13178
|
+
function workIsLive(at, now = Date.now()) {
|
|
13179
|
+
const idle = now - Date.parse(at ?? "");
|
|
13180
|
+
return !Number.isFinite(idle) || idle < CLAIM_STAMP_IDLE_MS;
|
|
13181
|
+
}
|
|
13182
|
+
function claimFromMarker(item, marker, linked = {}, now = Date.now()) {
|
|
13170
13183
|
return {
|
|
13171
13184
|
marker,
|
|
13172
13185
|
holder: describeClaimMarker(marker),
|
|
13173
|
-
ageMs:
|
|
13174
|
-
currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
|
|
13186
|
+
ageMs: now - Date.parse(marker.ts),
|
|
13187
|
+
currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress",
|
|
13188
|
+
...claimStampStaleness(marker, { activityAt: [item.updatedAt, ...linked.activityAt ?? []], linkedWork: linked.linkedWork }, now)
|
|
13175
13189
|
};
|
|
13176
13190
|
}
|
|
13177
|
-
function classifyClaimableMarker(comments, actor, now = Date.now()) {
|
|
13191
|
+
function classifyClaimableMarker(comments, actor, now = Date.now(), activityAt = []) {
|
|
13178
13192
|
const marker = comments ? latestClaimMarker(comments) : void 0;
|
|
13179
13193
|
if (!marker) return { verdict: "claimable" };
|
|
13180
13194
|
const ownership = laneOwnership(marker, actor);
|
|
13181
13195
|
if (ownership === "mine") return { verdict: "owned", holder: describeClaimMarker(marker) };
|
|
13182
|
-
|
|
13183
|
-
if (Number.isFinite(age) && age >= 0 && age < CLAIM_MARKER_LIVE_MS) {
|
|
13196
|
+
if (!claimStampStaleness(marker, { activityAt }, now).stale) {
|
|
13184
13197
|
return { verdict: "taken", holder: describeClaimMarker(marker) };
|
|
13185
13198
|
}
|
|
13186
13199
|
return { verdict: "claimable" };
|
|
13187
13200
|
}
|
|
13201
|
+
function claimMarkerCommentsReader(client) {
|
|
13202
|
+
const cache = /* @__PURE__ */ new Map();
|
|
13203
|
+
return (item) => {
|
|
13204
|
+
let read = cache.get(item);
|
|
13205
|
+
if (!read) {
|
|
13206
|
+
read = (async () => {
|
|
13207
|
+
if (item.detailsUnavailable) return void 0;
|
|
13208
|
+
if (item.details && !item.details.commentsUnread && !item.details.omittedComments) return item.details.comments;
|
|
13209
|
+
const comments = await client.restPaginate(`repos/${item.repository}/issues/${item.number}/comments`);
|
|
13210
|
+
return comments.map((comment) => ({ body: comment.body ?? "" }));
|
|
13211
|
+
})();
|
|
13212
|
+
cache.set(item, read);
|
|
13213
|
+
}
|
|
13214
|
+
return read;
|
|
13215
|
+
};
|
|
13216
|
+
}
|
|
13217
|
+
async function markStaleClaimStamps(scope, readComments, options = {}) {
|
|
13218
|
+
const actor = options.actor ?? describeSessionIdentity();
|
|
13219
|
+
const now = options.now ?? Date.now();
|
|
13220
|
+
const promoted = [];
|
|
13221
|
+
for (const bucket of ["userOwned", "claimable", "unownedInFlight", "taken"]) {
|
|
13222
|
+
const kept = [];
|
|
13223
|
+
for (const item of scope[bucket]) {
|
|
13224
|
+
kept.push(item);
|
|
13225
|
+
if (item.contentType !== "Issue") continue;
|
|
13226
|
+
const comments = await readComments(item).catch(() => void 0);
|
|
13227
|
+
const marker = comments ? latestClaimMarker(comments) : void 0;
|
|
13228
|
+
if (!marker) continue;
|
|
13229
|
+
const prs = (options.pullRequests ?? []).filter((pr) => pr.closesIssues.some((ref) => ref.toLowerCase() === item.ref.toLowerCase()));
|
|
13230
|
+
item.claim = claimFromMarker(item, marker, {
|
|
13231
|
+
activityAt: prs.map((pr) => pr.updatedAt),
|
|
13232
|
+
linkedWork: prs.some((pr) => !pr.updatedAt)
|
|
13233
|
+
}, now);
|
|
13234
|
+
const movable = bucket === "userOwned" || bucket === "unownedInFlight";
|
|
13235
|
+
if (movable && item.claim.stale && laneOwnership(marker, actor) !== "mine" && (!options.writable || options.writable.has(item.repository.toLowerCase()))) {
|
|
13236
|
+
kept.pop();
|
|
13237
|
+
promoted.push(item);
|
|
13238
|
+
}
|
|
13239
|
+
}
|
|
13240
|
+
scope[bucket] = kept;
|
|
13241
|
+
}
|
|
13242
|
+
if (promoted.length) {
|
|
13243
|
+
scope.claimable.push(...promoted);
|
|
13244
|
+
scope.claimable.sort(compareItems);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13188
13247
|
async function demoteLiveClaimedClaimables(scope, client, actor = describeSessionIdentity(), deps = {}) {
|
|
13189
13248
|
const warnings = [];
|
|
13190
13249
|
let partial = false;
|
|
13191
|
-
const fetchComments = deps.fetchComments ?? (
|
|
13192
|
-
if (item.detailsUnavailable) return void 0;
|
|
13193
|
-
if (item.details && !item.details.commentsUnread && !item.details.omittedComments) return item.details.comments;
|
|
13194
|
-
const comments = await client.restPaginate(`repos/${item.repository}/issues/${item.number}/comments`);
|
|
13195
|
-
return comments.map((comment) => ({ body: comment.body ?? "" }));
|
|
13196
|
-
});
|
|
13250
|
+
const fetchComments = deps.fetchComments ?? claimMarkerCommentsReader(client);
|
|
13197
13251
|
const stillClaimable = [];
|
|
13198
13252
|
const movedTaken = [];
|
|
13199
13253
|
const movedOwned = [];
|
|
@@ -13213,7 +13267,7 @@ async function demoteLiveClaimedClaimables(scope, client, actor = describeSessio
|
|
|
13213
13267
|
warnings.push(`${item.ref} left claimable: claim-marker evidence could not be read`);
|
|
13214
13268
|
continue;
|
|
13215
13269
|
}
|
|
13216
|
-
const verdict = classifyClaimableMarker(comments, actor, deps.now);
|
|
13270
|
+
const verdict = classifyClaimableMarker(comments, actor, deps.now, [item.updatedAt]);
|
|
13217
13271
|
if (verdict.verdict === "claimable") {
|
|
13218
13272
|
stillClaimable.push(item);
|
|
13219
13273
|
} else if (verdict.verdict === "owned") {
|
|
@@ -13308,6 +13362,12 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
13308
13362
|
const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
|
|
13309
13363
|
const rows = comments.map((comment) => ({ body: comment.body ?? "", login: comment.user?.login ?? void 0 }));
|
|
13310
13364
|
out.marker = latestClaimMarker(rows);
|
|
13365
|
+
for (let i = comments.length - 1; i >= 0; i -= 1) {
|
|
13366
|
+
if (comments[i].created_at && !latestClaimMarker([rows[i]])) {
|
|
13367
|
+
out.lastCommentAt = comments[i].created_at;
|
|
13368
|
+
break;
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13311
13371
|
if (out.marker) {
|
|
13312
13372
|
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
13313
13373
|
if (latestClaimMarker([rows[i]])) {
|
|
@@ -13334,7 +13394,10 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
13334
13394
|
if (from && from.toLowerCase() !== repo.toLowerCase()) return false;
|
|
13335
13395
|
return pullClosesIssue(source, number);
|
|
13336
13396
|
});
|
|
13337
|
-
if (pr?.source?.issue?.number)
|
|
13397
|
+
if (pr?.source?.issue?.number) {
|
|
13398
|
+
out.openPr = `open PR #${pr.source.issue.number} would close it on merge`;
|
|
13399
|
+
out.openPrAt = pr.source.issue.updated_at ?? void 0;
|
|
13400
|
+
}
|
|
13338
13401
|
} catch {
|
|
13339
13402
|
out.failed.push("timeline");
|
|
13340
13403
|
}
|
|
@@ -13345,11 +13408,24 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
13345
13408
|
} catch {
|
|
13346
13409
|
out.failed.push("branches");
|
|
13347
13410
|
}
|
|
13411
|
+
if (out.branch) {
|
|
13412
|
+
try {
|
|
13413
|
+
const head = await client.rest(
|
|
13414
|
+
"GET",
|
|
13415
|
+
`repos/${repo}/branches/${encodeURIComponent(out.branch)}`
|
|
13416
|
+
);
|
|
13417
|
+
out.branchAt = head?.commit?.commit?.committer?.date ?? void 0;
|
|
13418
|
+
} catch {
|
|
13419
|
+
}
|
|
13420
|
+
}
|
|
13348
13421
|
if (!out.branch) {
|
|
13349
13422
|
try {
|
|
13350
13423
|
const pulls = await fetchOpenPulls(repo);
|
|
13351
13424
|
const hit = pulls.find((pr) => pullClosesIssue(pr, number));
|
|
13352
|
-
if (hit?.head?.ref)
|
|
13425
|
+
if (hit?.head?.ref) {
|
|
13426
|
+
out.branch = `${hit.head.ref} (head of open PR #${hit.number} closing #${number})`;
|
|
13427
|
+
out.branchAt = hit.updated_at ?? void 0;
|
|
13428
|
+
}
|
|
13353
13429
|
} catch {
|
|
13354
13430
|
out.failed.push("pulls");
|
|
13355
13431
|
}
|
|
@@ -13360,13 +13436,24 @@ function liveEvidenceLines(evidence, repo) {
|
|
|
13360
13436
|
const live = [];
|
|
13361
13437
|
if (evidence.marker && evidence.sessionState === "live") {
|
|
13362
13438
|
live.push(`claim session ${describeClaimMarker(evidence.marker)} has recent local transcript activity`);
|
|
13363
|
-
} else if (evidence.marker && evidence.sessionState !== "dead"
|
|
13364
|
-
|
|
13439
|
+
} else if (evidence.marker && evidence.sessionState !== "dead") {
|
|
13440
|
+
const { stale, idleMs } = claimStampStaleness(evidence.marker, { activityAt: [evidence.lastCommentAt] });
|
|
13441
|
+
const marker = describeClaimMarker(evidence.marker);
|
|
13442
|
+
if (!stale) {
|
|
13443
|
+
live.push(evidence.lastCommentAt && Date.parse(evidence.lastCommentAt) > Date.parse(evidence.marker.ts) ? `claim marker from ${marker} is ${formatClaimAge(evidence.markerAgeMs)} old but the issue was commented ${formatClaimAge(idleMs)} ago (session not locally probeable)` : `claim marker from ${marker} is only ${formatClaimAge(evidence.markerAgeMs)} old (session not locally probeable)`);
|
|
13444
|
+
}
|
|
13365
13445
|
}
|
|
13366
|
-
if (evidence.openPr) live.push(evidence.openPr);
|
|
13367
|
-
if (evidence.branch) live.push(`live branch ${evidence.branch} on ${repo}`);
|
|
13446
|
+
if (evidence.openPr && workIsLive(evidence.openPrAt)) live.push(evidence.openPr);
|
|
13447
|
+
if (evidence.branch && workIsLive(evidence.branchAt)) live.push(`live branch ${evidence.branch} on ${repo}`);
|
|
13368
13448
|
return live;
|
|
13369
13449
|
}
|
|
13450
|
+
function staleWorkLines(evidence) {
|
|
13451
|
+
const age = (at) => formatClaimAge(Date.now() - Date.parse(at ?? ""));
|
|
13452
|
+
return [
|
|
13453
|
+
evidence.branch && !workIsLive(evidence.branchAt) ? `branch ${evidence.branch} (last commit ${age(evidence.branchAt)} ago)` : void 0,
|
|
13454
|
+
evidence.openPr && !workIsLive(evidence.openPrAt) ? `${evidence.openPr.replace(/ would close it on merge$/, "")} (updated ${age(evidence.openPrAt)} ago)` : void 0
|
|
13455
|
+
].filter((line) => Boolean(line));
|
|
13456
|
+
}
|
|
13370
13457
|
function sameClaimHost(a, b) {
|
|
13371
13458
|
return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
|
|
13372
13459
|
}
|
|
@@ -13399,13 +13486,17 @@ async function checkLaneContest(client, item, actor = describeSessionIdentity(),
|
|
|
13399
13486
|
const resume = ownership !== "mine" && sameOwner ? laneResumeEvidence(evidence) : void 0;
|
|
13400
13487
|
const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
|
|
13401
13488
|
const unverifiable = ownership === "mine" ? [] : evidence.failed;
|
|
13489
|
+
const contested = ownership !== "mine" && !resume && (live.length > 0 || unverifiable.length > 0);
|
|
13490
|
+
const carry = staleWorkLines(evidence);
|
|
13491
|
+
const staleStamp = !contested && !resume && ownership !== "mine" && evidence.marker ? `stale claim stamp ${describeClaimMarker(evidence.marker)} (idle ${formatClaimAge(claimStampStaleness(evidence.marker, { activityAt: [evidence.lastCommentAt] }).idleMs)})${carry.length ? `; continue its ${carry.join(" and ")}` : ""}` : void 0;
|
|
13402
13492
|
return {
|
|
13403
|
-
contested
|
|
13493
|
+
contested,
|
|
13404
13494
|
live,
|
|
13405
13495
|
unverifiable,
|
|
13406
13496
|
ownership,
|
|
13407
13497
|
marker: evidence.marker,
|
|
13408
|
-
resume
|
|
13498
|
+
resume,
|
|
13499
|
+
...staleStamp ? { staleStamp } : {}
|
|
13409
13500
|
};
|
|
13410
13501
|
}
|
|
13411
13502
|
function laneContestParts(contest) {
|
|
@@ -13849,11 +13940,13 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
13849
13940
|
};
|
|
13850
13941
|
let previousHolder;
|
|
13851
13942
|
let resumeEvidence;
|
|
13943
|
+
let staleStamp;
|
|
13944
|
+
const tookOver = () => ({ outcome: "took-over", holder, previousHolder, ...staleStamp ? { staleStamp } : {} });
|
|
13852
13945
|
const claimedReceipt = () => ({
|
|
13853
13946
|
...item.status === "In Review" ? { reclaimedFrom: "In Review" } : {},
|
|
13854
|
-
...previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } :
|
|
13947
|
+
...previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } : tookOver() : { outcome: "claimed", holder }
|
|
13855
13948
|
});
|
|
13856
|
-
const heldReceipt = () => previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } :
|
|
13949
|
+
const heldReceipt = () => previousHolder ? resumeEvidence ? { outcome: "resumed", holder, previousHolder, resumeEvidence } : tookOver() : { outcome: "held", holder };
|
|
13857
13950
|
if (flatItem.contentType !== "Issue") throw new Error(`${flatItem.ref} is not an issue`);
|
|
13858
13951
|
const pre = evaluateClaim(flatItem, assignedLogin);
|
|
13859
13952
|
if (!pre.ok) throw new NotClaimableError(pre.reason, { ref: flatItem.ref, status: flatItem.status });
|
|
@@ -13866,6 +13959,7 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
13866
13959
|
const refuseIfContested = async () => {
|
|
13867
13960
|
const contest = await checkLaneContest(client, item, ctx.session, report.viewer);
|
|
13868
13961
|
resumeEvidence = void 0;
|
|
13962
|
+
staleStamp = void 0;
|
|
13869
13963
|
const displaced = () => ({
|
|
13870
13964
|
login: assignedLogin,
|
|
13871
13965
|
...contest.marker ? {
|
|
@@ -13878,6 +13972,9 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
13878
13972
|
if (contest.resume && contest.marker) {
|
|
13879
13973
|
resumeEvidence = contest.resume;
|
|
13880
13974
|
previousHolder = displaced();
|
|
13975
|
+
} else if (contest.staleStamp) {
|
|
13976
|
+
staleStamp = contest.staleStamp;
|
|
13977
|
+
previousHolder = displaced();
|
|
13881
13978
|
}
|
|
13882
13979
|
return;
|
|
13883
13980
|
}
|
|
@@ -13892,7 +13989,7 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
13892
13989
|
if (options.check) {
|
|
13893
13990
|
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, ...heldReceipt(), alreadyClaimed: true, checked: true };
|
|
13894
13991
|
}
|
|
13895
|
-
const renewed = await postClaimMarkerComment(client, item, ctx.session);
|
|
13992
|
+
const renewed = await postClaimMarkerComment(client, item, ctx.session, staleStamp);
|
|
13896
13993
|
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, ...heldReceipt(), alreadyClaimed: true, renewed };
|
|
13897
13994
|
}
|
|
13898
13995
|
if (options.check) {
|
|
@@ -13904,7 +14001,7 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
13904
14001
|
} catch (e) {
|
|
13905
14002
|
throw new Error(`claim failed before board status changed: ${ghError(e)}`);
|
|
13906
14003
|
}
|
|
13907
|
-
await postClaimMarkerComment(client, item, ctx.session);
|
|
14004
|
+
await postClaimMarkerComment(client, item, ctx.session, staleStamp);
|
|
13908
14005
|
let credential = "user";
|
|
13909
14006
|
try {
|
|
13910
14007
|
({ credential } = await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]));
|
|
@@ -13958,7 +14055,7 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
13958
14055
|
const ref = `${selector.repo}#${selector.number}`;
|
|
13959
14056
|
try {
|
|
13960
14057
|
const result = await claimOneBoardItem(ctx, selector, { ...options, bulk: true });
|
|
13961
|
-
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, reclaimedFrom: result.reclaimedFrom, alreadyClaimed: result.alreadyClaimed, renewed: result.renewed, checked: result.checked, credential: result.credential };
|
|
14058
|
+
results[index] = { ref: result.item.ref, claimed: true, item: result.item, status: result.status, partial: result.partial, warning: result.warning, outcome: result.outcome, holder: result.holder, previousHolder: result.previousHolder, resumeEvidence: result.resumeEvidence, staleStamp: result.staleStamp, reclaimedFrom: result.reclaimedFrom, alreadyClaimed: result.alreadyClaimed, renewed: result.renewed, checked: result.checked, credential: result.credential };
|
|
13962
14059
|
} catch (e) {
|
|
13963
14060
|
results[index] = { ref, claimed: false, reason: e.message };
|
|
13964
14061
|
}
|
|
@@ -14146,7 +14243,7 @@ async function setBoardPriorityForSelector(options, deps = {}) {
|
|
|
14146
14243
|
}
|
|
14147
14244
|
return { ref: `${options.selector.repo}#${options.selector.number}`, itemId, priority };
|
|
14148
14245
|
}
|
|
14149
|
-
async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
|
|
14246
|
+
async function postClaimMarkerComment(client, item, actor = describeSessionIdentity(), staleStamp) {
|
|
14150
14247
|
try {
|
|
14151
14248
|
const marker = {
|
|
14152
14249
|
v: 1,
|
|
@@ -14157,7 +14254,7 @@ async function postClaimMarkerComment(client, item, actor = describeSessionIdent
|
|
|
14157
14254
|
};
|
|
14158
14255
|
await postIssueComment(client, {
|
|
14159
14256
|
ref: item.ref,
|
|
14160
|
-
body: `Claimed by \`${describeClaimMarker(marker)}\` at ${marker.ts}
|
|
14257
|
+
body: `Claimed by \`${describeClaimMarker(marker)}\` at ${marker.ts}.${staleStamp ? ` Took over from ${staleStamp}.` : ""}
|
|
14161
14258
|
|
|
14162
14259
|
<!-- mmi-claim: ${JSON.stringify(marker)} -->`
|
|
14163
14260
|
});
|
|
@@ -17128,10 +17225,10 @@ var rollout_plan_default = {
|
|
|
17128
17225
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
17129
17226
|
},
|
|
17130
17227
|
baseline: {
|
|
17131
|
-
version: "4.5.
|
|
17132
|
-
tag: "v4.5.
|
|
17133
|
-
commit: "
|
|
17134
|
-
npm: "@mutmutco/cli@4.5.
|
|
17228
|
+
version: "4.5.89",
|
|
17229
|
+
tag: "v4.5.89",
|
|
17230
|
+
commit: "3288efa7a0af",
|
|
17231
|
+
npm: "@mutmutco/cli@4.5.89"
|
|
17135
17232
|
},
|
|
17136
17233
|
exitCriterion: "fleet-n-of-n",
|
|
17137
17234
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17148,14 +17245,14 @@ var rollout_plan_default = {
|
|
|
17148
17245
|
repo: "mutmutco/mmi-hub",
|
|
17149
17246
|
role: "canary",
|
|
17150
17247
|
schedule: "train",
|
|
17151
|
-
v3Target: "v4.5.
|
|
17248
|
+
v3Target: "v4.5.89"
|
|
17152
17249
|
}
|
|
17153
17250
|
],
|
|
17154
17251
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
17155
17252
|
rollback: {
|
|
17156
17253
|
independent: true,
|
|
17157
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17158
|
-
v3Target: "v4.5.
|
|
17254
|
+
mechanism: "npm dist-tag latest -> 4.5.89 and redeploy the Hub Lambda from tag v4.5.89 (3288efa7a0af); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17255
|
+
v3Target: "v4.5.89 (@mutmutco/cli@4.5.89, tag commit 3288efa7a0af \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17159
17256
|
}
|
|
17160
17257
|
},
|
|
17161
17258
|
{
|
|
@@ -23182,7 +23279,12 @@ var TRANSIENT_TRANSPORT_MARKERS = [
|
|
|
23182
23279
|
];
|
|
23183
23280
|
function classifyTransientTransportFailure(evidence) {
|
|
23184
23281
|
if (!evidence) return false;
|
|
23185
|
-
return TRANSIENT_TRANSPORT_MARKERS.some((marker) => marker.test(evidence));
|
|
23282
|
+
return TRANSIENT_TRANSPORT_MARKERS.some((marker) => marker.test(evidence)) || isSetupBunSpawnEtxtbsy(evidence);
|
|
23283
|
+
}
|
|
23284
|
+
function isSetupBunSpawnEtxtbsy(evidence) {
|
|
23285
|
+
if (!/\bspawn ETXTBSY\b/.test(evidence)) return false;
|
|
23286
|
+
const steps = [...evidence.matchAll(/"steps":\[([^\]]*)\]/g)].flatMap((m) => [...m[1].matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((s) => s[1]));
|
|
23287
|
+
return steps.length > 0 && steps.every((name) => /\bsetup[- ]bun\b|oven-sh\/setup-bun/i.test(name));
|
|
23186
23288
|
}
|
|
23187
23289
|
var TRANSIENT_EVIDENCE_MAX_CHARS = 16e3;
|
|
23188
23290
|
var AUTO_RERUN_MAX_RETRIES = 1;
|
|
@@ -23229,8 +23331,16 @@ async function transientEvidenceForRun(deps, repo, runId) {
|
|
|
23229
23331
|
parts.push(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--log-failed"]));
|
|
23230
23332
|
} catch {
|
|
23231
23333
|
}
|
|
23232
|
-
const
|
|
23233
|
-
|
|
23334
|
+
const [head, ...rest] = parts.filter(Boolean);
|
|
23335
|
+
if (rest.length === 0 || !head?.startsWith("[")) {
|
|
23336
|
+
const evidence = parts.filter(Boolean).join("\n");
|
|
23337
|
+
return evidence.length > TRANSIENT_EVIDENCE_MAX_CHARS ? evidence.slice(-TRANSIENT_EVIDENCE_MAX_CHARS) : evidence;
|
|
23338
|
+
}
|
|
23339
|
+
const header = head.slice(0, TRANSIENT_EVIDENCE_MAX_CHARS / 4);
|
|
23340
|
+
const log = rest.join("\n");
|
|
23341
|
+
const room = TRANSIENT_EVIDENCE_MAX_CHARS - header.length - 1;
|
|
23342
|
+
return `${header}
|
|
23343
|
+
${log.length > room ? log.slice(-room) : log}`;
|
|
23234
23344
|
}
|
|
23235
23345
|
function extractFailedRequiredContexts(failure) {
|
|
23236
23346
|
const match = /required train check failed:\s*([^\n(]+)/.exec(failure);
|
|
@@ -30012,11 +30122,13 @@ function registerBoardCommands(program3) {
|
|
|
30012
30122
|
}
|
|
30013
30123
|
function claimVerdict(ref, result) {
|
|
30014
30124
|
const holder = formatClaimHolder(result.holder);
|
|
30015
|
-
const previousHolder = result.previousHolder ? formatClaimHolder(result.previousHolder) : "another lane";
|
|
30125
|
+
const previousHolder = result.staleStamp ?? (result.previousHolder ? formatClaimHolder(result.previousHolder) : "another lane");
|
|
30016
30126
|
const reclaimed = result.reclaimedFrom ? ` (reclaimed from ${result.reclaimedFrom})` : "";
|
|
30017
30127
|
if (result.checked) {
|
|
30018
30128
|
if (result.outcome === "held") return `Check ${ref}: held by ${holder} - claim would renew the lease (nothing written)`;
|
|
30019
|
-
if (result.outcome === "took-over")
|
|
30129
|
+
if (result.outcome === "took-over") {
|
|
30130
|
+
return result.staleStamp ? `Check ${ref}: ${previousHolder} - claim would take it over for ${holder} (nothing written)` : `Check ${ref}: held by ${previousHolder} - --force claim would take it over for ${holder} (nothing written)`;
|
|
30131
|
+
}
|
|
30020
30132
|
if (result.outcome === "resumed") return `Check ${ref}: prior lane ${previousHolder} is verifiably dead - claim would resume its work for ${holder} (nothing written; ${result.resumeEvidence})`;
|
|
30021
30133
|
return `Check ${ref}: free - claim would proceed for ${holder}${reclaimed} (nothing written)`;
|
|
30022
30134
|
}
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
package/skills/board/SKILL.md
CHANGED
|
@@ -27,9 +27,16 @@ Project. Partial reads exit nonzero; pass `--allow-partial` only when the dev ac
|
|
|
27
27
|
Partition, as the CLI computes it:
|
|
28
28
|
|
|
29
29
|
- **Yours** — assignee includes the viewer, status in `Todo · In Progress · In Review`.
|
|
30
|
-
- **Free to claim** — status `Todo`, unassigned, and the viewer has push on the repo.
|
|
30
|
+
- **Free to claim** — status `Todo`, unassigned, and the viewer has push on the repo. Also any item in
|
|
31
|
+
any status whose claim stamp is stale: its session has done no work on it for 4 hours. A stale stamp
|
|
32
|
+
holds nothing; `board claim` takes it over without `--force` and names the old stamp.
|
|
31
33
|
- **Taken** — someone else's, any active status.
|
|
32
34
|
|
|
35
|
+
Every stamped row names its session (`lane surface/session@host`), or `stale:<age>` when the stamp is
|
|
36
|
+
stale. Work means the stamp, an issue comment, a commit on the issue's branch, or an update to an open
|
|
37
|
+
PR that closes it. Any of these in the last 4 hours keeps the hold; a date that cannot be read counts as
|
|
38
|
+
work. A takeover keeps the old branch and PR and names them, so the new session continues them.
|
|
39
|
+
|
|
33
40
|
## Step 2 — render it
|
|
34
41
|
|
|
35
42
|
Plain markdown, never a fenced block. Flat `-` lists under bold heads, never `##` headings, never tables.
|
|
@@ -100,8 +107,9 @@ Claim a whole batch in one call — it shares setup cost and returns per-item re
|
|
|
100
107
|
mmi-cli oracle board claim <ref> <ref> <ref> --json
|
|
101
108
|
```
|
|
102
109
|
|
|
103
|
-
Claiming
|
|
104
|
-
|
|
110
|
+
Claiming takes a card that is unassigned or assigned to the viewer, in `Todo`, `In Progress` or
|
|
111
|
+
`In Review`, unless another session still shows work on it in the last 4 hours (then it refuses). It assigns the viewer, moves Status to
|
|
112
|
+
`In Progress`, and stamps a lane-identity comment so other agents can attribute the hold. Any per-item failure exits nonzero — read
|
|
105
113
|
the per-item results rather than assuming the batch took. **Route is `oracle board claim` only**; there is
|
|
106
114
|
no `oracle issue claim` and no flat `board claim` — both refuse. Guessing either burns a call.
|
|
107
115
|
|