@ai-dossier/cli 0.55.0 → 0.57.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 CHANGED
@@ -453,7 +453,7 @@ prints `No runstate milestones on issue #440.` (or `null` under `--json`) and ex
453
453
  | `--status <s>` | `done`, `partial`, `blocked`, `awaiting-merge` (required). `superseded` is rejected here — a fence is written by [`runstate fence`](#run-fencing-504) |
454
454
  | `--run <id>` | Run id (`r-<issue>-<hex>`) — mint one with `runstate mint`; full-cycle runs mint it at the gate phase (required) |
455
455
  | `--kv <key=value...>` | Phase-specific key, repeatable (and variadic: `--kv a=1 b=2` works too) |
456
- | `--next <phase>` | Override the computed `next=` line — a phase name or `done` |
456
+ | `--next <phase>` | Override the computed `next=` line — a phase name, `done`, or `operator` (batch phases only) |
457
457
  | `--gen <n>` | Run generation this agent owns (default 0). A post below the trail's fenced generation is refused — see [Run fencing](#run-fencing-504) |
458
458
  | `--repo <owner/name>` | Target repository (defaults to the current one) |
459
459
  | `--dry-run` | Print the comment body instead of posting it |
@@ -532,7 +532,8 @@ vocabulary underneath it is stable. `batch-ship` mirrors `ship`'s two-milestone
532
532
  `awaiting-merge`, then `done` after the merge, and `stats` reports the gap between them
533
533
  under its own `batch-merge-wait` label (not pooled with full-cycle `merge-wait`).
534
534
  `next=` walks the batch line: batch-setup → batch-validate → batch-review → batch-ship →
535
- batch-report → done.
535
+ batch-report → done. A `blocked` batch milestone is followed by `next=operator`, not `done`
536
+ (#768): the batch's anchor is still open and its next step is operator inspection.
536
537
 
537
538
  > **Compatibility.** Reading and posting these phases requires CLI ≥ 0.14.0. An older
538
539
  > CLI rejects them outright on `post` (`Unknown phase 'classify' — expected one of: …`),
@@ -634,7 +635,8 @@ valid on **every** phase — a takeover can happen anywhere — and requires `ge
634
635
  `post` refuses it.
635
636
 
636
637
  `next=` is computed for you: the linear order `gate → setup → plan → implement → review →
637
- ship → report → done`, except that `blocked` ends the run (`next=done`) and the three
638
+ ship → report → done`, except that `blocked` ends the run (`next=done` — on a `batch-*`
639
+ phase it is `next=operator` instead, #768) and the three
638
640
  non-terminal statuses stay in their own phase — `superseded` points back at its own phase,
639
641
  so a takeover that dies leaves a resumable trail rather than a terminal one;
640
642
  `ship`/`awaiting-merge` is followed by a
@@ -677,7 +679,7 @@ Every `--kv` pair is checked before anything is posted:
677
679
  | `deps` | `none`, or comma-separated issue numbers, e.g. `474,480` |
678
680
  | `batch` | a batch id slug starting with a letter or digit, then letters, digits, `.`, `_`, `-` (e.g. `b-2026-08-29-01`) |
679
681
 
680
- - `--next` must be a phase name or `done`. It is written to the comment verbatim, so an
682
+ - `--next` must be a phase name, `done`, or `operator` (batch phases only). It is written to the comment verbatim, so an
681
683
  unchecked typo would point the next resume at a phase that does not exist.
682
684
  - Comments are append-only: never edit or delete a prior milestone.
683
685
 
@@ -1114,6 +1116,132 @@ measured pre-screen hit rate:
1114
1116
 
1115
1117
  ---
1116
1118
 
1119
+ ## Batch Composition (`batch compose`)
1120
+
1121
+ Previews which issues may share a batch PR **before any model spend** (#773, #770 P3
1122
+ "selection = admission"). It runs the classify pre-screen (`prescreen:v2`) plus the
1123
+ deterministic readiness screen over the operator's picks and/or the open backlog, and proposes
1124
+ one composition that honours the scheduler's batch invariants. No model call, no writes (no
1125
+ labels, comments, or queue changes) — only `gh` reads and a read-only look at the local sched
1126
+ queue/config. Exits 0 once arguments validate; `status` is the payload, not a pass/fail gate.
1127
+
1128
+ ```bash
1129
+ ai-dossier batch compose --issues 4114,4178,4327 [--repo owner/name] [--json]
1130
+ ai-dossier batch compose --backlog [--label backend]... [--search "no:assignee"] [--limit 100]
1131
+ ```
1132
+
1133
+ | Flag | Default | Meaning |
1134
+ |---|---|---|
1135
+ | `--issues <selection>` | — | Operator picks (fleet grammar: `1,2,5..8`, ≤ 200) |
1136
+ | `--backlog` | off | Draw candidates from the open backlog (`gh issue list`, one call) |
1137
+ | `--no-backfill` | backfill on | With `--issues`: never query the backlog, even when picks fall below `--min-members` |
1138
+ | `--label <name>` (repeatable), `--search <q>`, `--limit <n>` | —, —, 100 (max 500) | Backlog filters, passed to `gh issue list` |
1139
+ | `--base <branch>` | `main` | The base branch every member shares |
1140
+ | `--min-members <n>` / `--max-members <n>` | 3 / 6 | Minimum viable batch / member ceiling (≤ 6) |
1141
+ | `--max-full-review <n>` | sched config `max_full_review_members`, else 2 | Per-batch `review=full` cap (#771) |
1142
+ | `--rules v2\|legacy` | `v2` | `legacy` replays pre-#770 admission (any risk keyword ⇒ `mode=full` ⇒ excluded) for comparison |
1143
+ | `--repo`, `--project` | cwd repo, `owner-name` | Target repo; sched project whose queue/config is read |
1144
+
1145
+ At least one of `--issues` / `--backlog` is required. With `--issues` alone, the backlog is
1146
+ read **only** when the picks by themselves compose fewer than `--min-members` members — counted
1147
+ after the caps, so five admissible `review=full` picks (cap 2) still trigger backfill.
1148
+
1149
+ **Admission.** An issue is excluded — with every reason recorded, not just the first — on:
1150
+
1151
+ | `code` | Rule |
1152
+ |---|---|
1153
+ | `unreadable` | `gh issue view` failed (the report is `degraded`; the run continues) |
1154
+ | `closed` | Not `OPEN` |
1155
+ | `assigned` | Has an assignee |
1156
+ | `in-progress` | Carries the `in-progress` label |
1157
+ | `hard-block-label` | `decision-pending`, `needs-clarification`, `epic`, `decomposed` |
1158
+ | `batch-anchor` | Carries `batch-epic` |
1159
+ | `not-a-unit` | Tracker / decision / research / parked: labels `tracker` `decision` `question` `discussion` `research` `parked` `on-hold` `wontfix` `duplicate`; titles like `[PARKED] …`, `research: …`, `epic(x): …`; a `## Decision needed` section |
1160
+ | `in-flight` | Latest runstate milestone is any phase other than `classify` |
1161
+ | `sched-active` | A non-terminal, not-yet-merged sched queue entry exists |
1162
+ | `open-dependency` | `Depends on #N` with N open and not among the picks; N a pick that is itself excluded; or N whose state could not be read (fails closed) |
1163
+ | `data-mutation` | Change surface names a production data action (`data migration`, `data backfill`, `backfill script`, `one-off script`, `bulk delete`, …) — never shares a PR |
1164
+ | `prescreen-full` | `prescreen:v2` excluding floor: plan:v1 path floor or > 8 predicted files |
1165
+ | `legacy-full` | `--rules legacy` only: any text-floor keyword anywhere in title/body/labels |
1166
+
1167
+ `--rules legacy` replays only the pre-#770 keyword rule on top of today's readiness screen (the
1168
+ other codes above still apply) — it is a comparison of the admission rule, not a full replay of
1169
+ the old pipeline, which also ran a model classifier no deterministic check reproduces.
1170
+
1171
+ A risk keyword in the change surface (`billing`, `security`, `deploy`, …) does **not**
1172
+ exclude: the issue is admissible as a `review=full` member (#770 Option A). The data-mutation
1173
+ and text-floor scans read the same change-surface text (`floorScanText`: reference sections,
1174
+ provenance clauses and quoted spans removed).
1175
+
1176
+ **Composition** (deterministic): every admissible pick joins, bounded by `--max-members` and
1177
+ the `review=full` cap; the overflow is listed under `held` (`review-full-cap` / `max-members`).
1178
+ Then, while the set is below its target — `--min-members` with picks, `--max-members` with
1179
+ `--backlog` alone — admissible backlog issues are added greedily, ranked: shares a workspace
1180
+ package with the current members → `light` before `full` → more shared packages → lower issue
1181
+ number. Packages come from a plan:v1 artifact's predicted files when present, else from paths
1182
+ named in the issue body (`…/packages/<x>/…` → `packages/<x>`, else the first path segment).
1183
+ `status` is `ok` (≥ min), `under-min` (2 … min−1), or `no-batch` (< 2 — run the survivor as a
1184
+ full-cycle issue, #770 P4).
1185
+
1186
+ **JSON contract (`schema: batch-compose:v1`).** Consumers (batch-issues-preparation, #774)
1187
+ branch on `status`, take `manifest_entries`, and report `excluded`:
1188
+
1189
+ ```json
1190
+ {
1191
+ "schema": "batch-compose:v1",
1192
+ "repo": "imboard-ai/imboard-monorepo",
1193
+ "project": "imboard-ai-imboard-monorepo",
1194
+ "base_branch": "main",
1195
+ "rules": "v2",
1196
+ "params": {
1197
+ "min_members": 3, "max_members": 6, "max_full_review": 2, "picks": [4178, 4333, 4343],
1198
+ "backlog": { "queried": true, "labels": [], "search": null, "limit": 100 }
1199
+ },
1200
+ "status": "ok",
1201
+ "members": [
1202
+ { "issue": 4333, "title": "…", "review": "light", "source": "pick", "packages": [], "review_reasons": [] },
1203
+ { "issue": 4343, "title": "…", "review": "full", "source": "pick", "packages": ["packages/backend"],
1204
+ "review_reasons": ["Title/body/labels match 'rule1-risk-floor-area' (keyword: 'billing')."] },
1205
+ { "issue": 4241, "title": "…", "review": "full", "source": "backfill", "packages": ["packages/backend"], "review_reasons": ["…"] }
1206
+ ],
1207
+ "held": [],
1208
+ "backfill": [
1209
+ { "rank": 1, "issue": 4241, "title": "…", "review": "full", "packages": ["packages/backend"], "shared_packages": ["packages/backend"], "selected": true }
1210
+ ],
1211
+ "shared_packages": ["packages/backend"],
1212
+ "recommendation": "Form one batch of 3 on 'main' (2 review=full).",
1213
+ "manifest_entries": [
1214
+ { "issue": 4333, "mode": "slot", "review": "light", "base_branch": "main" },
1215
+ { "issue": 4343, "mode": "slot", "review": "full", "base_branch": "main" },
1216
+ { "issue": 4241, "mode": "slot", "review": "full", "base_branch": "main" }
1217
+ ],
1218
+ "excluded": [
1219
+ { "issue": 4178, "title": "…", "source": "pick",
1220
+ "reasons": [{ "code": "in-progress", "message": "Carries the 'in-progress' label." }] }
1221
+ ],
1222
+ "counts": { "assessed": 102, "admissible": 62, "excluded": 40 },
1223
+ "model_calls": 0,
1224
+ "degraded": false,
1225
+ "warnings": []
1226
+ }
1227
+ ```
1228
+
1229
+ - `members` is in selection order (picks, then backfill); `source` is `pick`, `backfill`, or
1230
+ `backlog` (backlog-only mode). `review_reasons` are the prescreen findings behind a `full`.
1231
+ - `manifest_entries` are **draft** `sched enqueue --from-manifest` entries (`parseManifest`
1232
+ shape): batch-issues-preparation adds `batch`, `anchor`, `run_id`, `tier`, `deps`, and
1233
+ `dispatch` — compose mints nothing and claims nothing. The report itself is not a manifest;
1234
+ extract the entries first, e.g.
1235
+ `jq '{entries: [.manifest_entries[] | . + {batch: $b, anchor: $a}]}' --arg b b-20260924-02 --argjson a 4410`.
1236
+ - `backfill` is present only with `--issues`: every admissible backlog candidate, ranked
1237
+ against the admitted picks, `selected: true` on the ones the composition took.
1238
+ - `held` lists admissible issues left out (`review-full-cap`, `max-members`).
1239
+ - `degraded`/`warnings` name any lookup that did not complete (an unreadable pick, the backlog
1240
+ list, a dependency's state, the sched queue/config); the result reflects what did complete.
1241
+ - `model_calls` is always `0`.
1242
+
1243
+ ---
1244
+
1117
1245
  ## Plan Artifacts (`plan`) — plan:v1
1118
1246
 
1119
1247
  Issues used to be planned up to three times (triage, batch prep, plan-issue). The
@@ -1168,7 +1296,7 @@ respectively; `get --json` includes the comment's `author`.
1168
1296
  ai-dossier sched enqueue --issues 101,105..109 [--mode full|slot] [--batch b1] [--more-members-expected] [--deps 100,104] [--tier mechanical|mid|strong] [--priority <n>] [--repo owner/name]
1169
1297
  ai-dossier sched enqueue --from-manifest batch-prep.json [--repo owner/name]
1170
1298
  ai-dossier sched start [--interval <seconds>] [--once] [--auto-upgrade] [--json]
1171
- ai-dossier sched status [--json] # ⚠ health warnings: long pause, stale lease, stuck / stale-closed slots (#776)
1299
+ ai-dossier sched status [--json] [--anchors] # ⚠ health warnings: long pause, stale lease, stuck / stale-closed slots (#776)
1172
1300
  ai-dossier sched pause | resume
1173
1301
  ai-dossier sched stop (--issue 42 | --batch b1) [--reason "..."]
1174
1302
  ai-dossier sched abandon --issue 42 [--reason "..."] | --batch b1 [--reason "..."]
@@ -1268,6 +1396,20 @@ atomically. `abandon` instead records failure and releases a slot without termin
1268
1396
  `shipped` — journaled `stale-failure-reconciled` — unblocking whatever
1269
1397
  dependents were blocked by the original failure, up to a 7-day watch
1270
1398
  window past which an abandoned failure is left as-is.
1399
+ A batch's anchor issue is closed off the happy path too (#768), but only on
1400
+ positive evidence: every member issue CLOSED as completed BY SHIPPED CODE (a
1401
+ PR merged into the batch's base, or a commit reachable from it — never a bare
1402
+ hand close), and no member evicted, handed back (`decision-pending`, any
1403
+ case), requeued, or failed. The engine then comments on the anchor with each
1404
+ member's shipping PR or commit (marker `batch-close:v1`, honoured only on its
1405
+ own comments, so a rerun never double-posts) and closes it — journaled
1406
+ `anchor-closed` (`anchor-close-failed` on a failed write, deduped like
1407
+ `pr-watch-failed`). It acts only on batches touched within 7 days, and only
1408
+ when the current directory is verified to be the project's repository (every
1409
+ `gh` call names it with `-R`). Any other shape leaves the anchor open;
1410
+ `status --anchors` lists every still-open anchor under
1411
+ `== Open batch anchors ==` as `closable`, `needs-operator`, or `unknown`
1412
+ (report-only and opt-in — without `--anchors`, `status` makes no GitHub call).
1271
1413
  `--once` runs a single tick (cron-style); Ctrl-C stops
1272
1414
  the engine while spawned agents keep running. Pids are identity-guarded via
1273
1415
  `/proc` start-times (a reused pid is never signalled; best-effort on macOS/Windows),
@@ -0,0 +1,172 @@
1
+ /**
2
+ * `ai-dossier batch compose` core (#773, #770 P3 "selection = admission").
3
+ *
4
+ * The batch pipeline used to admit members by keyword AFTER humans/models had already selected
5
+ * them — a hand-picked five-issue set collapsed to one member only after ~425k decision-grade
6
+ * classifier tokens (#770). This module previews admission BEFORE any model spend: it runs the
7
+ * deterministic `classify prescreen` (prescreen:v2, #772) plus the readiness screen
8
+ * batch-issues-preparation Step 1/5 applies, and proposes a composition that honours the
9
+ * scheduler's batch invariants — at most `max_full_review_members` (default 2, #771) `review=full`
10
+ * members, one base branch, `min_members` (default 3) to `max_members` (default 6) — preferring
11
+ * members that share a workspace package so the repo's expensive gate stage runs once for all.
12
+ *
13
+ * Pure and dependency-free (no `gh`, network, fs, or model) — same discipline as `prescreen.ts`.
14
+ * All I/O (fetching issues, dependency states, the sched queue) lives in `commands/batch.ts`.
15
+ */
16
+ import { type PrescreenReason } from './prescreen';
17
+ /** Version of the `batch compose --json` contract. Bump on any breaking shape change. */
18
+ export declare const COMPOSE_SCHEMA = "batch-compose:v1";
19
+ /** Default floor below which a composition is reported `under-min` and backfill is proposed (#770 P4). */
20
+ export declare const DEFAULT_MIN_MEMBERS = 3;
21
+ /** Default (and hard) member ceiling — batch-issues-preparation Step 5 constraint 3 (≤ 6 members). */
22
+ export declare const DEFAULT_MAX_MEMBERS = 6;
23
+ /** Fewer admissible members than this and no batch should form — hand the survivor to full-cycle (#770 P4). */
24
+ export declare const MIN_FORMABLE_MEMBERS = 2;
25
+ /** Admission rules: `v2` = prescreen:v2 + #771 review level (today); `legacy` = pre-#770 (any keyword ⇒ full ⇒ excluded). */
26
+ export type ComposeRules = 'v2' | 'legacy';
27
+ /** Why an issue cannot join a batch. Stable codes — consumers branch on `code`, humans read `message`. */
28
+ export type ExclusionCode = 'unreadable' | 'closed' | 'assigned' | 'in-progress' | 'hard-block-label' | 'batch-anchor' | 'in-flight' | 'sched-active' | 'open-dependency' | 'data-mutation' | 'not-a-unit' | 'prescreen-full' | 'legacy-full';
29
+ export interface ExclusionReason {
30
+ code: ExclusionCode;
31
+ message: string;
32
+ }
33
+ /** Everything the core needs about one issue — the command layer fills it from `gh`. */
34
+ export interface ComposeIssueInput {
35
+ issue: number;
36
+ /** `pick` = named by the operator via `--issues`; `backlog` = drawn from the backlog query. */
37
+ source: 'pick' | 'backlog';
38
+ /** Set when the issue could not be read at all; everything else is then ignored. */
39
+ error?: string;
40
+ title: string;
41
+ body: string;
42
+ labels: readonly string[];
43
+ /** GitHub `state` (`OPEN` / `CLOSED`). */
44
+ state: string;
45
+ assignees: readonly string[];
46
+ /** plan:v1 predicted files when the issue carries an artifact. */
47
+ predictedFiles?: readonly string[];
48
+ /** Phase of the LATEST runstate milestone on the issue, or null when it has none. */
49
+ latestPhase?: string | null;
50
+ /** Status of the issue's non-terminal sched queue entry, or null when it has none. */
51
+ schedStatus?: string | null;
52
+ /** `Depends on #N` refs resolved OPEN and outside the operator's picks. */
53
+ openDependencies?: readonly number[];
54
+ /** `Depends on #N` refs whose state could not be read — an admission gate fails closed on them. */
55
+ unknownDependencies?: readonly number[];
56
+ }
57
+ export interface AssessedIssue {
58
+ issue: number;
59
+ source: 'pick' | 'backlog';
60
+ title: string;
61
+ admissible: boolean;
62
+ /** Review depth the member needs (prescreen:v2 / #771 vocabulary). */
63
+ review: 'light' | 'full';
64
+ /** Workspace packages the issue is predicted to touch (plan:v1 files first, else paths named in the body). */
65
+ packages: string[];
66
+ /** Every prescreen finding — the reason a member is `review=full`. */
67
+ prescreen: PrescreenReason[];
68
+ /** Empty when admissible. */
69
+ excluded: ExclusionReason[];
70
+ }
71
+ /**
72
+ * Phrases that mark an issue as production data mutation / ops — the one class #770 P1 keeps
73
+ * out of shared PRs ("prod data mutation/ops"): a batch reverts per commit, a data change does
74
+ * not revert with it. Deliberately ACTION phrases, not bare words or places: "backfill" alone is batch
75
+ * vocabulary (this very feature proposes "backfill candidates"), "migration" alone is already a
76
+ * `review=full` text-floor hit, "delete" alone is ordinary code prose, and "production data" /
77
+ * "prod db" name a place an issue merely talks about (an IAM or pentest issue) — measured on the
78
+ * imboard backlog, those place phrases excluded five issues that mutate nothing. Matched against the
79
+ * same change-surface text the prescreen text floor scans (`floorScanText` — reference sections,
80
+ * provenance clauses and quoted spans removed), whole-word, case-insensitive.
81
+ */
82
+ export declare const DATA_MUTATION_PHRASES: readonly string[];
83
+ /** First data-mutation phrase the change-surface text matches, or null. */
84
+ export declare function matchDataMutation(text: string): string | null;
85
+ /**
86
+ * Labels marking an issue as something other than one implementable change — a tracker, a
87
+ * decision, parked or research work (#770 P1: "decisions/epics/trackers" never share a PR;
88
+ * batch-issues-preparation Step 5 "Is it a tracker or a decision?"). The hard-block labels
89
+ * (`epic`, `decomposed`, …) are reported separately as `hard-block-label`.
90
+ */
91
+ export declare const NOT_A_UNIT_LABELS: readonly string[];
92
+ /** Why the issue is not one implementable unit, or null. */
93
+ export declare function notAUnitReason(title: string, body: string, labels: readonly string[]): string | null;
94
+ /** The workspace package a repo-relative path belongs to: `…/packages/<x>/…` → `packages/<x>`, else its first segment. */
95
+ export declare function workspaceOf(path: string): string | null;
96
+ /**
97
+ * Workspace packages an issue is predicted to touch. A plan:v1 artifact's predicted files win
98
+ * (they are the plan's own claim); otherwise path tokens named in the change-surface body
99
+ * (reference sections and provenance removed, but quoted spans KEPT — backticked paths are the
100
+ * main signal here). Sorted, de-duplicated, capped at {@link MAX_PACKAGES}.
101
+ */
102
+ export declare function inferPackages(body: string, predictedFiles?: readonly string[]): string[];
103
+ /**
104
+ * Deterministic admission for one issue: readiness (batch-issues-preparation Step 1/5) +
105
+ * prescreen:v2 (#772) + data-mutation. Records EVERY exclusion reason, not just the first, so an
106
+ * operator sees the whole picture of why a pick cannot join.
107
+ */
108
+ export declare function assessIssue(input: ComposeIssueInput, rules?: ComposeRules): AssessedIssue;
109
+ export interface ComposeOptions {
110
+ rules: ComposeRules;
111
+ baseBranch: string;
112
+ minMembers: number;
113
+ maxMembers: number;
114
+ /** Per-batch `review=full` cap (#771 — `max_full_review_members`, default 2). */
115
+ maxFullReview: number;
116
+ /** Whether operator picks were given (`--issues`). Picks mode fills to `minMembers`; backlog-only mode fills to `maxMembers`. */
117
+ picksMode: boolean;
118
+ }
119
+ export interface ComposedMember {
120
+ issue: number;
121
+ title: string;
122
+ review: 'light' | 'full';
123
+ source: 'pick' | 'backfill' | 'backlog';
124
+ packages: string[];
125
+ /** Why `review=full` (prescreen findings); empty for `light`. */
126
+ review_reasons: string[];
127
+ }
128
+ /** An admissible issue left out of the composition, and why. */
129
+ export interface HeldIssue {
130
+ issue: number;
131
+ title: string;
132
+ review: 'light' | 'full';
133
+ source: 'pick' | 'backlog';
134
+ packages: string[];
135
+ reason: 'review-full-cap' | 'max-members';
136
+ }
137
+ export interface BackfillCandidate {
138
+ rank: number;
139
+ issue: number;
140
+ title: string;
141
+ review: 'light' | 'full';
142
+ packages: string[];
143
+ /** Packages it shares with the operator's admitted picks — the ranking's first key. */
144
+ shared_packages: string[];
145
+ /** Whether the proposed composition takes it. */
146
+ selected: boolean;
147
+ }
148
+ export type ComposeStatus = 'ok' | 'under-min' | 'no-batch';
149
+ export interface CompositionResult {
150
+ status: ComposeStatus;
151
+ members: ComposedMember[];
152
+ held: HeldIssue[];
153
+ backfill: BackfillCandidate[];
154
+ /** Packages touched by ≥ 2 members — the expensive gate stage these members amortize. */
155
+ shared_packages: string[];
156
+ /** One line saying what to do with this result. */
157
+ recommendation: string;
158
+ }
159
+ /**
160
+ * Propose a composition from assessed issues. Picks first (they are the operator's intent) —
161
+ * all admissible picks join, bounded by `maxMembers` and the `review=full` cap; then, while the
162
+ * set is below its target (`minMembers` with picks, `maxMembers` from the backlog alone), backfill
163
+ * from admissible backlog issues. Member order: picks, then backfill, each in selection order.
164
+ */
165
+ export declare function composeBatch(assessed: AssessedIssue[], opts: ComposeOptions): CompositionResult;
166
+ /**
167
+ * A pick that depends on ANOTHER pick is admissible only while that pick is: when the depended-on
168
+ * pick is excluded (and still open), the dependent would ship without it. Applied to a fixpoint,
169
+ * so a chain A → B → C collapses when C is excluded. Mutates and returns `assessed`.
170
+ */
171
+ export declare function applyPickDependencies(assessed: AssessedIssue[], pickDeps: ReadonlyMap<number, readonly number[]>, isOpen: (issue: number) => boolean): AssessedIssue[];
172
+ //# sourceMappingURL=batch-compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch-compose.d.ts","sourceRoot":"","sources":["../src/batch-compose.ts"],"names":[],"mappings":"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"}