@humanbased/crosscheck 1.5.0 → 1.6.0-beta.13

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.
Files changed (53) hide show
  1. package/dist/__tests__/board.test.js +527 -20
  2. package/dist/__tests__/board.test.js.map +1 -1
  3. package/dist/__tests__/lock-ownership.test.d.ts +2 -0
  4. package/dist/__tests__/lock-ownership.test.d.ts.map +1 -0
  5. package/dist/__tests__/lock-ownership.test.js +167 -0
  6. package/dist/__tests__/lock-ownership.test.js.map +1 -0
  7. package/dist/__tests__/vendor-error-summary.test.d.ts +2 -0
  8. package/dist/__tests__/vendor-error-summary.test.d.ts.map +1 -0
  9. package/dist/__tests__/vendor-error-summary.test.js +74 -0
  10. package/dist/__tests__/vendor-error-summary.test.js.map +1 -0
  11. package/dist/cli.js +2 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/review.d.ts +3 -1
  14. package/dist/commands/review.d.ts.map +1 -1
  15. package/dist/commands/review.js +88 -2
  16. package/dist/commands/review.js.map +1 -1
  17. package/dist/commands/run.d.ts.map +1 -1
  18. package/dist/commands/run.js +22 -5
  19. package/dist/commands/run.js.map +1 -1
  20. package/dist/commands/watch.d.ts.map +1 -1
  21. package/dist/commands/watch.js +27 -23
  22. package/dist/commands/watch.js.map +1 -1
  23. package/dist/github/review-status.d.ts +33 -0
  24. package/dist/github/review-status.d.ts.map +1 -1
  25. package/dist/github/review-status.js +94 -9
  26. package/dist/github/review-status.js.map +1 -1
  27. package/dist/lib/board.d.ts +59 -4
  28. package/dist/lib/board.d.ts.map +1 -1
  29. package/dist/lib/board.js +376 -64
  30. package/dist/lib/board.js.map +1 -1
  31. package/dist/lib/pr-lock.d.ts +37 -1
  32. package/dist/lib/pr-lock.d.ts.map +1 -1
  33. package/dist/lib/pr-lock.js +176 -29
  34. package/dist/lib/pr-lock.js.map +1 -1
  35. package/dist/lib/pr-workflow-state.d.ts +2 -0
  36. package/dist/lib/pr-workflow-state.d.ts.map +1 -1
  37. package/dist/lib/pr-workflow-state.js +15 -0
  38. package/dist/lib/pr-workflow-state.js.map +1 -1
  39. package/dist/lib/tips.d.ts.map +1 -1
  40. package/dist/lib/tips.js +1 -0
  41. package/dist/lib/tips.js.map +1 -1
  42. package/dist/lib/vendor-error-summary.d.ts +19 -0
  43. package/dist/lib/vendor-error-summary.d.ts.map +1 -0
  44. package/dist/lib/vendor-error-summary.js +95 -0
  45. package/dist/lib/vendor-error-summary.js.map +1 -0
  46. package/dist/reviewers/claude.d.ts.map +1 -1
  47. package/dist/reviewers/claude.js +2 -1
  48. package/dist/reviewers/claude.js.map +1 -1
  49. package/dist/reviewers/codex.d.ts.map +1 -1
  50. package/dist/reviewers/codex.js +2 -1
  51. package/dist/reviewers/codex.js.map +1 -1
  52. package/get-started.md +18 -0
  53. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { INSTANCE_ID } from '../lib/pr-lock.js';
1
2
  const CONTEXT = 'crosscheck/review';
2
3
  // Lock is stale when the process hasn't heartbeated within this window.
3
4
  // Shorter = faster self-heal after a crash; must exceed HEARTBEAT_INTERVAL_MS
@@ -6,16 +7,51 @@ const STALE_MS = 5 * 60 * 1000;
6
7
  // Heartbeat keeps the pending status fresh so long-running reviews don't look
7
8
  // abandoned. Must be well under STALE_MS so a live review never goes stale.
8
9
  const HEARTBEAT_INTERVAL_MS = 2 * 60 * 1000;
10
+ // How long to let concurrent claims settle before reading back who won.
11
+ // createCommitStatus is an upsert, not a compare-and-swap — GitHub offers no
12
+ // conditional create for a commit status — so two instances can both write. The
13
+ // settle-then-read-back below is what turns that into a decision instead of two
14
+ // instances both proceeding. It must comfortably exceed a write round-trip.
15
+ const CLAIM_SETTLE_MS = 2_500;
16
+ // The claim marker. Kept short: GitHub truncates a status description at 140 chars,
17
+ // and the instance id has to survive that intact to be readable on the way back.
18
+ function claimDescription(id) {
19
+ return `crosscheck ${id}`;
20
+ }
21
+ /** Parse the instance id out of a claim description. Null for a foreign or legacy status. */
22
+ export function parseClaimOwner(description) {
23
+ if (!description)
24
+ return null;
25
+ const m = description.match(/^crosscheck ([^\s]+)/);
26
+ return m ? (m[1] ?? null) : null;
27
+ }
28
+ async function readClaim(octokit, owner, repo, sha) {
29
+ const { data } = await octokit.rest.repos.getCombinedStatusForRef({ owner, repo, ref: sha });
30
+ const status = data.statuses
31
+ .filter(s => s.context === CONTEXT)
32
+ .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())[0];
33
+ if (!status)
34
+ return null;
35
+ return {
36
+ state: status.state,
37
+ owner: parseClaimOwner(status.description),
38
+ updatedAt: new Date(status.updated_at).getTime(),
39
+ };
40
+ }
41
+ /**
42
+ * Is another instance actively reviewing this commit?
43
+ *
44
+ * Our own claim does not count: a retry inside the same process must not be
45
+ * blocked by the pending status that process itself just wrote.
46
+ */
9
47
  export async function checkRemoteLock(octokit, owner, repo, sha) {
10
48
  try {
11
- const { data } = await octokit.rest.repos.getCombinedStatusForRef({ owner, repo, ref: sha });
12
- const status = data.statuses
13
- .filter(s => s.context === CONTEXT)
14
- .sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())[0];
15
- if (!status || status.state !== 'pending')
49
+ const claim = await readClaim(octokit, owner, repo, sha);
50
+ if (!claim || claim.state !== 'pending')
16
51
  return false;
17
- const age = Date.now() - new Date(status.updated_at).getTime();
18
- return age < STALE_MS;
52
+ if (claim.owner === INSTANCE_ID)
53
+ return false;
54
+ return Date.now() - claim.updatedAt < STALE_MS;
19
55
  }
20
56
  catch {
21
57
  return false;
@@ -26,9 +62,43 @@ export async function acquireRemoteLock(octokit, owner, repo, sha) {
26
62
  owner, repo, sha,
27
63
  state: 'pending',
28
64
  context: CONTEXT,
29
- description: `Review started at ${new Date().toISOString()}`,
65
+ description: `${claimDescription(INSTANCE_ID)} started ${new Date().toISOString()}`,
30
66
  });
31
67
  }
68
+ /**
69
+ * Claim the commit and confirm the claim held. Returns false when another instance
70
+ * won, in which case this caller must not review.
71
+ *
72
+ * `checkRemoteLock` then `acquireRemoteLock` is a check-then-act race: two instances
73
+ * that receive the same webhook within a round-trip of each other both see no pending
74
+ * status, both write one, and both review the same commit — the duplicate same-SHA
75
+ * reviews this arbitration exists to stop. GitHub has no conditional status write, so
76
+ * instead of pretending the write was exclusive, both writers read back after a settle
77
+ * window and only the instance whose id survived as the newest status proceeds. Last
78
+ * write wins, deterministically, rather than nobody losing.
79
+ *
80
+ * This is arbitration, not a mutex: a peer writing after the read-back still gets in.
81
+ * The window is milliseconds against a review measured in minutes, and the local lock
82
+ * covers same-machine collisions outright.
83
+ */
84
+ export async function claimRemoteLock(octokit, owner, repo, sha, settleMs = CLAIM_SETTLE_MS) {
85
+ await acquireRemoteLock(octokit, owner, repo, sha);
86
+ await new Promise(resolve => setTimeout(resolve, settleMs));
87
+ try {
88
+ const claim = await readClaim(octokit, owner, repo, sha);
89
+ // No claim readable, or one with no owner (a legacy status, or a description
90
+ // GitHub truncated): nothing proves we lost, and refusing to review on an
91
+ // unreadable status would strand the PR. Proceed — the local lock still holds.
92
+ if (!claim || claim.owner === null)
93
+ return true;
94
+ return claim.owner === INSTANCE_ID;
95
+ }
96
+ catch {
97
+ // The read-back is the optional half. Failing it leaves us exactly where the
98
+ // old unconditional acquire always was, which is still safe enough to review.
99
+ return true;
100
+ }
101
+ }
32
102
  // Starts a repeating interval that refreshes the pending status timestamp so
33
103
  // checkRemoteLock never treats an active review as stale. Returns a stop
34
104
  // function that must be called in the finally block after the review completes.
@@ -38,15 +108,30 @@ export function startRemoteLockHeartbeat(octokit, owner, repo, sha) {
38
108
  owner, repo, sha,
39
109
  state: 'pending',
40
110
  context: CONTEXT,
41
- description: `Review active at ${new Date().toISOString()}`,
111
+ description: `${claimDescription(INSTANCE_ID)} active ${new Date().toISOString()}`,
42
112
  }).catch(() => { });
43
113
  }, HEARTBEAT_INTERVAL_MS);
44
114
  return () => clearInterval(id);
45
115
  }
116
+ /**
117
+ * Publish the review outcome and drop the claim — but only if the claim is still ours.
118
+ *
119
+ * Writing unconditionally let any instance clear any other instance's lock. Worse than
120
+ * the lost mutual exclusion, `crosscheck/review` is a status a repo can require for
121
+ * merge: a late `success` from an instance that reviewed an older commit, or one whose
122
+ * claim was superseded, marks a commit green that nothing has actually reviewed.
123
+ */
46
124
  export async function releaseRemoteLock(octokit, owner, repo, sha, outcome) {
47
125
  try {
126
+ const claim = await readClaim(octokit, owner, repo, sha);
127
+ // A claim owned by someone else is theirs to resolve. An unowned claim (legacy
128
+ // or truncated) is indistinguishable from our own predecessor's, so resolve it
129
+ // rather than leaving a pending status behind forever.
130
+ if (claim && claim.owner !== null && claim.owner !== INSTANCE_ID)
131
+ return;
48
132
  await octokit.rest.repos.createCommitStatus({
49
133
  owner, repo, sha, state: outcome, context: CONTEXT,
134
+ description: `${claimDescription(INSTANCE_ID)} ${outcome}`,
50
135
  });
51
136
  }
52
137
  catch { /* best-effort */ }
@@ -1 +1 @@
1
- {"version":3,"file":"review-status.js","sourceRoot":"","sources":["../../src/github/review-status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"review-status.js","sourceRoot":"","sources":["../../src/github/review-status.ts"],"names":[],"mappings":"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"}
@@ -24,10 +24,31 @@ export interface PRCompletionData {
24
24
  * run, such as one the review strategy short-circuited. */
25
25
  label?: string;
26
26
  }
27
+ /**
28
+ * The terminal state of one settled slot, for the session outcome distribution.
29
+ * `skipped` is a PR that settled without any verdict at all — the review
30
+ * strategy short-circuited it (lockfile-only, doc-only) — as distinct from
31
+ * `no verdict`, where a reviewer ran and returned nothing parseable.
32
+ */
33
+ export type Outcome = 'APPROVE' | 'NEEDS WORK' | 'BLOCK' | 'no verdict' | 'skipped' | 'error';
27
34
  export declare function fmtTime(d?: Date): string;
28
35
  export declare const FMT_TIME_WIDTH = 11;
36
+ export declare function fmtUptime(ms: number): string;
37
+ /**
38
+ * Whole percentages summing to exactly 100, by the largest-remainder method.
39
+ * Plain rounding drifts — three equal shares render as 33/33/33 — and a
40
+ * distribution that visibly fails to add up reads as a bug in the numbers.
41
+ *
42
+ * A non-zero count too small to earn a point comes back as 0; the caller
43
+ * renders those as "<1%" rather than claiming the outcome never happened.
44
+ */
45
+ export declare function distribute(counts: readonly number[]): number[];
29
46
  export declare function fmtTokens(n?: number): string;
30
47
  export declare function fmtTokensRaw(n?: number): string;
48
+ export declare function isNoticeLine(msg: string): boolean;
49
+ export type PageKey = 'older' | 'newer' | null;
50
+ /** Map a raw stdin key sequence to a page direction, or null when it is not a page key. */
51
+ export declare function pageKeyAction(seq: string): PageKey;
31
52
  export declare class PRBoard {
32
53
  private slots;
33
54
  private frameIdx;
@@ -36,6 +57,10 @@ export declare class PRBoard {
36
57
  private liveContent;
37
58
  private readonly isTTY;
38
59
  private connLog;
60
+ private page;
61
+ private pageCount;
62
+ private keyHandler;
63
+ private stdinWasRaw;
39
64
  private stats;
40
65
  private tunnel;
41
66
  private config;
@@ -45,9 +70,20 @@ export declare class PRBoard {
45
70
  setTunnel(type: string, url: string | null, alive: boolean): void;
46
71
  start(): void;
47
72
  stop(): void;
73
+ /** Flip one page toward older history. No-op when already at the oldest page. */
74
+ pageOlder(): void;
75
+ /** Flip one page toward the live page. No-op when already on it. */
76
+ pageNewer(): void;
48
77
  addPR(key: string, prNumber: number, repo: string, branch: string, round?: number): void;
49
78
  updatePR(key: string, updates: PRUpdate): void;
50
79
  completePR(key: string, data: PRCompletionData): void;
80
+ /**
81
+ * Settle a slot that ended in an error. The slot stays in the workspace as a
82
+ * settled row, the same as a completed one: a failed run is a session record,
83
+ * and deleting it here was what pushed reviewer timeouts out of the table and
84
+ * into raw scrollback, where a long watch session showed 43 errors above an
85
+ * empty workspace reading "no PRs yet".
86
+ */
51
87
  failPR(key: string, error: string): void;
52
88
  /** Print 1–2 static lines to scrollback (above the live block). */
53
89
  log(line1: string, line2?: string): void;
@@ -60,7 +96,13 @@ export declare class PRBoard {
60
96
  private verdictBadge;
61
97
  private crFillFn;
62
98
  private crLabelFn;
63
- private uptime;
99
+ /** "since 08:25 PM · up 3h32m" — when watch started, and how long it has run. */
100
+ private sessionRow;
101
+ /**
102
+ * Outcome shares across every PR that settled this session, e.g.
103
+ * "APPROVE 45% · BLOCK 30% · error 25%". Empty when nothing has settled yet.
104
+ */
105
+ private outcomeRow;
64
106
  private statsRow;
65
107
  private renderPRSlot;
66
108
  private phaseLine1Label;
@@ -68,17 +110,30 @@ export declare class PRBoard {
68
110
  private renderFixSection;
69
111
  private renderRecheckSection;
70
112
  private render;
113
+ /**
114
+ * Pick the slots the live page shows: the expanded layout when it fits, then
115
+ * the compact one, then compact with the oldest settled slots handed over to
116
+ * history until the block fits the viewport. Active slots are never handed
117
+ * over — a running review must stay on screen.
118
+ */
119
+ private fitLivePage;
120
+ /** Rows the panels and footer cost, i.e. everything but the PR rows. */
121
+ private chromeRows;
122
+ private fitToBudget;
71
123
  private renderLayout;
72
- /** Evict the oldest completed slot to scrollback. Returns false when none left. */
73
- private evictOldestCompleted;
124
+ /** Drop the oldest settled slots once the session history outgrows the cap. */
125
+ private trimHistory;
74
126
  /** Drop rows from the top until the content (plus an indicator line) fits the budget. */
75
127
  private truncateTop;
76
128
  private renderConfigPanel;
77
129
  private renderStatsPanel;
78
130
  private renderTipLine;
79
131
  private renderPRWorkspace;
132
+ /** Footer: where in the retained history this page sits, and how to move. */
133
+ private renderFooter;
80
134
  private renderPRSlotFolded;
81
- private evictOverflow;
82
135
  private redraw;
136
+ private attachKeys;
137
+ private detachKeys;
83
138
  }
84
139
  //# sourceMappingURL=board.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"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"}