mandrel 2.42.0 → 2.44.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 (25) hide show
  1. package/.agents/docs/agentrc-reference.json +4 -0
  2. package/.agents/docs/configuration.md +4 -1
  3. package/.agents/docs/workflows.md +1 -1
  4. package/.agents/schemas/agentrc.schema.json +20 -1
  5. package/.agents/scripts/lib/ITicketingProvider.js +27 -0
  6. package/.agents/scripts/lib/config-settings-schema.js +29 -1
  7. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  8. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  9. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-detect.js +187 -0
  10. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +37 -90
  11. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +115 -19
  12. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +10 -2
  13. package/.agents/scripts/lib/orchestration/plan-context.js +4 -0
  14. package/.agents/scripts/lib/orchestration/plan-run-labels/reap.js +327 -0
  15. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +9 -1
  16. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +159 -55
  17. package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +117 -36
  18. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +81 -0
  19. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-outcome.js +12 -7
  20. package/.agents/scripts/providers/github/labels.js +88 -0
  21. package/.agents/scripts/providers/github.js +2 -0
  22. package/.agents/scripts/prune-plan-run-labels.js +218 -0
  23. package/.agents/workflows/memory-consolidate.md +18 -6
  24. package/docs/CHANGELOG.md +20 -0
  25. package/package.json +1 -1
@@ -45,6 +45,28 @@
45
45
  * and that intent stands). It additionally reports a `degradations[]` array
46
46
  * naming **which** surface could not run and **why**, so the review outcome can
47
47
  * say "this gate did not run" instead of silently reading clean.
48
+ *
49
+ * ## The code surface never got the same treatment (Story #5193)
50
+ *
51
+ * Story #4839 gave the *markdown* surface a disk probe and left the code
52
+ * surface spawning `npx --no biome` and classifying the exit code. On npm 11.x
53
+ * that spawn exits **0 with empty output** when biome is absent, so
54
+ * `parseLintOutput` saw no failure, produced no degradation, and the surface
55
+ * contributed `errors: 0, warnings: 0` — a *silent false clean*, which is
56
+ * strictly worse than a degradation: a degraded surface announces itself, a
57
+ * falsely-clean one is trusted. Since nothing in the framework requires either
58
+ * runner, "absent" is the default state of a consumer checkout.
59
+ *
60
+ * Fix: runner resolution is a **precondition** for every surface, not an
61
+ * outcome inferred from an exit code. Both surfaces now resolve through
62
+ * {@link resolveRunner}, and an unresolved runner is never spawned.
63
+ *
64
+ * The same measurement invalidated the `NPX_UNRESOLVABLE` sentinel: current npm
65
+ * answers an unresolvable bin with `npm error code E404`, not `could not
66
+ * determine executable to run`, so every genuinely-unresolvable runner was
67
+ * being labelled `unparseable-output`. The sentinel now recognises both shapes.
68
+ * It still earns its keep after the disk probe: the probe only sees
69
+ * `node_modules/.bin`, so a runner resolvable some other way can still fail.
48
70
  */
49
71
 
50
72
  import { spawnSync } from 'node:child_process';
@@ -54,8 +76,14 @@ import path from 'node:path';
54
76
  /** Paths these extensions land on the biome (code) runner. */
55
77
  const CODE_EXTENSIONS = /\.(js|mjs|cjs|jsx|ts|tsx|json|jsonc)$/i;
56
78
 
57
- /** npx's message when the requested bin cannot be resolved. */
58
- const NPX_UNRESOLVABLE = /could not determine executable to run/i;
79
+ /**
80
+ * npx's output when the requested bin cannot be resolved. Two shapes: the
81
+ * legacy message, and the `E404` current npm answers with instead (measured
82
+ * 2026-09-07 on npm 11.13.0). Matching the E404 *code* rather than any
83
+ * `npm error` line keeps a genuine runner error out of this classification.
84
+ */
85
+ const NPX_UNRESOLVABLE =
86
+ /could not determine executable to run|npm (?:error|ERR!)\s+code\s+E404/i;
59
87
 
60
88
  /** Biome's exit-1 message when every supplied path is config-excluded. */
61
89
  const BIOME_EMPTY_SCOPE = /No files were processed in the specified paths/i;
@@ -74,6 +102,15 @@ const MARKDOWN_RUNNERS = Object.freeze([
74
102
  }),
75
103
  ]);
76
104
 
105
+ /**
106
+ * Code runners in preference order. `@biomejs/biome` installs a bare `biome`
107
+ * bin, which is also the canonical name this surface reports itself under when
108
+ * nothing resolves.
109
+ */
110
+ const CODE_RUNNERS = Object.freeze([
111
+ Object.freeze({ bin: 'biome', extraArgs: Object.freeze([]) }),
112
+ ]);
113
+
77
114
  /** Reason codes carried on a degradation record. */
78
115
  const DEGRADATION_REASONS = Object.freeze({
79
116
  RUNNER_NOT_INSTALLED: 'runner-not-installed',
@@ -103,24 +140,30 @@ function spawnLintRunner(bin, args, cwd) {
103
140
  }
104
141
 
105
142
  /**
106
- * Pure-ish: pick the first markdown runner whose bin is actually installed
107
- * under `<cwd>/node_modules/.bin`. Returns `null` when none is — an honest
108
- * "this surface has no runner" that the caller reports rather than silently
109
- * folding into a generic parse failure.
143
+ * Pure-ish: pick the first candidate whose bin is actually installed under
144
+ * `<cwd>/node_modules/.bin`. Returns `null` when none is — an honest "this
145
+ * surface has no runner" that the caller reports rather than silently folding
146
+ * into a generic parse failure.
110
147
  *
111
148
  * The disk probe (rather than "spawn and see") is what makes the failure
112
- * *nameable*: `npx --no <missing-bin>` yields only a generic npm error, which
113
- * is precisely how the defect hid for months.
149
+ * *nameable*, and — since Story #5193 — what makes it *visible at all* on the
150
+ * code surface: `npx --no <missing-bin>` answers with a generic npm error at
151
+ * best and an empty exit 0 at worst, which is precisely how both defects hid.
152
+ *
153
+ * Probing `node_modules/.bin` only is a deliberate bound: a globally-installed
154
+ * runner reads as absent here, which degrades the gate honestly rather than
155
+ * trusting a spawn nobody resolved.
114
156
  *
115
157
  * Not exported: it is reachable — and asserted — through {@link runScopedLint},
116
158
  * whose `existsFn` seam drives every resolution branch.
117
159
  *
160
+ * @param {ReadonlyArray<{ bin: string, extraArgs: ReadonlyArray<string> }>} candidates
118
161
  * @param {string} cwd
119
- * @param {(p: string) => boolean} [existsFn] Injected for testing.
162
+ * @param {(p: string) => boolean} existsFn Injected for testing.
120
163
  * @returns {{ bin: string, extraArgs: ReadonlyArray<string> }|null}
121
164
  */
122
- function resolveMarkdownRunner(cwd, existsFn = existsSync) {
123
- for (const candidate of MARKDOWN_RUNNERS) {
165
+ function resolveRunner(candidates, cwd, existsFn) {
166
+ for (const candidate of candidates) {
124
167
  const base = path.join(cwd, 'node_modules', '.bin', candidate.bin);
125
168
  if (existsFn(base)) return candidate;
126
169
  if (
@@ -133,6 +176,49 @@ function resolveMarkdownRunner(cwd, existsFn = existsSync) {
133
176
  return null;
134
177
  }
135
178
 
179
+ /**
180
+ * Pure: the summary a surface reports when it has no runner to spawn. Counts
181
+ * are zero *and* `executionFailed` is true, so the row can never be read as a
182
+ * clean result — the invariant Story #5193 restored.
183
+ *
184
+ * @returns {ReturnType<typeof parseLintOutput>}
185
+ */
186
+ function unresolvedRunnerSummary() {
187
+ return {
188
+ errors: 0,
189
+ warnings: 0,
190
+ parsed: false,
191
+ executionFailed: true,
192
+ emptyScope: false,
193
+ reason: DEGRADATION_REASONS.RUNNER_NOT_INSTALLED,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Resolve one surface's runner and, only if it resolved, spawn and classify it.
199
+ * Shared by both surfaces so neither can drift back into spawn-and-see.
200
+ *
201
+ * @param {{
202
+ * label: string,
203
+ * candidates: ReadonlyArray<{ bin: string, extraArgs: ReadonlyArray<string> }>,
204
+ * buildArgs: (runner: { bin: string, extraArgs: ReadonlyArray<string> }) => string[],
205
+ * cwd: string,
206
+ * runnerFn: typeof spawnLintRunner,
207
+ * existsFn: (p: string) => boolean,
208
+ * }} args
209
+ * @returns {{ surface: string, summary: ReturnType<typeof parseLintOutput> }}
210
+ */
211
+ function runSurface({ label, candidates, buildArgs, cwd, runnerFn, existsFn }) {
212
+ const runner = resolveRunner(candidates, cwd, existsFn);
213
+ if (runner === null) {
214
+ return { surface: label, summary: unresolvedRunnerSummary() };
215
+ }
216
+ return {
217
+ surface: runner.bin,
218
+ summary: parseLintOutput(runnerFn(runner.bin, buildArgs(runner), cwd)),
219
+ };
220
+ }
221
+
136
222
  /**
137
223
  * Pure: split changed paths into the file lists each lint runner consumes.
138
224
  *
@@ -267,33 +353,28 @@ export function runScopedLint(
267
353
 
268
354
  const surfaces = [];
269
355
  if (code.length > 0) {
270
- surfaces.push({
271
- surface: 'biome',
272
- summary: parseLintOutput(runnerFn('biome', ['lint', ...code], cwd)),
273
- });
356
+ surfaces.push(
357
+ runSurface({
358
+ label: 'biome',
359
+ candidates: CODE_RUNNERS,
360
+ buildArgs: (runner) => ['lint', ...code, ...runner.extraArgs],
361
+ cwd,
362
+ runnerFn,
363
+ existsFn,
364
+ }),
365
+ );
274
366
  }
275
367
  if (md.length > 0) {
276
- const runner = resolveMarkdownRunner(cwd, existsFn);
277
- if (runner === null) {
278
- surfaces.push({
279
- surface: 'markdownlint',
280
- summary: {
281
- errors: 0,
282
- warnings: 0,
283
- parsed: false,
284
- executionFailed: true,
285
- emptyScope: false,
286
- reason: DEGRADATION_REASONS.RUNNER_NOT_INSTALLED,
287
- },
288
- });
289
- } else {
290
- surfaces.push({
291
- surface: runner.bin,
292
- summary: parseLintOutput(
293
- runnerFn(runner.bin, [...md, ...runner.extraArgs], cwd),
294
- ),
295
- });
296
- }
368
+ surfaces.push(
369
+ runSurface({
370
+ label: 'markdownlint',
371
+ candidates: MARKDOWN_RUNNERS,
372
+ buildArgs: (runner) => [...md, ...runner.extraArgs],
373
+ cwd,
374
+ runnerFn,
375
+ existsFn,
376
+ }),
377
+ );
297
378
  }
298
379
 
299
380
  return mergeSurfaceSummaries(surfaces);
@@ -44,6 +44,7 @@ import {
44
44
  executeFastForward as defaultExecuteFastForward,
45
45
  planFastForward as defaultPlanFastForward,
46
46
  } from '../../git-cleanup/phases/fast-forward.js';
47
+ import { reapPlanRunLabelsForStory as defaultReapPlanRunLabelsForStory } from '../../plan-run-labels/reap.js';
47
48
  import { reassertStatusColumn as defaultReassertStatusColumn } from '../../reassert-status-column.js';
48
49
  import { releaseStoryLease as defaultReleaseStoryLease } from '../../single-story-lease-guard.js';
49
50
  import { captureStoryFollowUps as defaultCaptureStoryFollowUps } from '../../story-follow-ups.js';
@@ -290,6 +291,70 @@ async function stepLeaseRelease({
290
291
  };
291
292
  }
292
293
 
294
+ /**
295
+ * The whole-repository sweep an operator runs when the automatic reap could
296
+ * not finish its job. Named in the warning itself so the next step is in the
297
+ * message rather than in a runbook nobody opens mid-incident.
298
+ */
299
+ const REAP_SWEEP_REMEDY = 'node .agents/scripts/prune-plan-run-labels.js';
300
+
301
+ /**
302
+ * Reap the cohort labels the closing Story carried (Story #5189).
303
+ *
304
+ * This seam is chosen deliberately. It is the only one that fires for both
305
+ * single- and multi-Story runs: the multi-Story run epilogue is keyed on a
306
+ * synthesized ad-hoc id, never sees the cohort label, and reports
307
+ * `applicable: false` at N=1 — which is the planning default, so wiring the
308
+ * reap there would leave the common case unreaped forever.
309
+ *
310
+ * Best-effort in the strongest sense the tail offers: the outcome is
311
+ * deliberately NOT reported in the returned `tail` envelope. A per-step
312
+ * boolean is the right shape for a step whose failure degrades the *report of
313
+ * the land* — a missed follow-up, an unresynced status column. Label
314
+ * vocabulary hygiene is not that: nothing downstream reads a cohort label as
315
+ * an input, so a failed reap costs one stale label. Surfacing it in the
316
+ * envelope would make a close whose label read flaked terminate differently
317
+ * from one where no label was reapable, for no difference an operator can act
318
+ * on. The failure is named in a warning instead, and the whole-repository
319
+ * sweep (`node .agents/scripts/prune-plan-run-labels.js`) collects whatever
320
+ * the automatic path misses.
321
+ *
322
+ * Never reaps a zero-issue label: that shape is indistinguishable from a label
323
+ * an in-flight persist has just minted, so the opt-in stays off here.
324
+ */
325
+ async function stepPlanRunLabelReap({
326
+ storyId,
327
+ provider,
328
+ progress,
329
+ reapPlanRunLabelsForStoryFn,
330
+ }) {
331
+ const warn = (message) =>
332
+ progress?.(
333
+ 'POST-LAND',
334
+ `⚠️ plan-run label reap: ${message} — sweep the pile with ` +
335
+ `"${REAP_SWEEP_REMEDY}".`,
336
+ );
337
+ const outcome = await reapPlanRunLabelsForStoryFn({
338
+ storyId,
339
+ provider,
340
+ onWarn: warn,
341
+ });
342
+ const reaped = outcome?.deleted?.length ?? 0;
343
+ if (reaped > 0) {
344
+ progress?.(
345
+ 'POST-LAND',
346
+ `🏷️ Reaped ${reaped} spent plan-run label(s): ` +
347
+ `${outcome.deleted.map((d) => d.label).join(', ')}.`,
348
+ );
349
+ }
350
+ return {
351
+ ok: (outcome?.failed?.length ?? 0) === 0,
352
+ detail: outcome?.failed?.length
353
+ ? outcome.failed.map((f) => f.label).join(', ')
354
+ : null,
355
+ };
356
+ }
357
+
293
358
  /**
294
359
  * Run the whole post-land tail. Never throws.
295
360
  *
@@ -330,6 +395,7 @@ async function stepLeaseRelease({
330
395
  * @param {Function} [args.acquireLockWithWaitFn] Test seam.
331
396
  * @param {Function} [args.purgeStoryTempArtifactsFn] Test seam.
332
397
  * @param {Function} [args.releaseStoryLeaseFn] Test seam.
398
+ * @param {Function} [args.reapPlanRunLabelsForStoryFn] Test seam.
333
399
  * @returns {Promise<{ followUps: boolean, statusResync: boolean, refCleanup: boolean, baseFastForward: boolean, tempPurge: boolean, leaseRelease: boolean, details: Record<string, string|null> }>}
334
400
  */
335
401
  export async function runPostLandTail({
@@ -350,6 +416,7 @@ export async function runPostLandTail({
350
416
  acquireLockWithWaitFn = defaultAcquireLockWithWait,
351
417
  purgeStoryTempArtifactsFn = defaultPurgeStoryTempArtifacts,
352
418
  releaseStoryLeaseFn = defaultReleaseStoryLease,
419
+ reapPlanRunLabelsForStoryFn = defaultReapPlanRunLabelsForStory,
353
420
  }) {
354
421
  progress?.('POST-LAND', `🧾 Running land tail for Story #${storyId}...`);
355
422
 
@@ -402,6 +469,20 @@ export async function runPostLandTail({
402
469
  }),
403
470
  { name: 'status-column resync', progress },
404
471
  );
472
+ // Story #5189 — the cohort label's end of life. Runs with the other
473
+ // GitHub-touching steps (outside the checkout lock) and contributes nothing
474
+ // to `tail`; see `stepPlanRunLabelReap` for why that omission is the point.
475
+ await step(
476
+ () =>
477
+ stepPlanRunLabelReap({
478
+ storyId,
479
+ provider,
480
+ progress,
481
+ reapPlanRunLabelsForStoryFn,
482
+ }),
483
+ { name: 'plan-run label reap', progress },
484
+ );
485
+
405
486
  // Local-checkout mutations: serialized behind a best-effort cross-process
406
487
  // lock (Story #4622). Acquire once, run both steps, release in `finally`.
407
488
  const lockCfg = config?.delivery?.postLandLock ?? {};
@@ -9,12 +9,17 @@
9
9
  * explicitly, and always state them (as `none` when healthy) so an absent line
10
10
  * can never be mistaken for a clean gate.
11
11
  *
12
- * A degraded gate is **reported, not blocking**: the canonical `npm run lint`
13
- * close-validation gate has already covered this diff before the review phase
14
- * runs, so failing the merge on a secondary read of an already-gated surface
15
- * would cost delivery without buying coverage. The rationale for that posture
16
- * lives with the channel itself in
12
+ * A degraded gate is **reported, not blocking**: this review is a secondary
13
+ * read, and the close does not gate the merge on it. The rationale for that
14
+ * posture lives with the channel itself in
17
15
  * [`review-providers/degraded-gates.js`](../../review-providers/degraded-gates.js).
16
+ *
17
+ * What this module must **not** do (Story #5193) is tell the operator that the
18
+ * canonical `npm run lint` close gate covered the surface instead. A stub
19
+ * `lint` script is a supported consumer shape, so that claim is unverifiable
20
+ * from here — and asserting it talks the operator out of the exact concern the
21
+ * degradation was raised to surface. State the posture; never vouch for
22
+ * coverage this module cannot see.
18
23
  */
19
24
 
20
25
  import { summarizeDegradations } from '../../review-providers/degraded-gates.js';
@@ -58,8 +63,8 @@ export function formatReviewOutcomeLines({
58
63
  if (summarizeDegradations(degradations) !== 'none') {
59
64
  lines.push(
60
65
  '⚠️ Review ran DEGRADED — the surface(s) above were not reviewed. The close ' +
61
- 'is not blocked (the canonical `npm run lint` close gate already covered ' +
62
- 'this diff), but this review does not vouch for them.',
66
+ 'is not blocked — a secondary review does not gate the merge — but ' +
67
+ 'nothing here vouches for those surfaces.',
63
68
  );
64
69
  }
65
70
  return lines;
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { withTransientRetry } from './errors.js';
20
+ import { paginateRest } from './request-helpers.js';
20
21
 
21
22
  /**
22
23
  * Detect the "label already exists" signal across the surfaces `gh label
@@ -60,6 +61,28 @@ export function isLabelAlreadyExistsError(err) {
60
61
  return false;
61
62
  }
62
63
 
64
+ /**
65
+ * Detect the "label does not exist" signal on the delete path. `gh api` exits
66
+ * non-zero on a 404 with `gh: Not Found (HTTP 404)` on stderr; the REST body
67
+ * carries `"message": "Not Found"`. Both are matched, and the numeric status
68
+ * is matched on its own so a transport that surfaces only `err.status` still
69
+ * classifies.
70
+ *
71
+ * Deliberately narrow: only a 404 counts. A 403 (scope) or a 422 must stay
72
+ * loud, because reading either as "already gone" would let a sweep report
73
+ * labels as reaped that are all still there.
74
+ *
75
+ * @param {unknown} err
76
+ * @returns {boolean}
77
+ */
78
+ export function isLabelNotFoundError(err) {
79
+ if (!err) return false;
80
+ if (err.status === 404 || err.statusCode === 404) return true;
81
+ return /\bHTTP\s+404\b|\bnot found\b/i.test(
82
+ `${err.message ?? ''} ${err.stderr ?? ''}`,
83
+ );
84
+ }
85
+
63
86
  export class LabelGateway {
64
87
  /**
65
88
  * @param {{ gh: object, owner: string, repo: string }} deps
@@ -162,6 +185,71 @@ export class LabelGateway {
162
185
  return missing;
163
186
  }
164
187
 
188
+ /**
189
+ * List the repository's whole label vocabulary, paginated.
190
+ *
191
+ * Deliberately NOT modelled on `_reconcileLabelsPresence`'s
192
+ * `gh label list --limit 500`. That hard cap is fine for its own job —
193
+ * "are these 20 bootstrap labels present?" — and fatal for this one: a
194
+ * caller deciding which labels to delete from a truncated view would skip
195
+ * exactly the labels that sort after an accumulated pile, which is the
196
+ * failure Story #5189 exists to stop reproducing. `paginateRest` walks
197
+ * pages until a short one lands and throws (loudly) rather than truncating
198
+ * if the repository somehow exceeds its ceiling.
199
+ *
200
+ * Rows are projected to the fields a caller can rely on; a row without a
201
+ * usable `name` is dropped rather than passed on as a deletable target.
202
+ *
203
+ * @returns {Promise<Array<{ name: string, color: string|null, description: string|null }>>}
204
+ * @field-manifest /repos/{owner}/{repo}/labels: name, color, description
205
+ */
206
+ async listLabels() {
207
+ const endpoint = `/repos/${this.owner}/${this.repo}/labels`;
208
+ const rows = await paginateRest(this._gh, endpoint, {
209
+ label: `listLabels ${this.owner}/${this.repo}`,
210
+ });
211
+ return (Array.isArray(rows) ? rows : [])
212
+ .filter((row) => typeof row?.name === 'string')
213
+ .map((row) => ({
214
+ name: row.name,
215
+ color: row.color ?? null,
216
+ description: row.description ?? null,
217
+ }));
218
+ }
219
+
220
+ /**
221
+ * Delete one label by name.
222
+ *
223
+ * Goes through the REST surface (`DELETE /repos/{owner}/{repo}/labels/{name}`)
224
+ * rather than `gh label delete`, so the "already gone" signal arrives as a
225
+ * structured 404 on the same facade every other read here uses instead of
226
+ * as CLI prose — and so this gateway needs no new verb on the `gh` facade.
227
+ *
228
+ * A missing label resolves as a successful no-op. That is not leniency: the
229
+ * sweep this port exists for is expected to run repeatedly and concurrently
230
+ * with the close-path reap, so "someone else already deleted it" is the
231
+ * normal case, not an error.
232
+ *
233
+ * @param {string} name
234
+ * @returns {Promise<{ deleted: boolean, reason: string|null }>}
235
+ */
236
+ async deleteLabel(name) {
237
+ if (typeof name !== 'string' || name.trim().length === 0) {
238
+ throw new Error('deleteLabel: a non-empty label name is required');
239
+ }
240
+ const endpoint = `/repos/${this.owner}/${this.repo}/labels/${encodeURIComponent(name)}`;
241
+ try {
242
+ await withTransientRetry(() =>
243
+ this._gh.api({ method: 'DELETE', endpoint }),
244
+ );
245
+ return { deleted: true, reason: null };
246
+ } catch (err) {
247
+ if (isLabelNotFoundError(err))
248
+ return { deleted: false, reason: 'not-found' };
249
+ throw err;
250
+ }
251
+ }
252
+
165
253
  _normalizeLabelListResult(result) {
166
254
  if (Array.isArray(result)) return result;
167
255
  if (result && typeof result.stdout === 'string') {
@@ -133,6 +133,8 @@ const DELEGATIONS = [
133
133
  ['getBranchProtection', 'branchProtection.getBranchProtection'],
134
134
  ['setBranchProtection', 'branchProtection.setBranchProtection'],
135
135
  ['ensureLabels', 'labels.ensureLabels'],
136
+ ['listLabels', 'labels.listLabels'],
137
+ ['deleteLabel', 'labels.deleteLabel'],
136
138
  ['_reconcileLabelsPresence', 'labels._reconcileLabelsPresence'],
137
139
  ['getMergeMethods', 'mergeMethods.getMergeMethods'],
138
140
  ['setMergeMethods', 'mergeMethods.setMergeMethods'],