patchrelay 0.30.0 → 0.31.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/dist/service.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { resolveGitHubAppCredentials, createGitHubAppTokenManager, ensureGhWrapper, } from "./github-app-token.js";
2
+ import { parseGitHubFailureContext, summarizeGitHubFailureContext } from "./github-failure-context.js";
2
3
  import { GitHubWebhookHandler } from "./github-webhook-handler.js";
3
4
  import { IssueQueryService } from "./issue-query-service.js";
4
5
  import { DatabaseBackedLinearClientProvider } from "./linear-client.js";
@@ -216,6 +217,10 @@ export class PatchRelayService {
216
217
  i.current_linear_state, i.factory_state, i.updated_at,
217
218
  i.pending_run_type,
218
219
  i.pr_number, i.pr_review_state, i.pr_check_status,
220
+ i.last_github_failure_source,
221
+ i.last_github_failure_head_sha,
222
+ i.last_github_failure_check_name,
223
+ i.last_github_failure_context_json,
219
224
  active_run.run_type AS active_run_type,
220
225
  latest_run.run_type AS latest_run_type,
221
226
  latest_run.status AS latest_run_status,
@@ -229,7 +234,10 @@ export class PatchRelayService {
229
234
  AND blockers.linear_issue_id = d.blocker_linear_issue_id
230
235
  WHERE d.project_id = i.project_id
231
236
  AND d.linear_issue_id = i.linear_issue_id
232
- AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
237
+ AND (
238
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
239
+ AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
240
+ )
233
241
  ) AS blocked_by_count,
234
242
  (
235
243
  SELECT json_group_array(COALESCE(blockers.issue_key, d.blocker_issue_key, d.blocker_linear_issue_id))
@@ -239,7 +247,10 @@ export class PatchRelayService {
239
247
  AND blockers.linear_issue_id = d.blocker_linear_issue_id
240
248
  WHERE d.project_id = i.project_id
241
249
  AND d.linear_issue_id = i.linear_issue_id
242
- AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
250
+ AND (
251
+ COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
252
+ AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
253
+ )
243
254
  ) AS blocked_by_keys_json
244
255
  FROM issues i
245
256
  LEFT JOIN runs active_run ON active_run.id = i.active_run_id
@@ -251,13 +262,22 @@ export class PatchRelayService {
251
262
  ORDER BY i.updated_at DESC, i.issue_key ASC`)
252
263
  .all();
253
264
  return rows.map((row) => {
265
+ const failureContext = parseGitHubFailureContext(typeof row.last_github_failure_context_json === "string" ? row.last_github_failure_context_json : undefined);
254
266
  const statusNote = extractStatusNote(typeof row.latest_run_summary_json === "string" ? row.latest_run_summary_json : undefined, typeof row.latest_run_report_json === "string" ? row.latest_run_report_json : undefined);
255
267
  const blockedByKeys = parseStringArray(typeof row.blocked_by_keys_json === "string" ? row.blocked_by_keys_json : undefined);
256
268
  const blockedByCount = Number(row.blocked_by_count ?? 0);
257
269
  const readyForExecution = row.pending_run_type !== null && row.pending_run_type !== undefined && row.active_run_type === null && blockedByCount === 0;
270
+ const failureSummary = summarizeGitHubFailureContext(failureContext);
271
+ const derivedStatusNote = blockedByCount > 0
272
+ ? `Blocked by ${blockedByKeys.join(", ")}`
273
+ : failureSummary && (row.factory_state === "repairing_ci"
274
+ || row.factory_state === "repairing_queue"
275
+ || row.factory_state === "failed")
276
+ ? failureSummary
277
+ : statusNote;
258
278
  const statusNoteWithBlockers = blockedByCount > 0
259
279
  ? `Blocked by ${blockedByKeys.join(", ")}`
260
- : statusNote;
280
+ : derivedStatusNote;
261
281
  return {
262
282
  ...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
263
283
  ...(row.title !== null ? { title: String(row.title) } : {}),
@@ -275,6 +295,11 @@ export class PatchRelayService {
275
295
  ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
276
296
  ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
277
297
  ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
298
+ ...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
299
+ ...(row.last_github_failure_head_sha !== null ? { latestFailureHeadSha: String(row.last_github_failure_head_sha) } : {}),
300
+ ...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
301
+ ...(failureContext?.stepName ? { latestFailureStepName: failureContext.stepName } : {}),
302
+ ...(failureContext?.summary ? { latestFailureSummary: failureContext.summary } : {}),
278
303
  updatedAt: String(row.updated_at),
279
304
  };
280
305
  });
@@ -180,6 +180,7 @@ export class WebhookHandler {
180
180
  ...(hydratedIssue.priority != null ? { priority: hydratedIssue.priority } : {}),
181
181
  ...(hydratedIssue.estimate != null ? { estimate: hydratedIssue.estimate } : {}),
182
182
  ...(hydratedIssue.stateName ? { currentLinearState: hydratedIssue.stateName } : {}),
183
+ ...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
183
184
  ...(pendingRunType ? { pendingRunType, factoryState: "delegated" } : {}),
184
185
  ...(clearPendingImplementation ? { pendingRunType: null } : {}),
185
186
  ...((pendingRunType || existingIssue?.pendingRunType === "implementation") && pendingRunContextJson
@@ -203,27 +204,30 @@ export class WebhookHandler {
203
204
  }
204
205
  async syncIssueDependencies(projectId, issue) {
205
206
  let source = issue;
206
- if (source.blockedBy.length === 0 && source.blocks.length === 0) {
207
+ if (!source.relationsKnown) {
207
208
  const linear = await this.linearProvider.forProject(projectId);
208
209
  if (linear) {
209
210
  try {
210
211
  source = mergeIssueMetadata(source, await linear.getIssue(issue.id));
211
212
  }
212
213
  catch {
213
- // Fall back to webhook payload data when live hydration is unavailable.
214
+ // Preserve existing dependency rows when webhook relation data is incomplete.
214
215
  }
215
216
  }
216
217
  }
217
- this.db.replaceIssueDependencies({
218
- projectId,
219
- linearIssueId: source.id,
220
- blockers: source.blockedBy.map((blocker) => ({
221
- blockerLinearIssueId: blocker.id,
222
- ...(blocker.identifier ? { blockerIssueKey: blocker.identifier } : {}),
223
- ...(blocker.title ? { blockerTitle: blocker.title } : {}),
224
- ...(blocker.stateName ? { blockerCurrentLinearState: blocker.stateName } : {}),
225
- })),
226
- });
218
+ if (source.relationsKnown) {
219
+ this.db.replaceIssueDependencies({
220
+ projectId,
221
+ linearIssueId: source.id,
222
+ blockers: source.blockedBy.map((blocker) => ({
223
+ blockerLinearIssueId: blocker.id,
224
+ ...(blocker.identifier ? { blockerIssueKey: blocker.identifier } : {}),
225
+ ...(blocker.title ? { blockerTitle: blocker.title } : {}),
226
+ ...(blocker.stateName ? { blockerCurrentLinearState: blocker.stateName } : {}),
227
+ ...(blocker.stateType ? { blockerCurrentLinearStateType: blocker.stateType } : {}),
228
+ })),
229
+ });
230
+ }
227
231
  return source;
228
232
  }
229
233
  reconcileDependentReadiness(projectId, blockerLinearIssueId) {
@@ -559,10 +563,12 @@ function mergeIssueMetadata(issue, liveIssue) {
559
563
  ...(issue.teamKey ? {} : liveIssue.teamKey ? { teamKey: liveIssue.teamKey } : {}),
560
564
  ...(issue.stateId ? {} : liveIssue.stateId ? { stateId: liveIssue.stateId } : {}),
561
565
  ...(issue.stateName ? {} : liveIssue.stateName ? { stateName: liveIssue.stateName } : {}),
566
+ ...(issue.stateType ? {} : liveIssue.stateType ? { stateType: liveIssue.stateType } : {}),
562
567
  ...(issue.delegateId ? {} : liveIssue.delegateId ? { delegateId: liveIssue.delegateId } : {}),
563
568
  ...(issue.delegateName ? {} : liveIssue.delegateName ? { delegateName: liveIssue.delegateName } : {}),
569
+ relationsKnown: issue.relationsKnown || liveIssue.blockedBy !== undefined || liveIssue.blocks !== undefined,
564
570
  labelNames: issue.labelNames.length > 0 ? issue.labelNames : (liveIssue.labels ?? []).map((l) => l.name),
565
- blockedBy: issue.blockedBy.length > 0 ? issue.blockedBy : (liveIssue.blockedBy ?? []),
566
- blocks: issue.blocks.length > 0 ? issue.blocks : (liveIssue.blocks ?? []),
571
+ blockedBy: issue.relationsKnown ? issue.blockedBy : (liveIssue.blockedBy ?? issue.blockedBy),
572
+ blocks: issue.relationsKnown ? issue.blocks : (liveIssue.blocks ?? issue.blocks),
567
573
  };
568
574
  }
package/dist/webhooks.js CHANGED
@@ -227,6 +227,7 @@ function extractIssueMetadata(payload) {
227
227
  ...(delegateName ? { delegateName } : {}),
228
228
  ...(priority != null ? { priority } : {}),
229
229
  ...(estimate != null ? { estimate } : {}),
230
+ relationsKnown: false,
230
231
  labelNames: extractLabelNames(issueRecord),
231
232
  blockedBy: [],
232
233
  blocks: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {