pr-report-core 0.2.0 → 0.3.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.
Files changed (107) hide show
  1. package/README.md +5 -2
  2. package/dist/branch-rules.d.mts +30 -0
  3. package/dist/branch-rules.d.mts.map +1 -0
  4. package/dist/branch-rules.mjs +71 -0
  5. package/dist/branch-rules.mjs.map +1 -0
  6. package/dist/checks.d.mts +61 -62
  7. package/dist/checks.d.mts.map +1 -1
  8. package/dist/checks.mjs +0 -1
  9. package/dist/checks.mjs.map +1 -1
  10. package/dist/claim.d.mts +21 -15
  11. package/dist/claim.d.mts.map +1 -1
  12. package/dist/claim.mjs +1 -1
  13. package/dist/claim.mjs.map +1 -1
  14. package/dist/closing-issues.d.mts +18 -19
  15. package/dist/closing-issues.d.mts.map +1 -1
  16. package/dist/closing-issues.mjs +0 -1
  17. package/dist/closing-issues.mjs.map +1 -1
  18. package/dist/code-owners.d.mts +33 -34
  19. package/dist/code-owners.d.mts.map +1 -1
  20. package/dist/code-owners.mjs +0 -2
  21. package/dist/code-owners.mjs.map +1 -1
  22. package/dist/index.d.mts +3 -0
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +3 -0
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/issue-claim.d.mts +56 -0
  27. package/dist/issue-claim.d.mts.map +1 -0
  28. package/dist/issue-claim.mjs +87 -0
  29. package/dist/issue-claim.mjs.map +1 -0
  30. package/dist/issue-priority.d.mts +44 -0
  31. package/dist/issue-priority.d.mts.map +1 -0
  32. package/dist/issue-priority.mjs +91 -0
  33. package/dist/issue-priority.mjs.map +1 -0
  34. package/dist/labels.d.mts +43 -42
  35. package/dist/labels.d.mts.map +1 -1
  36. package/dist/labels.mjs +2 -1
  37. package/dist/labels.mjs.map +1 -1
  38. package/dist/merge-after.d.mts +22 -16
  39. package/dist/merge-after.d.mts.map +1 -1
  40. package/dist/merge-after.mjs +87 -24
  41. package/dist/merge-after.mjs.map +1 -1
  42. package/dist/queue-comments.d.mts +2 -2
  43. package/dist/queue-order.d.mts +32 -32
  44. package/dist/queue-order.mjs +0 -2
  45. package/dist/queue-order.mjs.map +1 -1
  46. package/dist/queue-owner.d.mts +14 -14
  47. package/dist/queue-owner.d.mts.map +1 -1
  48. package/dist/queue-owner.mjs +18 -0
  49. package/dist/queue-owner.mjs.map +1 -1
  50. package/dist/report.d.mts +4 -4
  51. package/dist/review-hold.d.mts +14 -14
  52. package/dist/ruleset.d.mts +12 -13
  53. package/dist/ruleset.d.mts.map +1 -1
  54. package/dist/ruleset.mjs +0 -1
  55. package/dist/ruleset.mjs.map +1 -1
  56. package/dist/set-aside.d.mts +29 -28
  57. package/dist/set-aside.d.mts.map +1 -1
  58. package/dist/set-aside.mjs +2 -0
  59. package/dist/set-aside.mjs.map +1 -1
  60. package/dist/stack.d.mts +14 -15
  61. package/dist/stack.d.mts.map +1 -1
  62. package/dist/stack.mjs +0 -1
  63. package/dist/stack.mjs.map +1 -1
  64. package/dist/start-after.d.mts +20 -21
  65. package/dist/start-after.d.mts.map +1 -1
  66. package/dist/start-after.mjs +0 -1
  67. package/dist/start-after.mjs.map +1 -1
  68. package/dist/summarize.d.mts +2 -2
  69. package/dist/timestamp.d.mts +10 -10
  70. package/dist/tree.d.mts +5 -5
  71. package/dist/types.d.mts +32 -33
  72. package/dist/types.d.mts.map +1 -1
  73. package/package.json +10 -10
  74. package/src/branch-rules.mts +105 -0
  75. package/src/branch-rules.test.mts +221 -0
  76. package/src/checks.mts +64 -65
  77. package/src/checks.test.mts +30 -31
  78. package/src/claim.mts +16 -16
  79. package/src/closing-issues.mts +18 -19
  80. package/src/closing-issues.test.mts +2 -2
  81. package/src/code-owners.mts +39 -41
  82. package/src/index.mts +3 -0
  83. package/src/issue-claim.mts +138 -0
  84. package/src/issue-claim.test.mts +149 -0
  85. package/src/issue-priority.mts +116 -0
  86. package/src/issue-priority.test.mts +71 -0
  87. package/src/labels.mts +43 -42
  88. package/src/merge-after.mts +142 -49
  89. package/src/merge-after.test.mts +80 -3
  90. package/src/queue-comments.mts +2 -2
  91. package/src/queue-order.mts +60 -62
  92. package/src/queue-order.test.mts +6 -6
  93. package/src/queue-owner.mts +46 -25
  94. package/src/queue-owner.test.mts +4 -2
  95. package/src/report.mts +9 -9
  96. package/src/review-hold.mts +14 -14
  97. package/src/review-hold.test.mts +3 -3
  98. package/src/ruleset.mts +12 -13
  99. package/src/set-aside.mts +30 -28
  100. package/src/set-aside.test.mts +22 -0
  101. package/src/stack.mts +14 -15
  102. package/src/stack.test.mts +4 -4
  103. package/src/start-after.mts +20 -21
  104. package/src/summarize.mts +2 -2
  105. package/src/timestamp.mts +14 -14
  106. package/src/tree.mts +5 -5
  107. package/src/types.mts +32 -33
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Who has started an issue, or means to, written where every session can read
3
+ * it.
4
+ *
5
+ * The `in-progress` and `planned` labels (`labels.mts`) say that someone is on
6
+ * an issue, not who: every session writes as the same account, so neither the
7
+ * labels nor the assignees tell two of them apart, and a session refused by a
8
+ * label cannot say whose it is. So `start-issue` also writes a comment whose
9
+ * first line is a hidden record this module writes and reads, naming the
10
+ * claimant (a session URL, or any other word unique to it; `claim.mts`'s
11
+ * `isClaimant`).
12
+ *
13
+ * **A start lasts until it is released**, as the label always has: the pull
14
+ * request that closes the issue is what ends it. **A plan lapses** after
15
+ * {@link PLANNED_CLAIM_LIFETIME_HOURS}, since it holds nothing back and a
16
+ * session that meant to start next may never get there; a claimant still
17
+ * meaning to writes it again.
18
+ *
19
+ * **One comment per issue, edited in place**, as for the pull request claim; of
20
+ * several, the oldest is the one, so two claimants writing at once both read
21
+ * back the same answer — which a label cannot give, having no order to read.
22
+ *
23
+ * **Only the comments the reader's own account wrote count**: the repository is
24
+ * public.
25
+ */
26
+
27
+ import { CLOCK_SKEW_MINUTES } from './claim.mjs';
28
+ import type { OwnComment, QueueComment } from './queue-comments.mjs';
29
+ import { epochMsOf } from './timestamp.mjs';
30
+
31
+ /** How long a plan lasts when it is not written again. */
32
+ export const PLANNED_CLAIM_LIFETIME_HOURS = 24;
33
+
34
+ export type IssueClaim = Readonly<{
35
+ /** Started (`in-progress`) or meant to be started next (`planned`). */
36
+ state: 'planned' | 'started';
37
+ /** Who claimed it: a session URL, or any other word unique to the claimant. */
38
+ by: string;
39
+ /** When, as a UTC instant to the second (`2026-10-12T01:00:00Z`). */
40
+ at: string;
41
+ }>;
42
+
43
+ export type IssueClaimComment = Readonly<
44
+ { kind: 'released' } | { kind: 'standing'; claim: IssueClaim }
45
+ >;
46
+
47
+ /** The record line, then `prose`. */
48
+ export const writeIssueClaimComment = (
49
+ claim: IssueClaim,
50
+ prose: string,
51
+ ): string =>
52
+ [
53
+ `${MARKER} state=${claim.state} by=${claim.by} at=${claim.at} -->`,
54
+ '',
55
+ prose.trim(),
56
+ ].join('\n');
57
+
58
+ /** The record line saying the claim was given up, and `prose`. */
59
+ export const writeReleasedIssueClaimComment = (prose: string): string =>
60
+ [`${MARKER} released -->`, '', prose.trim()].join('\n');
61
+
62
+ /** What the comment says, or `undefined` for a comment this module did not write. */
63
+ export const parseIssueClaimComment = (
64
+ body: string,
65
+ ): IssueClaimComment | undefined => {
66
+ const first = body.trim().split(/\r?\n/u, 1)[0]?.trim();
67
+
68
+ if (first === undefined) {
69
+ return undefined;
70
+ }
71
+
72
+ if (first === `${MARKER} released -->`) {
73
+ return { kind: 'released' };
74
+ }
75
+
76
+ const groups = STANDING_LINE.exec(first)?.groups;
77
+
78
+ const state = groups?.['state'];
79
+
80
+ const by = groups?.['by'];
81
+
82
+ const at = groups?.['at'];
83
+
84
+ return by === undefined ||
85
+ at === undefined ||
86
+ (state !== 'planned' && state !== 'started')
87
+ ? undefined
88
+ : { kind: 'standing', claim: { state, by, at } };
89
+ };
90
+
91
+ /** Whether `claim` still holds the issue at `nowEpochMs`. */
92
+ export const issueClaimApplies = (
93
+ claim: IssueClaim,
94
+ nowEpochMs: number,
95
+ ): boolean => {
96
+ const at = epochMsOf(claim.at);
97
+
98
+ return (
99
+ at !== undefined &&
100
+ at - CLOCK_SKEW_MINUTES * MS_PER_MINUTE <= nowEpochMs &&
101
+ (claim.state === 'started' ||
102
+ nowEpochMs < at + PLANNED_CLAIM_LIFETIME_HOURS * MS_PER_HOUR)
103
+ );
104
+ };
105
+
106
+ /**
107
+ * Reads `comments`, oldest first, for the oldest claim comment, and whether it
108
+ * still holds; `undefined` when there is none.
109
+ */
110
+ export const readIssueClaim = (
111
+ comments: readonly OwnComment[],
112
+ nowEpochMs: number,
113
+ ): QueueComment<IssueClaimComment> | undefined => {
114
+ for (const { id, body } of comments) {
115
+ const says = parseIssueClaimComment(body);
116
+
117
+ if (says !== undefined) {
118
+ return {
119
+ commentId: id,
120
+ says,
121
+ applies:
122
+ says.kind === 'standing' && issueClaimApplies(says.claim, nowEpochMs),
123
+ };
124
+ }
125
+ }
126
+
127
+ return undefined;
128
+ };
129
+
130
+ const MS_PER_MINUTE = 60_000;
131
+
132
+ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
133
+
134
+ /** The first line starts with this, so the comment is found by it. */
135
+ const MARKER = '<!-- start-issue:claim';
136
+
137
+ const STANDING_LINE =
138
+ /^<!-- start-issue:claim state=(?<state>planned|started) by=(?<by>[^\s>]+) at=(?<at>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) -->$/u;
@@ -0,0 +1,149 @@
1
+ import {
2
+ issueClaimApplies,
3
+ parseIssueClaimComment,
4
+ PLANNED_CLAIM_LIFETIME_HOURS,
5
+ readIssueClaim,
6
+ writeIssueClaimComment,
7
+ writeReleasedIssueClaimComment,
8
+ type IssueClaim,
9
+ } from './issue-claim.mjs';
10
+
11
+ const STARTED: IssueClaim = {
12
+ state: 'started',
13
+ by: 'https://claude.ai/code/session_01abc',
14
+ at: '2026-10-12T01:00:00Z',
15
+ } as const;
16
+
17
+ const PLANNED: IssueClaim = { ...STARTED, state: 'planned' } as const;
18
+
19
+ /** `STARTED.at`. */
20
+ const AT = 1_791_766_800_000;
21
+
22
+ const MINUTE = 60_000;
23
+
24
+ const HOUR = 60 * MINUTE;
25
+
26
+ describe(writeIssueClaimComment, () => {
27
+ test('says who claimed it, how and when, in a form it can read back', () => {
28
+ assert.deepStrictEqual(
29
+ parseIssueClaimComment(writeIssueClaimComment(STARTED, 'Started.')),
30
+ { kind: 'standing', claim: STARTED },
31
+ );
32
+
33
+ assert.deepStrictEqual(
34
+ parseIssueClaimComment(writeIssueClaimComment(PLANNED, 'Planned.')),
35
+ { kind: 'standing', claim: PLANNED },
36
+ );
37
+ });
38
+
39
+ test('keeps the record out of the prose', () => {
40
+ const lines = writeIssueClaimComment(STARTED, 'Started.').split('\n');
41
+
42
+ assert.match(lines[0] ?? '', /^<!--.*-->$/u);
43
+
44
+ assert.strictEqual(lines[2], 'Started.');
45
+ });
46
+ });
47
+
48
+ describe(parseIssueClaimComment, () => {
49
+ test('reads a released claim', () => {
50
+ assert.deepStrictEqual(
51
+ parseIssueClaimComment(writeReleasedIssueClaimComment('Dropped.')),
52
+ { kind: 'released' },
53
+ );
54
+ });
55
+
56
+ test('is not a pull request claim, and a pull request claim is not one', () => {
57
+ assert.isUndefined(
58
+ parseIssueClaimComment(
59
+ `<!-- unblock-prs:claim head=${'c'.repeat(40)} by=x at=2026-10-12T01:00:00Z -->`,
60
+ ),
61
+ );
62
+ });
63
+
64
+ test('reads nothing from a comment it did not write', () => {
65
+ assert.isUndefined(parseIssueClaimComment('I will take this one.'));
66
+
67
+ assert.isUndefined(
68
+ parseIssueClaimComment(
69
+ '<!-- start-issue:claim state=done by=x at=2026-10-12T01:00:00Z -->',
70
+ ),
71
+ );
72
+ });
73
+ });
74
+
75
+ describe(issueClaimApplies, () => {
76
+ test('a start holds until it is released, however old', () => {
77
+ assert.isTrue(issueClaimApplies(STARTED, AT + 90 * 24 * HOUR));
78
+ });
79
+
80
+ test('a plan lapses after its lifetime', () => {
81
+ assert.isTrue(
82
+ issueClaimApplies(PLANNED, AT + PLANNED_CLAIM_LIFETIME_HOURS * HOUR - 1),
83
+ );
84
+
85
+ assert.isFalse(
86
+ issueClaimApplies(PLANNED, AT + PLANNED_CLAIM_LIFETIME_HOURS * HOUR),
87
+ );
88
+ });
89
+
90
+ test('neither counts when dated well ahead of the clock', () => {
91
+ assert.isTrue(issueClaimApplies(STARTED, AT - 5 * MINUTE));
92
+
93
+ assert.isFalse(issueClaimApplies(STARTED, AT - 6 * MINUTE));
94
+
95
+ assert.isFalse(issueClaimApplies(PLANNED, AT - 6 * MINUTE));
96
+ });
97
+ });
98
+
99
+ describe(readIssueClaim, () => {
100
+ test('is the oldest claim comment, whatever else is around it', () => {
101
+ assert.deepStrictEqual(
102
+ readIssueClaim(
103
+ [
104
+ { id: 1, body: 'LGTM' },
105
+ { id: 2, body: writeIssueClaimComment(STARTED, 'Started.') },
106
+ {
107
+ id: 3,
108
+ body: writeIssueClaimComment(
109
+ { ...STARTED, by: 'another-session' },
110
+ 'Started.',
111
+ ),
112
+ },
113
+ ],
114
+ AT,
115
+ ),
116
+ {
117
+ commentId: 2,
118
+ says: { kind: 'standing', claim: STARTED },
119
+ applies: true,
120
+ },
121
+ );
122
+ });
123
+
124
+ test('says when a claim no longer holds', () => {
125
+ assert.deepStrictEqual(
126
+ readIssueClaim(
127
+ [{ id: 4, body: writeReleasedIssueClaimComment('Dropped.') }],
128
+ AT,
129
+ ),
130
+ { commentId: 4, says: { kind: 'released' }, applies: false },
131
+ );
132
+
133
+ assert.deepStrictEqual(
134
+ readIssueClaim(
135
+ [{ id: 5, body: writeIssueClaimComment(PLANNED, 'Planned.') }],
136
+ AT + PLANNED_CLAIM_LIFETIME_HOURS * HOUR,
137
+ ),
138
+ {
139
+ commentId: 5,
140
+ says: { kind: 'standing', claim: PLANNED },
141
+ applies: false,
142
+ },
143
+ );
144
+ });
145
+
146
+ test('is undefined with no claim comment', () => {
147
+ assert.isUndefined(readIssueClaim([{ id: 1, body: 'LGTM' }], AT));
148
+ });
149
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * How soon an open issue should be started: one `priority:<tier>` label each,
3
+ * decided when the issue is filed. What each tier means, and how an issue is
4
+ * placed in one, is `docs/issue-priority.md`.
5
+ *
6
+ * These are issue labels. A pull request's place in the merge queue is
7
+ * `priority:high` / `priority:low` (`labels.mts`), which no issue carries.
8
+ */
9
+
10
+ /** The tier the one `priority:<tier>` label names, `none`, or `several`. */
11
+ export const issuePriorityOfLabels = (
12
+ labels: readonly string[],
13
+ ): IssuePriority | 'none' | 'several' => {
14
+ const carried = ISSUE_PRIORITIES.filter((priority) =>
15
+ labels.includes(issuePriorityLabel(priority)),
16
+ );
17
+
18
+ switch (carried.length) {
19
+ case 0:
20
+ return 'none';
21
+
22
+ case 1:
23
+ return carried[0] ?? 'none';
24
+
25
+ default:
26
+ return 'several';
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Where an issue goes in the order it is started in, as a sort key: `S` first,
32
+ * then `A`, `B` and `C`, then one with no tier or several, then `D`. What
33
+ * nobody has placed is not known to matter less than what was placed last.
34
+ * `hold` and `close` rank with no tier here; whoever takes issues in this order
35
+ * leaves them out first.
36
+ */
37
+ export const issueStartRank = (labels: readonly string[]): number => {
38
+ const priority = issuePriorityOfLabels(labels);
39
+
40
+ return priority === 'none' || priority === 'several'
41
+ ? UNPLACED_RANK
42
+ : rankOf(priority);
43
+ };
44
+
45
+ /** The tier as written on a command line, or `undefined` for anything else. */
46
+ export const parseIssuePriority = (raw: string): IssuePriority | undefined =>
47
+ ISSUE_PRIORITIES.find((priority) => priority === raw);
48
+
49
+ export const issuePriorityLabel = (
50
+ priority: IssuePriority,
51
+ ): `priority:${IssuePriority}` => `priority:${priority}` as const;
52
+
53
+ /**
54
+ * Every tier, the order `docs/issue-priority.md` lists them in.
55
+ *
56
+ * - `S` — speeds up the work on everything else now: CI, `check-changed`, the
57
+ * merge queue, the review loop, a step a person does by hand most days.
58
+ * - `A` — what the rest of that stands on, a step a person does by hand on
59
+ * every release or deploy, a security issue, or a bug real users meet that
60
+ * stops what they came to do.
61
+ * - `B` — the other bugs in an app with users or in a published package, and
62
+ * smaller gains in speed.
63
+ * - `C` — documents, refactoring, small improvements and features.
64
+ * - `D` — an app nobody uses but its author, and large features.
65
+ * - `hold` — not to be started as things stand: code about to be replaced, or
66
+ * work waiting on a decision.
67
+ * - `close` — to be closed or folded into another issue at the next triage.
68
+ *
69
+ * `pr-manager`'s tier tooltips (`issue-tier-group.tsx`) repeat this list.
70
+ */
71
+ export const ISSUE_PRIORITIES = [
72
+ 'S',
73
+ 'A',
74
+ 'B',
75
+ 'C',
76
+ 'D',
77
+ 'hold',
78
+ 'close',
79
+ ] as const;
80
+
81
+ export type IssuePriority = (typeof ISSUE_PRIORITIES)[number];
82
+
83
+ /** The tiers a new issue may be filed under: `close` is a verdict on one. */
84
+ export const FILING_PRIORITIES = [
85
+ 'S',
86
+ 'A',
87
+ 'B',
88
+ 'C',
89
+ 'D',
90
+ 'hold',
91
+ ] as const satisfies readonly IssuePriority[];
92
+
93
+ const UNPLACED_RANK = 4;
94
+
95
+ const rankOf = (priority: IssuePriority): number => {
96
+ switch (priority) {
97
+ case 'S':
98
+ return 0;
99
+
100
+ case 'A':
101
+ return 1;
102
+
103
+ case 'B':
104
+ return 2;
105
+
106
+ case 'C':
107
+ return 3;
108
+
109
+ case 'D':
110
+ return 5;
111
+
112
+ case 'hold':
113
+ case 'close':
114
+ return UNPLACED_RANK;
115
+ }
116
+ };
@@ -0,0 +1,71 @@
1
+ import {
2
+ issuePriorityLabel,
3
+ issuePriorityOfLabels,
4
+ issueStartRank,
5
+ parseIssuePriority,
6
+ } from './issue-priority.mjs';
7
+
8
+ describe(issuePriorityOfLabels, () => {
9
+ test('reads the one priority label an issue carries', () => {
10
+ assert.strictEqual(
11
+ issuePriorityOfLabels(['bug', 'priority:S', 'pending']),
12
+ 'S',
13
+ );
14
+
15
+ assert.strictEqual(issuePriorityOfLabels(['priority:hold']), 'hold');
16
+ });
17
+
18
+ test('says none for no priority label, and the pull request ones are not one', () => {
19
+ assert.strictEqual(issuePriorityOfLabels([]), 'none');
20
+
21
+ assert.strictEqual(
22
+ issuePriorityOfLabels(['priority:high', 'priority:low']),
23
+ 'none',
24
+ );
25
+ });
26
+
27
+ test('says several for more than one, which says nothing', () => {
28
+ assert.strictEqual(
29
+ issuePriorityOfLabels(['priority:A', 'priority:C']),
30
+ 'several',
31
+ );
32
+ });
33
+ });
34
+
35
+ describe(issueStartRank, () => {
36
+ test('ranks S first, then A, B and C, then what has no tier, then D', () => {
37
+ const ranks = [
38
+ ['priority:D'],
39
+ ['priority:S'],
40
+ [],
41
+ ['priority:C'],
42
+ ['priority:A', 'priority:B'],
43
+ ['priority:B'],
44
+ ['priority:A'],
45
+ ].map(issueStartRank);
46
+
47
+ assert.deepStrictEqual(ranks, [5, 0, 4, 3, 4, 2, 1]);
48
+ });
49
+ });
50
+
51
+ describe(parseIssuePriority, () => {
52
+ test('takes a tier as written', () => {
53
+ assert.strictEqual(parseIssuePriority('B'), 'B');
54
+
55
+ assert.strictEqual(parseIssuePriority('hold'), 'hold');
56
+ });
57
+
58
+ test('refuses anything else, the pull request priorities included', () => {
59
+ assert.isUndefined(parseIssuePriority('b'));
60
+
61
+ assert.isUndefined(parseIssuePriority('high'));
62
+
63
+ assert.isUndefined(parseIssuePriority('priority:S'));
64
+ });
65
+ });
66
+
67
+ describe(issuePriorityLabel, () => {
68
+ test('is the tier under priority:', () => {
69
+ assert.strictEqual(issuePriorityLabel('close'), 'priority:close');
70
+ });
71
+ });
package/src/labels.mts CHANGED
@@ -1,23 +1,23 @@
1
1
  /**
2
- * The labels this repository's queue is made of. They exist only on GitHub,
3
- * so these strings, and the ones in `.github/workflows/`, are the whole of
4
- * their declaration: `unblock-prs` and `open-pr` act on them, and
5
- * `pr-report` and the Pull Requests Manager page report them.
2
+ * The labels this repository's queue is made of. They exist only on GitHub, so
3
+ * these strings, and the ones in `.github/workflows/`, are the whole of their
4
+ * declaration: `unblock-prs` and `open-pr` act on them, and `pr-report` and the
5
+ * Pull Requests Manager page report them.
6
6
  */
7
7
 
8
8
  /**
9
- * The label that says "not yet": the check workflows skip while it is on
10
- * and `skip-ci-label.yml` holds the merge with a `pending` `no-skip-ci-label`
11
- * status. It is a label, deliberately — a `skip-ci` in the title means
12
- * nothing to any workflow.
9
+ * The label that says "not yet": the check workflows skip while it is on and
10
+ * `skip-ci-label.yml` holds the merge with a `pending` `no-skip-ci-label`
11
+ * status. It is a label, deliberately — a `skip-ci` in the title means nothing
12
+ * to any workflow.
13
13
  */
14
14
  export const SKIP_CI_LABEL = 'skip-ci';
15
15
 
16
16
  /**
17
- * The label that says a pull request has been reviewed and is waiting its
18
- * turn. It is the whole of `unblock-prs`'s scope rule: a pull request without
19
- * it is passed over in silence, and one with it is rebased, released from
20
- * `skip-ci` and watched when its turn comes.
17
+ * The label that says a pull request has been reviewed and is waiting its turn.
18
+ * It is the whole of `unblock-prs`'s scope rule: a pull request without it is
19
+ * passed over in silence, and one with it is rebased, released from `skip-ci`
20
+ * and watched when its turn comes.
21
21
  */
22
22
  export const MERGE_QUEUED_LABEL = 'merge-queued';
23
23
 
@@ -25,17 +25,16 @@ export const MERGE_QUEUED_LABEL = 'merge-queued';
25
25
  * The label a pull request carries to say the next release must contain it:
26
26
  * while it is open, the version pull request is not picked.
27
27
  *
28
- * It exists because the version pull request is the one pull request whose
29
- * body cannot declare a `Merge-After:` — `changesets/action` overwrites the
30
- * title and body of it on every push to the base — so the ordering is
31
- * declared from the other side, by the pull request the release is waiting
32
- * for. A label rather than a trailer for the same reason `merge-queued` is
33
- * one: this repository is public, and a label needs write access while a
34
- * comment does not.
28
+ * It exists because the version pull request is the one pull request whose body
29
+ * cannot declare a `Merge-After:` — `changesets/action` overwrites the title
30
+ * and body of it on every push to the base — so the ordering is declared from
31
+ * the other side, by the pull request the release is waiting for. A label
32
+ * rather than a trailer for the same reason `merge-queued` is one: this
33
+ * repository is public, and a label needs write access while a comment does
34
+ * not.
35
35
  *
36
- * Nothing ever takes it off. The constraint is "open", so it ends when the
37
- * pull request merges or is closed, and there is no state left behind to
38
- * clean up.
36
+ * Nothing ever takes it off. The constraint is "open", so it ends when the pull
37
+ * request merges or is closed, and there is no state left behind to clean up.
39
38
  */
40
39
  export const BLOCKS_RELEASE_LABEL = 'blocks-release';
41
40
 
@@ -43,22 +42,23 @@ export const BLOCKS_RELEASE_LABEL = 'blocks-release';
43
42
  * The label that asks `unblock-prs` to keep a pull request on the tip of the
44
43
  * default branch while it waits, so that its diff stays readable for review.
45
44
  * It is honoured only together with `skip-ci`, under which the push runs no
46
- * checks, and only when the queue has nothing to do. `open-pr` puts it on
47
- * every layer of a stack: one above the bottom is carried along by the layer
48
- * below, and needs the label of its own once that layer merges and it is
49
- * moved onto the default branch.
45
+ * checks, and only when the queue has nothing to do. `open-pr` puts it on every
46
+ * layer of a stack: one above the bottom is carried along by the layer below,
47
+ * and needs the label of its own once that layer merges and it is moved onto
48
+ * the default branch.
50
49
  */
51
50
  export const AUTO_REBASE_LABEL = 'auto-rebase';
52
51
 
53
52
  /**
54
53
  * The labels that move a pull request within `unblock-prs`'s pick order:
55
- * among the ones it may pick, those labelled `priority:high` go first and
56
- * those labelled `priority:low` last, the version pull request included.
57
- * They decide nothing else — what `Merge-After:`, a stack or
58
- * `blocks-release` holds back stays held, and a pull request already in
59
- * flight is watched whatever it carries. One carrying both counts as
60
- * neither. {@link PRIORITY_MID_LABEL} is not read there: it is the same as
61
- * carrying neither.
54
+ * among the ones it may pick, those labelled `priority:high` go first and those
55
+ * labelled `priority:low` last, the version pull request included.
56
+ * They decide nothing else — what `Merge-After:`, a stack or `blocks-release`
57
+ * holds back stays held, and a pull request already in flight is watched
58
+ * whatever it carries. One carrying both counts as neither. They are pull
59
+ * request labels: an issue's is a tier (`issue-priority.mts`).
60
+ * {@link PRIORITY_MID_LABEL} is not read there: it is the same as carrying
61
+ * neither.
62
62
  */
63
63
  export const PRIORITY_HIGH_LABEL = 'priority:high';
64
64
 
@@ -72,18 +72,19 @@ export const PRIORITY_MID_LABEL = 'priority:mid';
72
72
 
73
73
  /**
74
74
  * The label that says the author is holding a pull request back for now.
75
- * Also on GitHub, and read only by the Pull Requests Manager page, whose
76
- * merge order counts it and can hide what carries it. It asks nothing of
75
+ * Also on GitHub, and read only by the Pull Requests Manager page, whose merge
76
+ * order counts it and can hide what carries it. It asks nothing of
77
77
  * `unblock-prs`: what is held is simply not labelled `merge-queued`.
78
78
  */
79
79
  export const PENDING_LABEL = 'pending';
80
80
 
81
81
  /**
82
82
  * The label that says someone has started an issue, before there is a pull
83
- * request to say so. `start-issue` puts it on, and refuses an issue that
84
- * already carries it, so that two sessions do not pick up the same one; the
85
- * Pull Requests Manager page and `pr-report` show a startable issue carrying
86
- * it as in progress, as they show one an open pull request closes
83
+ * request to say so. `start-issue` puts it on, with a claim comment saying who
84
+ * (`issue-claim.mts`), and refuses an issue someone else claimed or that
85
+ * carries it unclaimed, so that two sessions do not pick up the same one; the
86
+ * Pull Requests Manager page and `pr-report` show a startable issue carrying it
87
+ * as in progress, as they show one an open pull request closes
87
88
  * (`start-after.mts`'s `progressOf`). Nothing takes it off when the work
88
89
  * merges: the issue closes, and the label with it stops mattering.
89
90
  */
@@ -92,8 +93,8 @@ export const IN_PROGRESS_LABEL = 'in-progress';
92
93
  /**
93
94
  * The label that says someone means to start an issue next. It holds nothing
94
95
  * back — `start-issue` takes it off as it puts {@link IN_PROGRESS_LABEL} on —
95
- * and only tells whoever is choosing what to start that this one is spoken
96
- * for; the page and `pr-report` show a startable issue carrying it as
97
- * planned.
96
+ * and only tells whoever is choosing what to start that this one is spoken for;
97
+ * `start-issue --plan` puts it on with a claim comment saying by whom;
98
+ * the page and `pr-report` show a startable issue carrying it as planned.
98
99
  */
99
100
  export const PLANNED_LABEL = 'planned';