patchrelay 0.54.3 → 0.55.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/README.md +8 -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/dist/config.js +24 -6
- package/dist/linear-client.js +6 -0
- package/dist/linear-session-reporting.js +10 -0
- package/dist/project-resolution.js +18 -0
- package/dist/webhooks/agent-session-handler.js +7 -1
- package/dist/webhooks/decision-helpers.js +2 -0
- package/dist/webhooks.js +5 -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
|
|
|
@@ -61,6 +61,8 @@ patchrelay service status
|
|
|
61
61
|
patchrelay dashboard
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
When one Linear team owns issues for multiple repositories, include `--project <Linear project>` on each `repo link`. PatchRelay routes Linear webhooks by project first, so separate projects inside the same `USE` team can map to separate GitHub repos.
|
|
65
|
+
|
|
64
66
|
Each repo needs two workflow files for repo-specific run behavior:
|
|
65
67
|
|
|
66
68
|
- `IMPLEMENTATION_WORKFLOW.md` — implementation, CI repair, queue repair runs
|
|
@@ -119,7 +121,7 @@ See the [merge-steward package README](./packages/merge-steward/README.md) for t
|
|
|
119
121
|
- [Prompting](./docs/prompting.md) — how workflow files and the built-in scaffold compose
|
|
120
122
|
- [Secrets](./docs/secrets.md) — systemd credentials, resolution order
|
|
121
123
|
- [review-quill reference](./docs/review-quill.md) · [merge-steward reference](./docs/merge-steward.md)
|
|
122
|
-
- [
|
|
124
|
+
- [Dashboard guidance](./docs/dashboard-guidance.md) · [Design docs](./docs/design-docs/index.md)
|
|
123
125
|
- [Contributing](./CONTRIBUTING.md) · [Security policy](./SECURITY.md)
|
|
124
126
|
|
|
125
127
|
## 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
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -52,6 +52,7 @@ const projectSchema = z.object({
|
|
|
52
52
|
trusted_actors: trustedActorsSchema,
|
|
53
53
|
issue_key_prefixes: z.array(z.string().min(1)).default([]),
|
|
54
54
|
linear_team_ids: z.array(z.string().min(1)).default([]),
|
|
55
|
+
linear_project_ids: z.array(z.string().min(1)).default([]),
|
|
55
56
|
allow_labels: z.array(z.string().min(1)).default([]),
|
|
56
57
|
trigger_events: z.array(z.string().min(1)).min(1).optional(),
|
|
57
58
|
branch_prefix: z.string().min(1).optional(),
|
|
@@ -466,6 +467,7 @@ export function loadConfig(configPath = process.env.PATCHRELAY_CONFIG ?? getDefa
|
|
|
466
467
|
worktreeRoot: ensureAbsolutePath(defaultWorktreeRoot(repository.githubRepo)),
|
|
467
468
|
issueKeyPrefixes: repository.issueKeyPrefixes,
|
|
468
469
|
linearTeamIds: repository.linearTeamIds,
|
|
470
|
+
linearProjectIds: repository.linearProjectIds,
|
|
469
471
|
allowLabels: [],
|
|
470
472
|
reviewChecks: repository.reviewChecks,
|
|
471
473
|
gateChecks: repository.gateChecks,
|
|
@@ -503,6 +505,7 @@ export function loadConfig(configPath = process.env.PATCHRELAY_CONFIG ?? getDefa
|
|
|
503
505
|
: {}),
|
|
504
506
|
issueKeyPrefixes: project.issue_key_prefixes,
|
|
505
507
|
linearTeamIds: project.linear_team_ids,
|
|
508
|
+
linearProjectIds: project.linear_project_ids,
|
|
506
509
|
allowLabels: project.allow_labels,
|
|
507
510
|
reviewChecks: project.review_checks,
|
|
508
511
|
gateChecks: project.gate_checks,
|
|
@@ -656,6 +659,7 @@ function validateConfigSemantics(config, options) {
|
|
|
656
659
|
const githubRepos = new Set();
|
|
657
660
|
const issuePrefixes = new Map();
|
|
658
661
|
const linearTeamIds = new Map();
|
|
662
|
+
const linearProjectIds = new Map();
|
|
659
663
|
for (const repository of config.repositories) {
|
|
660
664
|
if (githubRepos.has(repository.githubRepo)) {
|
|
661
665
|
throw new Error(`Duplicate repository github_repo: ${repository.githubRepo}`);
|
|
@@ -667,19 +671,26 @@ function validateConfigSemantics(config, options) {
|
|
|
667
671
|
throw new Error(`Duplicate project id: ${project.id}`);
|
|
668
672
|
}
|
|
669
673
|
projectIds.add(project.id);
|
|
674
|
+
for (const linearProjectId of project.linearProjectIds) {
|
|
675
|
+
const owner = linearProjectIds.get(linearProjectId);
|
|
676
|
+
if (owner && owner !== project.id) {
|
|
677
|
+
throw new Error(`Linear project id "${linearProjectId}" is configured for both ${owner} and ${project.id}`);
|
|
678
|
+
}
|
|
679
|
+
linearProjectIds.set(linearProjectId, project.id);
|
|
680
|
+
}
|
|
670
681
|
for (const prefix of project.issueKeyPrefixes) {
|
|
671
682
|
const owner = issuePrefixes.get(prefix);
|
|
672
|
-
if (owner && owner !== project.id) {
|
|
673
|
-
throw new Error(`Issue key prefix "${prefix}" is configured for both ${owner} and ${project.id}`);
|
|
683
|
+
if (owner && owner.id !== project.id && !hasDisjointProjectRouting(owner, project)) {
|
|
684
|
+
throw new Error(`Issue key prefix "${prefix}" is configured for both ${owner.id} and ${project.id}`);
|
|
674
685
|
}
|
|
675
|
-
issuePrefixes.set(prefix, project
|
|
686
|
+
issuePrefixes.set(prefix, project);
|
|
676
687
|
}
|
|
677
688
|
for (const teamId of project.linearTeamIds) {
|
|
678
689
|
const owner = linearTeamIds.get(teamId);
|
|
679
|
-
if (owner && owner !== project.id) {
|
|
680
|
-
throw new Error(`Linear team id "${teamId}" is configured for both ${owner} and ${project.id}`);
|
|
690
|
+
if (owner && owner.id !== project.id && !hasDisjointProjectRouting(owner, project)) {
|
|
691
|
+
throw new Error(`Linear team id "${teamId}" is configured for both ${owner.id} and ${project.id}`);
|
|
681
692
|
}
|
|
682
|
-
linearTeamIds.set(teamId, project
|
|
693
|
+
linearTeamIds.set(teamId, project);
|
|
683
694
|
}
|
|
684
695
|
}
|
|
685
696
|
if (config.operatorApi.enabled &&
|
|
@@ -689,3 +700,10 @@ function validateConfigSemantics(config, options) {
|
|
|
689
700
|
throw new Error("operator_api.enabled requires operator_api.bearer_token_env when server.bind is not 127.0.0.1");
|
|
690
701
|
}
|
|
691
702
|
}
|
|
703
|
+
function hasDisjointProjectRouting(left, right) {
|
|
704
|
+
if (left.linearProjectIds.length === 0 || right.linearProjectIds.length === 0) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
const rightProjectIds = new Set(right.linearProjectIds);
|
|
708
|
+
return left.linearProjectIds.every((linearProjectId) => !rightProjectIds.has(linearProjectId));
|
|
709
|
+
}
|
package/dist/linear-client.js
CHANGED
|
@@ -88,6 +88,10 @@ const LINEAR_ISSUE_SELECTION = `
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
project {
|
|
92
|
+
id
|
|
93
|
+
name
|
|
94
|
+
}
|
|
91
95
|
`;
|
|
92
96
|
export class LinearGraphqlClient {
|
|
93
97
|
options;
|
|
@@ -388,6 +392,8 @@ export class LinearGraphqlClient {
|
|
|
388
392
|
...(issue.state?.type ? { stateType: issue.state.type } : {}),
|
|
389
393
|
...(issue.team?.id ? { teamId: issue.team.id } : {}),
|
|
390
394
|
...(issue.team?.key ? { teamKey: issue.team.key } : {}),
|
|
395
|
+
...(issue.project?.id ? { projectId: issue.project.id } : {}),
|
|
396
|
+
...(issue.project?.name ? { projectName: issue.project.name } : {}),
|
|
391
397
|
...(issue.delegate?.id ? { delegateId: issue.delegate.id } : {}),
|
|
392
398
|
...(issue.delegate?.name ? { delegateName: issue.delegate.name } : {}),
|
|
393
399
|
workflowStates: (issue.team?.states?.nodes ?? []).map((state) => ({
|
|
@@ -42,6 +42,16 @@ export function buildAlreadyRunningThought(runType) {
|
|
|
42
42
|
body: `PatchRelay is already working on the ${lowerRunTypeLabel(runType)} workflow.`,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
export function buildBlockedDelegationActivity(blockedByKeys = []) {
|
|
46
|
+
const blockers = blockedByKeys.filter((key) => key.trim().length > 0);
|
|
47
|
+
const blockerText = blockers.length > 0
|
|
48
|
+
? ` Waiting on ${blockers.join(", ")}.`
|
|
49
|
+
: " Waiting for blocker issues to complete.";
|
|
50
|
+
return {
|
|
51
|
+
type: "response",
|
|
52
|
+
body: `PatchRelay accepted this delegation and will not start implementation until the issue is unblocked.${blockerText}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
45
55
|
export function buildPromptDeliveredThought(runType) {
|
|
46
56
|
return {
|
|
47
57
|
type: "thought",
|
|
@@ -11,12 +11,30 @@ function matchesProject(issue, project) {
|
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
export function resolveProject(config, issue) {
|
|
14
|
+
if (issue.projectId) {
|
|
15
|
+
const projectMatches = config.projects.filter((project) => matchesLinearProject(issue, project));
|
|
16
|
+
if (projectMatches.length === 1) {
|
|
17
|
+
return projectMatches[0];
|
|
18
|
+
}
|
|
19
|
+
if (projectMatches.length > 1) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
const matches = config.projects.filter((project) => matchesProject(issue, project));
|
|
15
24
|
if (matches.length === 1) {
|
|
16
25
|
return matches[0];
|
|
17
26
|
}
|
|
18
27
|
return undefined;
|
|
19
28
|
}
|
|
29
|
+
function matchesLinearProject(issue, project) {
|
|
30
|
+
if (!issue.projectId || !project.linearProjectIds.includes(issue.projectId)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (project.linearTeamIds.length === 0 || !issue.teamId) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return project.linearTeamIds.includes(issue.teamId);
|
|
37
|
+
}
|
|
20
38
|
export function triggerEventAllowed(project, triggerEvent) {
|
|
21
39
|
return project.triggerEvents.includes(triggerEvent);
|
|
22
40
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildAgentSessionPlanForIssue, } from "../agent-session-plan.js";
|
|
2
2
|
import { buildAgentSessionExternalUrls } from "../agent-session-presentation.js";
|
|
3
|
-
import { buildAlreadyRunningThought, buildDelegationThought, buildPromptDeliveredThought, buildStopConfirmationActivity, } from "../linear-session-reporting.js";
|
|
3
|
+
import { buildAlreadyRunningThought, buildBlockedDelegationActivity, buildDelegationThought, buildPromptDeliveredThought, buildStopConfirmationActivity, } from "../linear-session-reporting.js";
|
|
4
4
|
import { triggerEventAllowed } from "../project-resolution.js";
|
|
5
5
|
export class AgentSessionHandler {
|
|
6
6
|
config;
|
|
@@ -47,6 +47,12 @@ export class AgentSessionHandler {
|
|
|
47
47
|
await this.publishAgentActivity(linear, normalized.agentSession.id, buildAlreadyRunningThought(activeRun.runType));
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
+
if ((trackedIssue?.blockedByCount ?? 0) > 0) {
|
|
51
|
+
const latestIssue = this.db.issues.getIssue(project.id, normalized.issue.id);
|
|
52
|
+
await this.syncAgentSession(linear, normalized.agentSession.id, latestIssue ?? trackedIssue, params.peekPendingSessionWakeRunType);
|
|
53
|
+
await this.publishAgentActivity(linear, normalized.agentSession.id, buildBlockedDelegationActivity(trackedIssue?.blockedByKeys));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
50
56
|
if (!trackedIssue?.blockedByCount) {
|
|
51
57
|
await this.publishAgentActivity(linear, normalized.agentSession.id, {
|
|
52
58
|
type: "elicitation",
|
|
@@ -113,6 +113,8 @@ export function mergeIssueMetadata(issue, liveIssue) {
|
|
|
113
113
|
...(issue.attachments && issue.attachments.length > 0 ? {} : liveIssue.attachments ? { attachments: liveIssue.attachments } : {}),
|
|
114
114
|
...(issue.teamId ? {} : liveIssue.teamId ? { teamId: liveIssue.teamId } : {}),
|
|
115
115
|
...(issue.teamKey ? {} : liveIssue.teamKey ? { teamKey: liveIssue.teamKey } : {}),
|
|
116
|
+
...(issue.projectId ? {} : liveIssue.projectId ? { projectId: liveIssue.projectId } : {}),
|
|
117
|
+
...(issue.projectName ? {} : liveIssue.projectName ? { projectName: liveIssue.projectName } : {}),
|
|
116
118
|
...(issue.stateId ? {} : liveIssue.stateId ? { stateId: liveIssue.stateId } : {}),
|
|
117
119
|
...(issue.stateName ? {} : liveIssue.stateName ? { stateName: liveIssue.stateName } : {}),
|
|
118
120
|
...(issue.stateType ? {} : liveIssue.stateType ? { stateType: liveIssue.stateType } : {}),
|
package/dist/webhooks.js
CHANGED
|
@@ -200,8 +200,11 @@ function extractIssueMetadata(payload) {
|
|
|
200
200
|
const title = getString(issueRecord, "title");
|
|
201
201
|
const url = getString(issueRecord, "url") ?? payload.url;
|
|
202
202
|
const delegateRecord = asRecord(issueRecord.delegate);
|
|
203
|
+
const projectRecord = asRecord(issueRecord.project);
|
|
203
204
|
const teamId = getString(issueRecord, "teamId") ?? getString(teamRecord ?? {}, "id");
|
|
204
205
|
const teamKey = getString(teamRecord ?? {}, "key");
|
|
206
|
+
const projectId = getString(issueRecord, "projectId") ?? getString(projectRecord ?? {}, "id");
|
|
207
|
+
const projectName = getString(projectRecord ?? {}, "name");
|
|
205
208
|
const stateRecord = asRecord(issueRecord.state);
|
|
206
209
|
const stateId = getString(issueRecord, "stateId") ?? getString(stateRecord ?? {}, "id");
|
|
207
210
|
const stateName = getString(stateRecord ?? {}, "name");
|
|
@@ -227,6 +230,8 @@ function extractIssueMetadata(payload) {
|
|
|
227
230
|
...(url ? { url } : {}),
|
|
228
231
|
...(teamId ? { teamId } : {}),
|
|
229
232
|
...(teamKey ? { teamKey } : {}),
|
|
233
|
+
...(projectId ? { projectId } : {}),
|
|
234
|
+
...(projectName ? { projectName } : {}),
|
|
230
235
|
...(stateId ? { stateId } : {}),
|
|
231
236
|
...(stateName ? { stateName } : {}),
|
|
232
237
|
...(stateType ? { stateType } : {}),
|