patchrelay 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/README.md +6 -6
- package/dist/build-info.json +3 -3
- package/dist/cli/watch/IssueListView.js +6 -15
- package/dist/cli/watch/list-layout.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ This repository ships **three independent services**. Install one, two, or all t
|
|
|
8
8
|
|
|
9
9
|
| Service | Package | Role |
|
|
10
10
|
|-|-|-|
|
|
11
|
-
| [`patchrelay`](./) | `
|
|
12
|
-
| [`review-quill`](./packages/review-quill) | `
|
|
13
|
-
| [`merge-steward`](./packages/merge-steward) | `
|
|
11
|
+
| [`patchrelay`](./) | `pnpm add -g patchrelay` | Linear-driven harness that runs Codex sessions inside your real repos. Fully autonomous on webhooks: implementation, review fix, CI repair, queue repair. |
|
|
12
|
+
| [`review-quill`](./packages/review-quill) | `pnpm add -g review-quill` | GitHub PR review bot. Reviews every merge-ready head from a real local checkout and posts a normal `APPROVE` / `REQUEST_CHANGES` review. |
|
|
13
|
+
| [`merge-steward`](./packages/merge-steward) | `pnpm add -g merge-steward` | Serial merge queue. Speculatively integrates approved PRs on top of the latest `main`, runs CI on the integrated SHA, and fast-forwards `main` only when that tested result is green. |
|
|
14
14
|
|
|
15
15
|
Common setups:
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ Common setups:
|
|
|
22
22
|
|
|
23
23
|
- **PRs ship tested against the latest `main`.** The queue re-validates on the integrated SHA at admission time, and retries if `main` moves during validation. No more "green yesterday, broken today."
|
|
24
24
|
- **Many PR failures have mechanical fixes an agent can handle.** Requested changes like a rename, a missing null check, a new test, refreshing against `main`, resolving a conflict surfaced by speculation, or rerunning a flaky job. Both services publish structured failure reasons (inline review comments, failing check names, queue incidents) an agent can act on directly.
|
|
25
|
-
- **No prerequisites beyond GitHub.** A GitHub App, a webhook, and `
|
|
25
|
+
- **No prerequisites beyond GitHub.** A GitHub App, a webhook, and `pnpm add -g` per service.
|
|
26
26
|
|
|
27
27
|
## Use with your own agent
|
|
28
28
|
|
|
@@ -45,7 +45,7 @@ Prerequisites:
|
|
|
45
45
|
- a public HTTPS entrypoint (Caddy, nginx, tunnel) so Linear and GitHub can reach your webhooks
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
|
|
48
|
+
pnpm add -g patchrelay
|
|
49
49
|
patchrelay init https://patchrelay.example.com
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -119,7 +119,7 @@ See the [merge-steward package README](./packages/merge-steward/README.md) for t
|
|
|
119
119
|
- [Prompting](./docs/prompting.md) — how workflow files and the built-in scaffold compose
|
|
120
120
|
- [Secrets](./docs/secrets.md) — systemd credentials, resolution order
|
|
121
121
|
- [review-quill reference](./docs/review-quill.md) · [merge-steward reference](./docs/merge-steward.md)
|
|
122
|
-
- [
|
|
122
|
+
- [Dashboard guidance](./docs/dashboard-guidance.md) · [Design docs](./docs/design-docs/index.md)
|
|
123
123
|
- [Contributing](./CONTRIBUTING.md) · [Security policy](./SECURITY.md)
|
|
124
124
|
|
|
125
125
|
## Status
|
package/dist/build-info.json
CHANGED
|
@@ -4,25 +4,16 @@ import { Box, Text, useStdout } from "ink";
|
|
|
4
4
|
import { IssueRow } from "./IssueRow.js";
|
|
5
5
|
import { StatusBar } from "./StatusBar.js";
|
|
6
6
|
import { HelpBar } from "./HelpBar.js";
|
|
7
|
-
|
|
7
|
+
import { computeIssueListLayout, computeVisibleIssueParts, computeVisibleWindowForTotal } from "./list-layout.js";
|
|
8
8
|
export function computeVisibleWindow(issues, selectedIndex, maxRows) {
|
|
9
|
-
|
|
10
|
-
return { start: 0, end: 0 };
|
|
11
|
-
const clamped = Math.max(0, Math.min(selectedIndex, issues.length - 1));
|
|
12
|
-
const half = Math.floor(maxRows / 2);
|
|
13
|
-
let start = Math.max(0, clamped - half);
|
|
14
|
-
let end = Math.min(issues.length, start + maxRows);
|
|
15
|
-
if (end - start < maxRows) {
|
|
16
|
-
start = Math.max(0, end - maxRows);
|
|
17
|
-
}
|
|
18
|
-
return { start, end };
|
|
9
|
+
return computeVisibleWindowForTotal(issues.length, selectedIndex, maxRows);
|
|
19
10
|
}
|
|
20
11
|
export function IssueListView({ issues, selectedIndex, connected, lastServerMessageAt, filter, frozen, compact = false, }) {
|
|
21
12
|
const { stdout } = useStdout();
|
|
22
13
|
const cols = stdout?.columns ?? 80;
|
|
23
|
-
const rows = stdout?.rows ?? 24;
|
|
14
|
+
const rows = Math.max(1, stdout?.rows ?? 24);
|
|
24
15
|
const titleWidth = Math.max(0, cols - 42);
|
|
25
|
-
const
|
|
16
|
+
const layout = computeIssueListLayout(rows);
|
|
26
17
|
const [, tick] = useReducer((c) => c + 1, 0);
|
|
27
18
|
useEffect(() => {
|
|
28
19
|
if (frozen)
|
|
@@ -30,9 +21,9 @@ export function IssueListView({ issues, selectedIndex, connected, lastServerMess
|
|
|
30
21
|
const id = setInterval(tick, 5000);
|
|
31
22
|
return () => clearInterval(id);
|
|
32
23
|
}, [frozen]);
|
|
33
|
-
const { start: startIndex, end: endIndex } =
|
|
24
|
+
const { start: startIndex, end: endIndex, showAbove, showBelow, } = computeVisibleIssueParts(issues.length, selectedIndex, layout.bodyRows);
|
|
34
25
|
const visible = issues.slice(startIndex, endIndex);
|
|
35
26
|
const hiddenAbove = startIndex;
|
|
36
27
|
const hiddenBelow = Math.max(0, issues.length - endIndex);
|
|
37
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { filter: filter, connected: connected, lastServerMessageAt: lastServerMessageAt, frozen: frozen ?? false }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: issues.length === 0 ? (_jsx(Text, { dimColor: true, children: " " })) : (_jsxs(_Fragment, { children: [
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { filter: filter, connected: connected, lastServerMessageAt: lastServerMessageAt, frozen: frozen ?? false }), _jsx(Box, { marginTop: layout.showBodyGap ? 1 : 0, flexDirection: "column", children: issues.length === 0 ? (_jsx(Text, { dimColor: true, children: " " })) : (_jsxs(_Fragment, { children: [showAbove ? _jsx(Text, { dimColor: true, children: ` ↑${hiddenAbove}` }) : null, visible.map((issue, i) => (_jsx(IssueRow, { issue: issue, selected: startIndex + i === selectedIndex, titleWidth: titleWidth, compact: compact }, issue.issueKey ?? `${issue.projectId}-${startIndex + i}`))), showBelow ? _jsx(Text, { dimColor: true, children: ` ↓${hiddenBelow}` }) : null] })) }), layout.showHelp ? (_jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "list" }) })) : null] }));
|
|
38
29
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function computeIssueListLayout(totalRows) {
|
|
2
|
+
const rows = Math.max(1, totalRows);
|
|
3
|
+
const showBodyGap = rows >= 5;
|
|
4
|
+
const showHelp = rows >= 8;
|
|
5
|
+
const chromeRows = 1 + (showBodyGap ? 1 : 0) + (showHelp ? 2 : 0);
|
|
6
|
+
return {
|
|
7
|
+
bodyRows: Math.max(1, rows - chromeRows),
|
|
8
|
+
showBodyGap,
|
|
9
|
+
showHelp,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function computeVisibleWindowForTotal(total, selectedIndex, maxRows) {
|
|
13
|
+
if (total === 0)
|
|
14
|
+
return { start: 0, end: 0 };
|
|
15
|
+
const clamped = Math.max(0, Math.min(selectedIndex, total - 1));
|
|
16
|
+
const half = Math.floor(maxRows / 2);
|
|
17
|
+
let start = Math.max(0, clamped - half);
|
|
18
|
+
let end = Math.min(total, start + maxRows);
|
|
19
|
+
if (end - start < maxRows) {
|
|
20
|
+
start = Math.max(0, end - maxRows);
|
|
21
|
+
}
|
|
22
|
+
return { start, end };
|
|
23
|
+
}
|
|
24
|
+
export function computeVisibleIssueParts(total, selectedIndex, rowBudget) {
|
|
25
|
+
if (total === 0 || rowBudget <= 0) {
|
|
26
|
+
return { start: 0, end: 0, showAbove: false, showBelow: false };
|
|
27
|
+
}
|
|
28
|
+
let { start, end } = computeVisibleWindowForTotal(total, selectedIndex, Math.max(1, rowBudget));
|
|
29
|
+
let hiddenAbove = start > 0;
|
|
30
|
+
let hiddenBelow = end < total;
|
|
31
|
+
if (rowBudget >= 3 && (hiddenAbove || hiddenBelow)) {
|
|
32
|
+
const indicatorRows = (hiddenAbove ? 1 : 0) + (hiddenBelow ? 1 : 0);
|
|
33
|
+
({ start, end } = computeVisibleWindowForTotal(total, selectedIndex, Math.max(1, rowBudget - indicatorRows)));
|
|
34
|
+
hiddenAbove = start > 0;
|
|
35
|
+
hiddenBelow = end < total;
|
|
36
|
+
}
|
|
37
|
+
const usedRows = end - start;
|
|
38
|
+
let remaining = Math.max(0, rowBudget - usedRows);
|
|
39
|
+
const showAbove = hiddenAbove && remaining > 0;
|
|
40
|
+
if (showAbove)
|
|
41
|
+
remaining -= 1;
|
|
42
|
+
const showBelow = hiddenBelow && remaining > 0;
|
|
43
|
+
return { start, end, showAbove, showBelow };
|
|
44
|
+
}
|