@botbuddy/cli 1.8.8 → 1.9.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/package.json +1 -1
- package/src/wait-core.mjs +57 -0
- package/src/wait.mjs +17 -2
- package/src/botbuddy-release-repair.json +0 -1
package/package.json
CHANGED
package/src/wait-core.mjs
CHANGED
|
@@ -611,6 +611,63 @@ export function foldFeedLag(degraded, lagging) {
|
|
|
611
611
|
return degraded;
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
// BOT-1539 — arm-time outstanding-review notice.
|
|
616
|
+
//
|
|
617
|
+
// `pr-review` / `pr-state` are edge-triggered: they wake only on a review event
|
|
618
|
+
// delivered AFTER arm. When the relay reports (via `pr_review_snapshot` on the
|
|
619
|
+
// register receipt) that a PR ALREADY has an outstanding review, warn the
|
|
620
|
+
// operator on stderr so a silent park isn't mistaken for "no review yet". The
|
|
621
|
+
// existing review will NOT re-wake this wait.
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
|
|
624
|
+
// Compact, human relative age (e.g. "27m ago", "3h ago", "2d ago"). Returns
|
|
625
|
+
// null when the timestamp is absent or unparseable, so callers can omit the
|
|
626
|
+
// phrase entirely.
|
|
627
|
+
function relativeAge(iso, now) {
|
|
628
|
+
if (!iso) return null;
|
|
629
|
+
const then = Date.parse(iso);
|
|
630
|
+
if (!Number.isFinite(then)) return null;
|
|
631
|
+
const secs = Math.max(0, Math.round((now - then) / 1000));
|
|
632
|
+
if (secs < 60) return `${secs}s ago`;
|
|
633
|
+
const mins = Math.round(secs / 60);
|
|
634
|
+
if (mins < 60) return `${mins}m ago`;
|
|
635
|
+
const hours = Math.round(mins / 60);
|
|
636
|
+
if (hours < 48) return `${hours}h ago`;
|
|
637
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Build one stderr warning line per OUTSTANDING pr-review snapshot entry.
|
|
642
|
+
* Clean (not-outstanding) entries and empty/absent input yield no lines.
|
|
643
|
+
*
|
|
644
|
+
* @param {Array<{repo:string,pr:number,unresolved_threads:number,review_decision:string,pr_updated_at:string|null,existing_review_outstanding:boolean}>|null|undefined} snapshot
|
|
645
|
+
* @param {number} [now] epoch ms, for deterministic age formatting
|
|
646
|
+
* @returns {string[]}
|
|
647
|
+
*/
|
|
648
|
+
export function formatPrReviewSnapshotWarnings(snapshot, now = Date.now()) {
|
|
649
|
+
if (!Array.isArray(snapshot)) return [];
|
|
650
|
+
const lines = [];
|
|
651
|
+
for (const e of snapshot) {
|
|
652
|
+
if (!e || e.existing_review_outstanding !== true) continue;
|
|
653
|
+
const threads = typeof e.unresolved_threads === "number" ? e.unresolved_threads : 0;
|
|
654
|
+
const parts = [`pr-review armed: ${e.repo}#${e.pr} already has a review outstanding`];
|
|
655
|
+
if (threads > 0) {
|
|
656
|
+
parts.push(`${threads} unresolved review thread${threads === 1 ? "" : "s"}`);
|
|
657
|
+
}
|
|
658
|
+
if (e.review_decision && e.review_decision !== "unknown") {
|
|
659
|
+
parts.push(`review_decision=${e.review_decision}`);
|
|
660
|
+
}
|
|
661
|
+
const age = relativeAge(e.pr_updated_at, now);
|
|
662
|
+
if (age) parts.push(`last update ${age}`);
|
|
663
|
+
lines.push(
|
|
664
|
+
`${parts.join(" · ")}. This wait fires only on the NEXT review event; ` +
|
|
665
|
+
`the existing review will not re-wake it.`,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
return lines;
|
|
669
|
+
}
|
|
670
|
+
|
|
614
671
|
// ---------------------------------------------------------------------------
|
|
615
672
|
// Matching — does a spine signal frame satisfy a condition?
|
|
616
673
|
// ---------------------------------------------------------------------------
|
package/src/wait.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// Usage: botbuddy wait [--any] <condition>... [options]
|
|
14
14
|
// Run botbuddy wait --help for the condition grammar.
|
|
15
15
|
|
|
16
|
-
import { EXIT, parseConditions, parseSseFrames, runWaitLoop, normalizeSince, truncateReceipt } from "./wait-core.mjs";
|
|
16
|
+
import { EXIT, parseConditions, parseSseFrames, runWaitLoop, normalizeSince, truncateReceipt, formatPrReviewSnapshotWarnings } from "./wait-core.mjs";
|
|
17
17
|
import { resolveAgentProfile, withPrincipalReceipt } from "./wait-profile.mjs";
|
|
18
18
|
import { VERSION } from "./version.mjs";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
@@ -52,7 +52,12 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
|
|
|
52
52
|
flip sets payload.mergeable_changed (no new condition).
|
|
53
53
|
pr-review:repo=<owner/repo>[,pr=<n>]
|
|
54
54
|
a new review comment, or a thread resolved/re-opened
|
|
55
|
-
(payload carries the unresolved-thread count)
|
|
55
|
+
(payload carries the unresolved-thread count).
|
|
56
|
+
Edge-triggered: it wakes only on review activity AFTER
|
|
57
|
+
arm. If the PR ALREADY has an outstanding review at arm,
|
|
58
|
+
bb-wait prints a one-line stderr notice (unresolved count,
|
|
59
|
+
review_decision, age) so a silent park isn't mistaken for
|
|
60
|
+
"no review yet" — the existing review will not re-wake it.
|
|
56
61
|
test-run:id=<run_id> a test run reaching a terminal status (owner-scoped)
|
|
57
62
|
lease:id=<lease_id> a batch stack lease (BOT-1218) leaving the queue /
|
|
58
63
|
becoming active — the zero-poll wake for a parked
|
|
@@ -334,6 +339,10 @@ async function registerWait(opts, conditions, deadlineIso) {
|
|
|
334
339
|
// echoes session_agent_id only when it overrode the profile agent).
|
|
335
340
|
sessionId: body.session_id ?? null,
|
|
336
341
|
sessionAgentId: body.session_agent_id ?? null,
|
|
342
|
+
// BOT-1539: arm-time snapshot of already-outstanding code review for any
|
|
343
|
+
// pr-review/pr-state target. Report-only — surfaced on stderr so a silent
|
|
344
|
+
// edge-triggered park isn't mistaken for "no review yet".
|
|
345
|
+
prReviewSnapshot: Array.isArray(body.pr_review_snapshot) ? body.pr_review_snapshot : null,
|
|
337
346
|
};
|
|
338
347
|
}
|
|
339
348
|
|
|
@@ -618,6 +627,12 @@ export async function runWait(argv) {
|
|
|
618
627
|
registeredAgentId = reg.agentId;
|
|
619
628
|
registeredSessionAgentId = reg.sessionAgentId;
|
|
620
629
|
registeredSessionId = reg.sessionId;
|
|
630
|
+
// BOT-1539: if any pr-review/pr-state target already has an outstanding
|
|
631
|
+
// review, tell the operator now (stderr, one line per PR). Purely
|
|
632
|
+
// informational — it does not change arming or exit semantics.
|
|
633
|
+
for (const line of formatPrReviewSnapshotWarnings(reg.prReviewSnapshot)) {
|
|
634
|
+
process.stderr.write(`bb-wait: ${line}\n`);
|
|
635
|
+
}
|
|
621
636
|
// BOT-1184: adopt the server's canonical host for each lock condition so the
|
|
622
637
|
// local matcher builds the same subject_key the availability/claim-grant
|
|
623
638
|
// signals carry (armed under an alias like 'jono-mac', the signal uses the
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"schema_version":1,"source_version":"1.8.7","source_identity":"e6b611000678281287808866482334e5a40e265ef3c0328c4e85f53a5bf23cd0"}
|