patchrelay 0.49.3 → 0.50.1
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/build-info.json +3 -3
- package/dist/cli/cluster-health.js +66 -4
- package/dist/cli/watch/IssueRow.js +15 -3
- package/dist/cli/watch/format-utils.js +3 -0
- package/dist/cli/watch/issue-token.js +19 -0
- package/dist/config.js +9 -9
- package/dist/issue-class.js +5 -25
- package/dist/prompting/patchrelay.js +8 -0
- package/dist/run-budgets.js +3 -3
- package/dist/webhooks/desired-stage-recorder.js +9 -7
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -490,7 +490,13 @@ function deriveCiOwner(params) {
|
|
|
490
490
|
return "paused";
|
|
491
491
|
if (params.factoryState === "changes_requested")
|
|
492
492
|
return "patchrelay";
|
|
493
|
-
if (params.reviewQuillAttempt
|
|
493
|
+
if (params.reviewQuillAttempt?.backlog
|
|
494
|
+
&& params.currentHeadSha
|
|
495
|
+
&& params.reviewQuillAttempt.headSha
|
|
496
|
+
&& params.currentHeadSha !== params.reviewQuillAttempt.headSha) {
|
|
497
|
+
return "review-quill";
|
|
498
|
+
}
|
|
499
|
+
if (params.reviewQuillAttempt && !params.reviewQuillAttempt.backlog)
|
|
494
500
|
return "review-quill";
|
|
495
501
|
if (headAdvancedPastBlockingReview)
|
|
496
502
|
return "reviewer";
|
|
@@ -524,7 +530,10 @@ function describeCiOwnership(params) {
|
|
|
524
530
|
: "PatchRelay owns the next requested-changes move";
|
|
525
531
|
}
|
|
526
532
|
if (params.owner === "review-quill") {
|
|
527
|
-
|
|
533
|
+
if (params.reviewQuillAttempt?.backlog) {
|
|
534
|
+
return "review-quill is actively reconciling this repo; this PR is waiting in the current review backlog";
|
|
535
|
+
}
|
|
536
|
+
return params.reviewQuillAttempt?.id && params.reviewQuillAttempt.status
|
|
528
537
|
? `review-quill attempt #${params.reviewQuillAttempt.id} is ${params.reviewQuillAttempt.status} on the current head`
|
|
529
538
|
: "review-quill owns the current review attempt";
|
|
530
539
|
}
|
|
@@ -576,7 +585,14 @@ function describeCiOwnership(params) {
|
|
|
576
585
|
return "No visible next owner for this PR state";
|
|
577
586
|
}
|
|
578
587
|
function isResolvedDependency(dep) {
|
|
579
|
-
|
|
588
|
+
const stateType = dep.blockerCurrentLinearStateType?.trim().toLowerCase();
|
|
589
|
+
const state = dep.blockerCurrentLinearState?.trim().toLowerCase();
|
|
590
|
+
return stateType === "completed"
|
|
591
|
+
|| stateType === "canceled"
|
|
592
|
+
|| stateType === "cancelled"
|
|
593
|
+
|| state === "done"
|
|
594
|
+
|| state === "canceled"
|
|
595
|
+
|| state === "cancelled";
|
|
580
596
|
}
|
|
581
597
|
function needsReviewAutomation(issue) {
|
|
582
598
|
if (issue.factoryState === "awaiting_queue" || issue.factoryState === "done") {
|
|
@@ -586,6 +602,7 @@ function needsReviewAutomation(issue) {
|
|
|
586
602
|
}
|
|
587
603
|
async function collectReviewQuillAttemptOwners(snapshots, config, runCommand) {
|
|
588
604
|
const owners = new Map();
|
|
605
|
+
const repoBacklog = await probeReviewQuillRepoBacklog(runCommand);
|
|
589
606
|
for (const snapshot of snapshots) {
|
|
590
607
|
const issueKey = snapshot.issue.issueKey;
|
|
591
608
|
const prNumber = snapshot.issue.prNumber;
|
|
@@ -601,8 +618,12 @@ async function collectReviewQuillAttemptOwners(snapshots, config, runCommand) {
|
|
|
601
618
|
const activeAttempt = probe.attempts.find((attempt) => (attempt.status === "queued" || attempt.status === "running")
|
|
602
619
|
&& !attempt.stale
|
|
603
620
|
&& attempt.headSha === probe.currentHeadSha);
|
|
604
|
-
if (!activeAttempt)
|
|
621
|
+
if (!activeAttempt) {
|
|
622
|
+
if (repoBacklog.has(repoFullName)) {
|
|
623
|
+
owners.set(issueKey, { backlog: true, headSha: probe.latestAttemptHeadSha });
|
|
624
|
+
}
|
|
605
625
|
continue;
|
|
626
|
+
}
|
|
606
627
|
owners.set(issueKey, {
|
|
607
628
|
id: activeAttempt.id,
|
|
608
629
|
status: activeAttempt.status,
|
|
@@ -611,6 +632,42 @@ async function collectReviewQuillAttemptOwners(snapshots, config, runCommand) {
|
|
|
611
632
|
}
|
|
612
633
|
return owners;
|
|
613
634
|
}
|
|
635
|
+
async function probeReviewQuillRepoBacklog(runCommand) {
|
|
636
|
+
let result;
|
|
637
|
+
try {
|
|
638
|
+
result = await runCommand("review-quill", ["status", "--json"]);
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
return new Set();
|
|
642
|
+
}
|
|
643
|
+
if (result.exitCode !== 0) {
|
|
644
|
+
return new Set();
|
|
645
|
+
}
|
|
646
|
+
const parsed = safeJsonParse(result.stdout);
|
|
647
|
+
if (!parsed || parsed.runtime?.reconcileInProgress !== true || !Array.isArray(parsed.repos)) {
|
|
648
|
+
return new Set();
|
|
649
|
+
}
|
|
650
|
+
const activeRepos = new Set();
|
|
651
|
+
for (const repo of parsed.repos) {
|
|
652
|
+
if (!repo || typeof repo !== "object")
|
|
653
|
+
continue;
|
|
654
|
+
const repoFullName = typeof repo.repoFullName === "string"
|
|
655
|
+
? String(repo.repoFullName).trim()
|
|
656
|
+
: undefined;
|
|
657
|
+
const runningAttempts = typeof repo.runningAttempts === "number"
|
|
658
|
+
? Number(repo.runningAttempts)
|
|
659
|
+
: 0;
|
|
660
|
+
const queuedAttempts = typeof repo.queuedAttempts === "number"
|
|
661
|
+
? Number(repo.queuedAttempts)
|
|
662
|
+
: 0;
|
|
663
|
+
if (!repoFullName)
|
|
664
|
+
continue;
|
|
665
|
+
if (runningAttempts > 0 || queuedAttempts > 0) {
|
|
666
|
+
activeRepos.add(repoFullName);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return activeRepos;
|
|
670
|
+
}
|
|
614
671
|
async function collectActiveOverlapFindings(snapshots, runCommand) {
|
|
615
672
|
const findings = [];
|
|
616
673
|
const diffsByProject = new Map();
|
|
@@ -723,6 +780,7 @@ async function probeReviewQuillAttempts(runCommand, repoFullName, prNumber) {
|
|
|
723
780
|
if (!prProbe.ok) {
|
|
724
781
|
return { ok: false, error: prProbe.error };
|
|
725
782
|
}
|
|
783
|
+
let latestAttemptHeadSha;
|
|
726
784
|
const attempts = parsedAttempts.attempts.flatMap((entry) => {
|
|
727
785
|
if (!entry || typeof entry !== "object")
|
|
728
786
|
return [];
|
|
@@ -730,6 +788,9 @@ async function probeReviewQuillAttempts(runCommand, repoFullName, prNumber) {
|
|
|
730
788
|
const headSha = entry.headSha;
|
|
731
789
|
const status = entry.status;
|
|
732
790
|
const stale = entry.stale;
|
|
791
|
+
if (!latestAttemptHeadSha && typeof headSha === "string" && headSha.trim().length > 0) {
|
|
792
|
+
latestAttemptHeadSha = headSha.trim();
|
|
793
|
+
}
|
|
733
794
|
if (typeof id !== "number"
|
|
734
795
|
|| typeof headSha !== "string"
|
|
735
796
|
|| (status !== "queued" && status !== "running")) {
|
|
@@ -745,6 +806,7 @@ async function probeReviewQuillAttempts(runCommand, repoFullName, prNumber) {
|
|
|
745
806
|
return {
|
|
746
807
|
ok: true,
|
|
747
808
|
currentHeadSha: prProbe.pr.headRefOid,
|
|
809
|
+
latestAttemptHeadSha,
|
|
748
810
|
attempts,
|
|
749
811
|
};
|
|
750
812
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { issueTokenFor, prTokenFor } from "./issue-token.js";
|
|
4
|
-
import { truncate } from "./format-utils.js";
|
|
4
|
+
import { formatIssueAge, truncate } from "./format-utils.js";
|
|
5
5
|
const KEY_WIDTH = 8;
|
|
6
6
|
const GLYPH_WIDTH = 3;
|
|
7
7
|
const PHRASE_WIDTH = 18;
|
|
8
|
+
const AGE_WIDTH = 4;
|
|
9
|
+
const WIDE_PR_PHRASE_THRESHOLD = 34;
|
|
10
|
+
function shouldShowVerbosePrToken(titleWidth, compact) {
|
|
11
|
+
return !compact && (titleWidth ?? 60) >= WIDE_PR_PHRASE_THRESHOLD;
|
|
12
|
+
}
|
|
8
13
|
export function IssueRow({ issue, selected, titleWidth, compact = false, }) {
|
|
9
14
|
const key = issue.issueKey ?? issue.projectId;
|
|
10
15
|
const token = issueTokenFor(issue);
|
|
@@ -12,11 +17,18 @@ export function IssueRow({ issue, selected, titleWidth, compact = false, }) {
|
|
|
12
17
|
const cursorChar = selected ? "\u25b8" : " ";
|
|
13
18
|
const paddedKey = key.padEnd(KEY_WIDTH, " ");
|
|
14
19
|
const paddedPhrase = token.phrase.padEnd(PHRASE_WIDTH, " ");
|
|
15
|
-
const
|
|
20
|
+
const age = formatIssueAge(issue.updatedAt);
|
|
21
|
+
const verbosePr = pr && shouldShowVerbosePrToken(titleWidth, compact);
|
|
22
|
+
const prWidth = pr
|
|
23
|
+
? verbosePr
|
|
24
|
+
? `#${pr.prNumber} ${pr.glyph} ${pr.phrase}`.length
|
|
25
|
+
: `#${pr.prNumber} ${pr.glyph}`.length
|
|
26
|
+
: 0;
|
|
27
|
+
const availableTitleWidth = Math.max(0, (titleWidth ?? 60) - prWidth - (pr ? 2 : 0) - (AGE_WIDTH + 2));
|
|
16
28
|
const title = !compact && selected && issue.title
|
|
17
29
|
? ` ${truncate(issue.title, Math.max(0, availableTitleWidth))}`
|
|
18
30
|
: "";
|
|
19
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : "gray", children: cursorChar }), _jsx(Text, { bold: selected, color: token.color, children: ` ${paddedKey}` }), _jsx(Text, { color: token.color, children: ` ${token.glyph.padEnd(GLYPH_WIDTH - 1, " ")}` }), _jsx(Text, { children: ` ${paddedPhrase}` }), pr ? (_jsx(_Fragment, { children: _jsx(Text, { color: pr.color, children: `#${pr.prNumber} ${pr.glyph}` }) })) : null, title ? _jsx(Text, { dimColor: true, children: title }) : null] }));
|
|
31
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : "gray", children: cursorChar }), _jsx(Text, { bold: selected, color: token.color, children: ` ${paddedKey}` }), _jsx(Text, { color: token.color, children: ` ${token.glyph.padEnd(GLYPH_WIDTH - 1, " ")}` }), _jsx(Text, { children: ` ${paddedPhrase}` }), pr ? (_jsx(_Fragment, { children: _jsx(Text, { color: pr.color, children: verbosePr ? `#${pr.prNumber} ${pr.glyph} ${pr.phrase}` : `#${pr.prNumber} ${pr.glyph}` }) })) : null, _jsx(Text, { dimColor: true, children: ` ${age}` }), title ? _jsx(Text, { dimColor: true, children: title }) : null] }));
|
|
20
32
|
}
|
|
21
33
|
export function estimateIssueRowHeight(_issue, _selected, _cols, _titleWidth, _compact = false) {
|
|
22
34
|
return 1;
|
|
@@ -19,6 +19,9 @@ export function relativeTime(iso) {
|
|
|
19
19
|
const days = Math.floor(hours / 24);
|
|
20
20
|
return `${days}d`;
|
|
21
21
|
}
|
|
22
|
+
export function formatIssueAge(updatedAt) {
|
|
23
|
+
return relativeTime(updatedAt).padStart(4, " ");
|
|
24
|
+
}
|
|
22
25
|
/** Format millisecond duration as "2m 30s" or "45s". */
|
|
23
26
|
export function formatDuration(ms) {
|
|
24
27
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isUndelegatedPausedIssue } from "../../paused-issue-state.js";
|
|
2
|
+
import { hasFailedPrChecks, hasPendingPrChecks, isApprovedReviewState, isAwaitingReviewState, isChangesRequestedReviewState, prChecksFact, } from "./pr-status.js";
|
|
2
3
|
const GLYPH = {
|
|
3
4
|
running: "\u25cf",
|
|
4
5
|
queued: "\u25cb",
|
|
@@ -84,6 +85,7 @@ export function prTokenFor(issue) {
|
|
|
84
85
|
glyph: GLYPH[kind],
|
|
85
86
|
color: COLOR[kind],
|
|
86
87
|
kind,
|
|
88
|
+
phrase: prPhraseFor(issue),
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
function prKind(issue) {
|
|
@@ -101,3 +103,20 @@ function prKind(issue) {
|
|
|
101
103
|
return "approved";
|
|
102
104
|
return "running";
|
|
103
105
|
}
|
|
106
|
+
function prPhraseFor(issue) {
|
|
107
|
+
if (issue.prState === "merged")
|
|
108
|
+
return "merged";
|
|
109
|
+
if (issue.prState === "closed")
|
|
110
|
+
return "closed";
|
|
111
|
+
if (isChangesRequestedReviewState(issue.prReviewState))
|
|
112
|
+
return "changes req";
|
|
113
|
+
if (isApprovedReviewState(issue.prReviewState))
|
|
114
|
+
return "approved";
|
|
115
|
+
if (isAwaitingReviewState(issue.prReviewState))
|
|
116
|
+
return "awaiting review";
|
|
117
|
+
if (hasFailedPrChecks(issue))
|
|
118
|
+
return prChecksFact(issue)?.text ?? "checks failed";
|
|
119
|
+
if (hasPendingPrChecks(issue))
|
|
120
|
+
return prChecksFact(issue)?.text ?? "checks running";
|
|
121
|
+
return prChecksFact(issue)?.text ?? "open";
|
|
122
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -22,9 +22,9 @@ const repoSettingsSchema = z.object({
|
|
|
22
22
|
branch_prefix: z.string().min(1).optional(),
|
|
23
23
|
});
|
|
24
24
|
const repairBudgetsSchema = z.object({
|
|
25
|
-
ci_repair: z.number().int().positive().default(
|
|
26
|
-
queue_repair: z.number().int().positive().default(
|
|
27
|
-
review_fix: z.number().int().positive().default(
|
|
25
|
+
ci_repair: z.number().int().positive().default(10),
|
|
26
|
+
queue_repair: z.number().int().positive().default(10),
|
|
27
|
+
review_fix: z.number().int().positive().default(10),
|
|
28
28
|
});
|
|
29
29
|
const projectSchema = z.object({
|
|
30
30
|
id: z.string().min(1),
|
|
@@ -37,9 +37,9 @@ const projectSchema = z.object({
|
|
|
37
37
|
trigger_events: z.array(z.string().min(1)).min(1).optional(),
|
|
38
38
|
branch_prefix: z.string().min(1).optional(),
|
|
39
39
|
repair_budgets: repairBudgetsSchema.default({
|
|
40
|
-
ci_repair:
|
|
41
|
-
queue_repair:
|
|
42
|
-
review_fix:
|
|
40
|
+
ci_repair: 10,
|
|
41
|
+
queue_repair: 10,
|
|
42
|
+
review_fix: 10,
|
|
43
43
|
}),
|
|
44
44
|
/** Check names that are review gates (AI Review, quality analysis). Default: code class. */
|
|
45
45
|
review_checks: z.array(z.string().min(1)).default([]),
|
|
@@ -63,9 +63,9 @@ const repositorySchema = z.object({
|
|
|
63
63
|
trigger_events: z.array(z.string().min(1)).min(1).optional(),
|
|
64
64
|
branch_prefix: z.string().min(1).optional(),
|
|
65
65
|
repair_budgets: repairBudgetsSchema.default({
|
|
66
|
-
ci_repair:
|
|
67
|
-
queue_repair:
|
|
68
|
-
review_fix:
|
|
66
|
+
ci_repair: 10,
|
|
67
|
+
queue_repair: 10,
|
|
68
|
+
review_fix: 10,
|
|
69
69
|
}),
|
|
70
70
|
github: z.object({
|
|
71
71
|
webhook_secret: z.string().min(1).optional(),
|
package/dist/issue-class.js
CHANGED
|
@@ -1,35 +1,15 @@
|
|
|
1
|
-
function normalizeText(value) {
|
|
2
|
-
return value?.trim().toLowerCase() ?? "";
|
|
3
|
-
}
|
|
4
|
-
function looksLikeUmbrellaText(issue) {
|
|
5
|
-
const haystack = `${normalizeText(issue.title)}\n${normalizeText(issue.description)}`;
|
|
6
|
-
if (!haystack.trim())
|
|
7
|
-
return false;
|
|
8
|
-
return [
|
|
9
|
-
"umbrella",
|
|
10
|
-
"tracker",
|
|
11
|
-
"tracking",
|
|
12
|
-
"rollout",
|
|
13
|
-
"migration",
|
|
14
|
-
"convergence",
|
|
15
|
-
"audit",
|
|
16
|
-
"follow-up issues",
|
|
17
|
-
"planning/specification issue only",
|
|
18
|
-
].some((token) => haystack.includes(token));
|
|
19
|
-
}
|
|
20
1
|
export function classifyIssue(params) {
|
|
21
|
-
if (params.issue.issueClassSource === "explicit"
|
|
22
|
-
&& (params.issue.issueClass === "implementation" || params.issue.issueClass === "orchestration")) {
|
|
23
|
-
return { issueClass: params.issue.issueClass, issueClassSource: "explicit" };
|
|
24
|
-
}
|
|
25
2
|
if (params.issue.parentLinearIssueId) {
|
|
26
3
|
return { issueClass: "implementation", issueClassSource: "hierarchy" };
|
|
27
4
|
}
|
|
28
5
|
if (params.childIssueCount > 0) {
|
|
6
|
+
if (params.issue.issueClassSource === "explicit" && params.issue.issueClass === "orchestration") {
|
|
7
|
+
return { issueClass: "orchestration", issueClassSource: "explicit" };
|
|
8
|
+
}
|
|
29
9
|
return { issueClass: "orchestration", issueClassSource: "hierarchy" };
|
|
30
10
|
}
|
|
31
|
-
if (
|
|
32
|
-
return { issueClass: "
|
|
11
|
+
if (params.issue.issueClassSource === "explicit" && params.issue.issueClass === "implementation") {
|
|
12
|
+
return { issueClass: "implementation", issueClassSource: "explicit" };
|
|
33
13
|
}
|
|
34
14
|
return { issueClass: "implementation", issueClassSource: "heuristic" };
|
|
35
15
|
}
|
|
@@ -182,10 +182,15 @@ function buildOrchestrationScopeDiscipline(context) {
|
|
|
182
182
|
"Adopt already-existing canonical child issues when they cover the intended split.",
|
|
183
183
|
"Do not recreate child issues that already exist under this parent unless a genuinely missing required slice remains.",
|
|
184
184
|
"Do not create an overlapping umbrella PR unless this parent clearly owns unique direct cleanup work that child issues do not already cover.",
|
|
185
|
+
"When you split required implementation work out of the parent, make it a child issue of this umbrella rather than making the parent block the child.",
|
|
186
|
+
"If sequencing matters, let the parent wait on the child; do not model the child as blocked by the parent umbrella it is supposed to satisfy.",
|
|
185
187
|
"If child work is still in motion, babysit the plan, record useful observations, and return to waiting.",
|
|
186
188
|
"If child work looks delivered, audit whether the original parent goal is actually satisfied.",
|
|
187
189
|
"Create blocking follow-up work only when it is necessary to satisfy the original parent goal.",
|
|
188
190
|
"Prefer non-blocking follow-up issues over keeping the umbrella open for optional polish or adjacent expansion.",
|
|
191
|
+
"New child issues should stay in Backlog and undelegated by default.",
|
|
192
|
+
"Only delegate or move a new child to Start when it is immediately actionable, unblocked, and you intend for PatchRelay to begin it right away.",
|
|
193
|
+
"If you create multiple new child issues, keep later-wave or dependency-bound children queued rather than waking them all at once.",
|
|
189
194
|
"",
|
|
190
195
|
"### Child Issue Summaries",
|
|
191
196
|
"",
|
|
@@ -476,6 +481,7 @@ function buildOrchestrationWorkflowGuidance() {
|
|
|
476
481
|
"",
|
|
477
482
|
"Use the wake reason and current child issue summaries to decide what kind of orchestration work is needed now.",
|
|
478
483
|
"Typical orchestration phases are: initial setup, waiting on child progress, reviewing delivered child work, final audit, creating a justified follow-up, or closing the umbrella.",
|
|
484
|
+
"When creating follow-up work, prefer one immediately runnable child at a time and keep the rest queued until their prerequisites are genuinely ready.",
|
|
479
485
|
"Keep outputs concise and observable in Linear.",
|
|
480
486
|
].join("\n");
|
|
481
487
|
}
|
|
@@ -488,6 +494,8 @@ function buildPublicationContract(runType, issueClass) {
|
|
|
488
494
|
"By default, orchestration work should finish without opening an overlapping umbrella PR.",
|
|
489
495
|
"Valid orchestration outcomes include: recording an observation, updating the rollout plan, creating follow-up issues, opening a small cleanup PR that the parent clearly owns, or closing the umbrella.",
|
|
490
496
|
"If you create new blocking follow-up work, justify it against the original parent goal rather than optional polish.",
|
|
497
|
+
"Blocking follow-up children must block the parent goal they satisfy, not the other way around.",
|
|
498
|
+
"If you create new child issues, leave them in Backlog unless they are truly ready to start now.",
|
|
491
499
|
].join("\n");
|
|
492
500
|
}
|
|
493
501
|
if (runType === "implementation") {
|
package/dist/run-budgets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export const DEFAULT_CI_REPAIR_BUDGET =
|
|
2
|
-
export const DEFAULT_QUEUE_REPAIR_BUDGET =
|
|
3
|
-
export const DEFAULT_REVIEW_FIX_BUDGET =
|
|
1
|
+
export const DEFAULT_CI_REPAIR_BUDGET = 10;
|
|
2
|
+
export const DEFAULT_QUEUE_REPAIR_BUDGET = 10;
|
|
3
|
+
export const DEFAULT_REVIEW_FIX_BUDGET = 10;
|
|
4
4
|
export function getCiRepairBudget(project) {
|
|
5
5
|
return project?.repairBudgets?.ciRepair ?? DEFAULT_CI_REPAIR_BUDGET;
|
|
6
6
|
}
|
|
@@ -304,13 +304,15 @@ export class DesiredStageRecorder {
|
|
|
304
304
|
? "child_delivered"
|
|
305
305
|
: wasResolved && !isResolved
|
|
306
306
|
? "child_regressed"
|
|
307
|
-
:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
: undefined;
|
|
308
|
+
if (eventType) {
|
|
309
|
+
wakeOrchestrationParentsForChildEvent({
|
|
310
|
+
db: this.db,
|
|
311
|
+
child: issue,
|
|
312
|
+
eventType,
|
|
313
|
+
changeKind,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
314
316
|
}
|
|
315
317
|
return {
|
|
316
318
|
issue: this.db.issueToTrackedIssue(issue),
|