pm-github 2026.8.7 → 2026.8.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.8.10 - 2026-08-10
4
+
5
+ ### Fixed
6
+
7
+ - Converge changelog generation and verification on replace mode ([pm-github-8f60](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-8f60.toon))
8
+ - The mandatory docstring gate could skip its own scan and still exit zero ([pm-github-wxob](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-wxob.toon))
9
+
10
+ ### Other
11
+
12
+ - Adopt the canonical pm-ops docstring gate ([pm-github-pbxd](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-pbxd.toon))
13
+
3
14
  ## 2026.8.7 - 2026-08-07
4
15
 
5
16
  ### Other
package/dist/index.d.ts CHANGED
@@ -1,31 +1,55 @@
1
1
  import type { ExtensionApi } from "@unbrained/pm-cli/sdk/authoring";
2
2
  import { type BulkItemMutation, type CommitItemMutationsOptions, type CommitItemMutationsResult, type ItemDocument } from "@unbrained/pm-cli/sdk";
3
+ /**
4
+ * One GitHub issue or pull request as returned by the REST issues endpoint.
5
+ *
6
+ * The REST issues endpoint lists PRs alongside issues (a PR is an issue that
7
+ * also carries a `pull_request` object); {@link isDraftPr} and the
8
+ * `includePrs` / `skipDrafts` filters decide which of those survive into an
9
+ * import.
10
+ */
3
11
  export interface GhIssue {
12
+ /** Issue/PR number within its repo (the `N` in `gh:owner/repo#N` provenance). */
4
13
  number: number;
14
+ /** One-line issue title, imported as the pm item title. */
5
15
  title: string;
16
+ /** Markdown body, imported as the pm item body (`null` when GitHub stored none). */
6
17
  body: string | null;
18
+ /** GitHub lifecycle state; mapped to a pm status by {@link mapState}. */
7
19
  state: "open" | "closed";
20
+ /** Why a closed issue was closed; `not_planned` maps to `canceled`, not `closed`. */
8
21
  state_reason?: "completed" | "not_planned" | "reopened" | null;
22
+ /** Labels on the issue, imported verbatim as pm tags (before any `--label-map`). */
9
23
  labels: Array<{
10
24
  name: string;
11
25
  }>;
26
+ /** Author login, surfaced as a `github_author:` tag when present. */
12
27
  user?: {
13
28
  login: string;
14
29
  } | null;
30
+ /** Assigned user, or `null` (imported as the item assignee when set). */
15
31
  assignee: {
16
32
  login: string;
17
33
  } | null;
34
+ /** Milestone title, or `null` (filtered client-side by `--milestone`). */
18
35
  milestone: {
19
36
  title: string;
20
37
  } | null;
38
+ /** ISO 8601 creation timestamp. */
21
39
  created_at: string;
40
+ /** ISO 8601 last-update timestamp, used by the `--since` incremental filter. */
22
41
  updated_at: string;
23
42
  /** GitHub completion timestamp (`null` while the issue is open); carried as `--completed-at` on close. */
24
43
  closed_at?: string | null;
44
+ /** Browser URL of the issue, embedded in the imported description. */
25
45
  html_url: string;
46
+ /** Comment count GitHub reports; drives whether comments are fetched at all. */
26
47
  comments?: number;
48
+ /** REST URL of the issue's comments collection (paginated when present). */
27
49
  comments_url?: string;
50
+ /** Present (opaque) when the issue is actually a pull request; its presence is the PR signal. */
28
51
  pull_request?: unknown;
52
+ /** `true` when the issue is a draft pull request (GitHub sets this only on draft PRs). */
29
53
  draft?: boolean;
30
54
  }
31
55
  export interface GhComment {
@@ -37,19 +61,38 @@ export interface GhComment {
37
61
  body: string | null;
38
62
  }
39
63
  type CommentsMode = "body" | "annotations" | "both";
64
+ /**
65
+ * Normalized options governing one GitHub issue import.
66
+ *
67
+ * Produced by {@link parseImportOptions} from the raw CLI flag bag so the rest
68
+ * of the import path consumes a typed shape rather than re-parsing strings.
69
+ */
40
70
  export interface ImportOptions {
71
+ /** GitHub issue state to fetch: `open`, `closed`, or `all`. */
41
72
  state: "open" | "closed" | "all";
73
+ /** Comma-separated label filter applied server-side. */
42
74
  labels?: string;
75
+ /** ISO timestamp; only issues updated after it are fetched (server-side). */
43
76
  since?: string;
77
+ /** Assignee login filter applied server-side. */
44
78
  assignee?: string;
79
+ /** Milestone title filter applied client-side (the API keys milestones by number, not title). */
45
80
  milestone?: string;
81
+ /** Whether pull requests are included (filtered out by default). */
46
82
  includePrs: boolean;
83
+ /** Whether draft pull requests are excluded (only meaningful with {@link includePrs}). */
47
84
  skipDrafts: boolean;
85
+ /** Whether fetched comments are also embedded in the item body (legacy behavior). */
48
86
  withComments: boolean;
87
+ /** How fetched comments are persisted: body, native annotations, or both. */
49
88
  commentsMode: CommentsMode;
89
+ /** pm item type assigned to every created item (default `Issue`). */
50
90
  itemType: string;
91
+ /** When true, preview the plan without writing to the tracker or GitHub. */
51
92
  dryRun: boolean;
93
+ /** When true, commit the batch as one crash-resumable SDK transaction. */
52
94
  atomic: boolean;
95
+ /** When true, run the `--link-deps` dependency-edge second pass after import. */
53
96
  linkDeps: boolean;
54
97
  }
55
98
  type CommitItemMutations = (options: CommitItemMutationsOptions) => Promise<CommitItemMutationsResult>;
@@ -57,10 +100,21 @@ type NormalizeItemId = (input: string, prefix: string) => string;
57
100
  type ReadSettings = (pmRoot: string) => Promise<{
58
101
  id_prefix?: string;
59
102
  }>;
103
+ /**
104
+ * Injectable collaborators for the atomic import path.
105
+ *
106
+ * Lets the crash-resumable transaction be exercised hermetically against fake
107
+ * SDK functions. Every member is optional because production wires the real SDK
108
+ * defaults; a test overrides only the ones it needs.
109
+ */
60
110
  export interface AtomicImportOptions {
111
+ /** Author identity stamped onto the transaction's mutations (default `pm-github`). */
61
112
  atomicAuthor?: string;
113
+ /** SDK bulk-mutation primitive that applies and journals the transaction. */
62
114
  commitItemMutations?: CommitItemMutations;
115
+ /** SDK id normalizer that derives each item's stable external-key id. */
63
116
  normalizeItemId?: NormalizeItemId;
117
+ /** SDK settings reader used to resolve the workspace's id prefix. */
64
118
  readSettings?: ReadSettings;
65
119
  }
66
120
  export interface ImportRunDependencies {
@@ -80,11 +134,45 @@ export interface ImportRunDependencies {
80
134
  };
81
135
  }
82
136
  export declare function resolveGitHubToken(): string | undefined;
137
+ /**
138
+ * Decide whether the Authorization token may be forwarded across a redirect.
139
+ *
140
+ * Only same-origin (scheme + host + port) redirects keep the credential; any
141
+ * origin change drops it so the bearer token can never leak to a third party.
142
+ *
143
+ * @param fromUrl - The URL the redirect originated from.
144
+ * @param toUrl - The redirect target URL.
145
+ * @returns True when both URLs share an origin.
146
+ */
83
147
  export declare function sameOrigin(fromUrl: string, toUrl: string): boolean;
148
+ /**
149
+ * Compute how long (ms) to wait before retrying a rate-limited or transient
150
+ * response.
151
+ *
152
+ * Honors `Retry-After` (seconds) and the primary rate-limit reset window
153
+ * (`X-RateLimit-Remaining: 0` + `X-RateLimit-Reset` epoch), then falls back to
154
+ * exponential backoff. The result is capped so a CLI run is never hung
155
+ * indefinitely.
156
+ *
157
+ * @param headers - Response headers to read the retry hints from.
158
+ * @param attempt - Zero-based retry index feeding the exponential backoff floor.
159
+ * @param nowMs - Current epoch ms, used to measure the reset window's remaining time.
160
+ * @returns The capped wait in milliseconds.
161
+ */
84
162
  export declare function computeBackoffMs(headers: Record<string, string | string[] | undefined>, attempt: number, nowMs?: number): number;
163
+ /**
164
+ * Structured view of GitHub's rate-limit headers off one response.
165
+ *
166
+ * Fields are `undefined` when the corresponding header is absent or non-numeric
167
+ * so callers can degrade gracefully; `reset` is the epoch-second timestamp at
168
+ * which the current window reopens.
169
+ */
85
170
  export interface RateLimitInfo {
171
+ /** Remaining requests in the current window. */
86
172
  remaining?: number;
173
+ /** Total requests permitted per window. */
87
174
  limit?: number;
175
+ /** Epoch seconds at which the window resets. */
88
176
  reset?: number;
89
177
  /** True when the remaining quota is at/under the low-water mark. */
90
178
  low: boolean;
@@ -105,22 +193,76 @@ export declare const EXIT_CODE: {
105
193
  readonly USAGE: 2;
106
194
  readonly NOT_FOUND: 3;
107
195
  };
196
+ /**
197
+ * Error that carries a semantic process exit code.
198
+ *
199
+ * pm's command runtime treats a thrown error as a cleanly handled non-zero exit
200
+ * only when it exposes a numeric `exitCode`; a plain `Error` instead falls
201
+ * through to the "unhandled" path, which re-invokes the handler (doubling side
202
+ * effects such as a second GitHub fetch) and exits with a generic code. Throwing
203
+ * this routes a failure to a clean, single exit at the chosen code.
204
+ */
108
205
  export declare class CommandError extends Error {
206
+ /** Numeric exit code the runtime propagates to the shell (one of {@link EXIT_CODE}). */
109
207
  exitCode: number;
110
208
  constructor(message: string, exitCode?: number);
111
209
  }
210
+ /**
211
+ * Build the `gh:owner/repo#N` provenance tag linking a pm item to a GitHub issue.
212
+ *
213
+ * The tag is the idempotency key: it round-trips losslessly through
214
+ * `pm create --tags` / `pm list --json`, so a re-import can find the existing
215
+ * item and UPDATE it instead of duplicating. Provenance also rides on declared
216
+ * schema fields and the description, but the tag is what matching keys on.
217
+ *
218
+ * @param repo - `owner/repo`, lowercased into the tag.
219
+ * @param issueNumber - The issue/PR number.
220
+ * @returns The provenance tag string.
221
+ */
112
222
  export declare function provenanceTag(repo: string, issueNumber: number): string;
223
+ /**
224
+ * Parse a `gh:owner/repo#N` provenance tag back into its repo and issue number.
225
+ *
226
+ * Returns `undefined` for anything that is not a provenance tag, so a caller
227
+ * scanning a tag list can skip foreign tags without a try/catch.
228
+ *
229
+ * @param tag - The candidate tag string.
230
+ * @returns The parsed repo (lowercased) and number, or `undefined`.
231
+ */
113
232
  export declare function parseProvenanceTag(tag: string): {
114
233
  repo: string;
115
234
  number: number;
116
235
  } | undefined;
236
+ /**
237
+ * Build the `github_author:login` tag recording who opened a GitHub issue.
238
+ *
239
+ * Mirrors how provenance rides on tags so the author survives a round-trip.
240
+ * Returns `undefined` when the API supplied no usable login, so an empty tag is
241
+ * never emitted.
242
+ *
243
+ * @param issue - The issue whose author to tag.
244
+ * @returns The author tag, or `undefined`.
245
+ */
117
246
  export declare function authorTag(issue: GhIssue): string | undefined;
247
+ /**
248
+ * The slice of a pm item this package reads and writes.
249
+ *
250
+ * A deliberately loose projection of the full tracker item: only the fields the
251
+ * import / export / sync paths touch, so JSON from `pm list-all` parses without
252
+ * depending on every SDK field.
253
+ */
118
254
  export interface PmItem {
255
+ /** Stable item id (absent for items not yet created). */
119
256
  id?: string;
257
+ /** One-line title. */
120
258
  title?: string;
259
+ /** Lifecycle status (`open`, `in_progress`, `closed`, `canceled`, …). */
121
260
  status?: string;
261
+ /** Long-form markdown body. */
122
262
  body?: string;
263
+ /** Short summary shown in listings. */
123
264
  description?: string;
265
+ /** Tag set, carrying provenance and labels. */
124
266
  tags?: string[];
125
267
  }
126
268
  export interface ItemScopeResult<TItem> {
@@ -206,14 +348,26 @@ export declare function importGithubAtomic(pmRoot: string, repo: string, entries
206
348
  export declare function buildIssuesUrl(repo: string, opts: ImportOptions): string;
207
349
  export declare function composeBody(issue: GhIssue, comments: GhComment[]): string;
208
350
  export declare const IMPORT_LOCK_TTL_MS_DEFAULT: number;
351
+ /** Default total budget a caller waits to acquire a contended comment-sync lock before giving up and skipping that item's sync. */
209
352
  export declare const IMPORT_LOCK_WAIT_MS_DEFAULT = 30000;
353
+ /**
354
+ * Payload JSON written into a comment-sync lock file.
355
+ *
356
+ * Mirrors the pm CLI's own lock payload so the CLI's `pm gc` lock sweep (which
357
+ * reads `ttl_seconds`) treats these locks exactly like its own.
358
+ */
210
359
  export interface ImportLockPayload {
360
+ /** Lock identity (the lock file's basename without `.lock`). */
211
361
  id: string;
362
+ /** OS process id of the holder, used to detect a dead owner. */
212
363
  pid: number;
364
+ /** Human-readable holder label (always `pm-github`). */
213
365
  owner: string;
214
366
  /** Unique per acquisition; release() only unlinks a file carrying it. */
215
367
  token: string;
368
+ /** ISO timestamp recorded at acquisition, the staleness age base. */
216
369
  created_at: string;
370
+ /** Lock lifetime in seconds, read by `pm gc` to sweep abandoned locks. */
217
371
  ttl_seconds: number;
218
372
  }
219
373
  export interface ImportLock {
@@ -230,25 +384,148 @@ export type ImportLockAcquisition = {
230
384
  } | {
231
385
  status: "degraded";
232
386
  };
387
+ /**
388
+ * Resolve the pm data dir from the `pmRoot` a command handler receives.
389
+ *
390
+ * The host may hand either the workspace root (the dir containing `.agents/pm`)
391
+ * or the data dir itself — the pm CLI accepts both for `--path`. This returns
392
+ * the dir that actually holds `settings.json` and `locks/`, defaulting to
393
+ * `pmRoot` unchanged when no nested `.agents/pm` exists.
394
+ *
395
+ * @param pmRoot - The path supplied by the extension host.
396
+ * @returns The resolved pm data directory.
397
+ */
233
398
  export declare function resolvePmDataDir(pmRoot: string): string;
399
+ /**
400
+ * Absolute lock file path for one item's comment-sync critical section.
401
+ *
402
+ * The item id is sanitized defensively: pm ids are already filename-safe, but
403
+ * the lock path must never become a directory-traversal vector if that ever
404
+ * changes.
405
+ *
406
+ * @param pmRoot - Workspace root or pm data dir.
407
+ * @param itemId - The pm item id to serialize.
408
+ * @returns Absolute path under the data dir's `locks/` directory.
409
+ */
234
410
  export declare function importCommentSyncLockPath(pmRoot: string, itemId: string): string;
411
+ /**
412
+ * Staleness TTL for a breaker election sidecar, in ms.
413
+ *
414
+ * A breaker's critical section is a handful of syscalls (re-stat, re-read,
415
+ * unlink), so a crashed breaker's sidecar goes stale in seconds, not minutes;
416
+ * this short window lets a contender clear and re-run a dead election.
417
+ */
235
418
  export declare const IMPORT_LOCK_BREAKER_TTL_MS = 10000;
419
+ /**
420
+ * Acquire the cross-process comment-sync lock for one item.
421
+ *
422
+ * Never throws: every failure mode maps to one of the three acquisition
423
+ * statuses (`acquired`, `contended`, `degraded`) so the caller decides how to
424
+ * degrade. `ttlMs`/`waitMs` are injectable so the wait and staleness windows
425
+ * can be exercised without real timing in tests.
426
+ *
427
+ * @param pmRoot - Workspace root or pm data dir passed to {@link resolvePmDataDir}.
428
+ * @param itemId - The pm item whose comment critical section is serialized.
429
+ * @param opts - Optional overrides for the staleness TTL and wait budget.
430
+ * @returns The acquisition outcome.
431
+ */
236
432
  export declare function acquireImportLock(pmRoot: string, itemId: string, opts?: {
237
433
  ttlMs?: number;
238
434
  waitMs?: number;
239
435
  }): Promise<ImportLockAcquisition>;
436
+ /**
437
+ * Matches the hidden HTML-comment marker carrying a synced GitHub comment id.
438
+ *
439
+ * HTML comments are invisible in rendered markdown but survive `pm comments`
440
+ * storage verbatim, so the id embedded in the marker is the stable dedupe key.
441
+ */
240
442
  export declare const COMMENT_MARKER_REGEX: RegExp;
443
+ /**
444
+ * Build the text for one native pm comment from a GitHub comment.
445
+ *
446
+ * Appends the marker so a later re-sync can de-duplicate on the GitHub comment
447
+ * id (see {@link COMMENT_MARKER_REGEX}).
448
+ *
449
+ * @param comment - The GitHub comment to render.
450
+ * @returns The comment text with the trailing id marker.
451
+ */
241
452
  export declare function buildCommentText(comment: GhComment): string;
453
+ /**
454
+ * Collect the GitHub comment ids already synced into an item's native comments.
455
+ *
456
+ * Scans each stored comment's text for the stable marker. Returns the set of
457
+ * synced ids (empty when none match, e.g. for hand-written pm comments).
458
+ *
459
+ * @param stored - The item's existing native comments (each may carry marker text).
460
+ * @returns The set of already-synced GitHub comment ids.
461
+ */
242
462
  export declare function extractSyncedCommentIds(stored: {
243
463
  text?: string;
244
464
  }[]): Set<number>;
465
+ /**
466
+ * Extract the newly-created item id from `pm create --json` stdout.
467
+ *
468
+ * The CLI emits a FLAT envelope (`{ "id": "pm-xxxx", … }`) with no `item`
469
+ * wrapper, so this reads `parsed.id` directly. Returns `undefined` when the
470
+ * output genuinely cannot be parsed, so callers skip-with-warning instead of
471
+ * crashing the whole import.
472
+ *
473
+ * @param stdout - Raw `pm create --json` output.
474
+ * @returns The created item id, or `undefined`.
475
+ */
245
476
  export declare function parseCreatedItemId(stdout: string): string | undefined;
477
+ /**
478
+ * Sync GitHub issue comments into a pm item's native comments collection.
479
+ *
480
+ * Idempotent: comments already present (matched by their marker id) are skipped,
481
+ * so re-running import never duplicates. Each GitHub comment becomes one pm
482
+ * comment authored by the GitHub login. Failures are logged and never abort the
483
+ * import; a contended or unavailable lock degrades gracefully (see
484
+ * {@link acquireImportLock}).
485
+ *
486
+ * @param itemId - The pm item to append comments to.
487
+ * @param comments - The GitHub comments to sync.
488
+ * @param pmRoot - Workspace root or pm data dir.
489
+ * @param issueNumber - The source issue number (for log prefixes).
490
+ * @returns How many comments were added and how many skipped as duplicates.
491
+ */
246
492
  export declare function syncGithubCommentsToAnnotations(itemId: string, comments: GhComment[], pmRoot: string, issueNumber: number): Promise<{
247
493
  added: number;
248
494
  skipped: number;
249
495
  }>;
496
+ /**
497
+ * Whether a GitHub issue node is a draft pull request.
498
+ *
499
+ * A draft PR is an issue that is BOTH a pull request and flagged `draft: true`;
500
+ * plain issues are never drafts.
501
+ *
502
+ * @param issue - The issue node to test.
503
+ * @returns True only for draft pull requests.
504
+ */
250
505
  export declare function isDraftPr(issue: GhIssue): boolean;
506
+ /**
507
+ * Apply the client-side import filters the REST API cannot express.
508
+ *
509
+ * Drops pull requests (unless `includePrs`), draft PRs (when `skipDrafts`), and
510
+ * items whose milestone title does not match (the API keys milestones by number,
511
+ * not title). Returns the surviving issues in input order.
512
+ *
513
+ * @param issues - Fetched issues to narrow.
514
+ * @param opts - Import options carrying the filter flags.
515
+ * @returns The filtered issue list.
516
+ */
251
517
  export declare function applyClientFilters(issues: GhIssue[], opts: ImportOptions): GhIssue[];
518
+ /**
519
+ * Normalize the raw CLI flag bag into a typed {@link ImportOptions}.
520
+ *
521
+ * Resolves the state filter, labels, assignee, milestone, the `--since` window,
522
+ * PR inclusion, draft skipping, and the comment-fetching mode, throwing a
523
+ * {@link CommandError} with a usage code on a malformed `--since` or
524
+ * `--comments-mode`.
525
+ *
526
+ * @param options - The raw option object from the command handler.
527
+ * @returns The normalized import options.
528
+ */
252
529
  export declare function parseImportOptions(options: Record<string, unknown>): ImportOptions;
253
530
  /** pm dependency kinds a GitHub body reference can map to. */
254
531
  export type DepRefKind = "blocked_by" | "blocks";
@@ -318,6 +595,20 @@ export declare function countDependencyRefCandidates(repo: string, issues: reado
318
595
  * `failures`/warnings so the import result stays truthful.
319
596
  */
320
597
  export declare function linkImportedDependencies(repo: string, issues: readonly GhIssue[], pmRoot: string, deps?: ImportRunDependencies): Promise<DepLinkResult>;
598
+ /**
599
+ * Run the full GitHub issue import flow.
600
+ *
601
+ * Idempotent: items already linked (by provenance tag) to a fetched issue are
602
+ * UPDATEd; new issues are created. Honors `--atomic` (one crash-resumable
603
+ * transaction) versus the per-item `pm` mutation path, optional `--link-deps`,
604
+ * and `--dry-run`. Returns a structured result and throws {@link CommandError}
605
+ * (with a semantic exit code) on failure.
606
+ *
607
+ * @param repoArg - The `owner/repo` to import from.
608
+ * @param pmRoot - Workspace root or pm data dir.
609
+ * @param opts - Normalized import options.
610
+ * @param deps - Injectable collaborators (token resolver, fetchers, etc.) for tests.
611
+ */
321
612
  export declare function runImport(repoArg: string | undefined, pmRoot: string, opts: ImportOptions, dependencies?: ImportRunDependencies): Promise<{
322
613
  imported: number;
323
614
  updated: number;
@@ -341,11 +632,22 @@ export declare function runImport(repoArg: string | undefined, pmRoot: string, o
341
632
  atomic?: boolean | undefined;
342
633
  wouldLinkDependencyCandidates?: number | undefined;
343
634
  }>;
635
+ /**
636
+ * One pm → GitHub issue state-change proposed by {@link planSync}.
637
+ *
638
+ * `from`/`to` are GitHub issue states; the executor only PATCHes when GitHub's
639
+ * live state disagrees with `to`.
640
+ */
344
641
  export interface SyncPlanEntry {
642
+ /** pm item id driving the proposed change. */
345
643
  id: string;
644
+ /** GitHub issue number to PATCH. */
346
645
  number: number;
646
+ /** Item title, surfaced in progress output. */
347
647
  title: string;
648
+ /** GitHub state assumed before the write (the inverse of `to`; re-checked live). */
348
649
  from: "open" | "closed";
650
+ /** Desired GitHub state derived from the pm status. */
349
651
  to: "open" | "closed";
350
652
  }
351
653
  export declare function planSync(items: PmItem[], repo: string): SyncPlanEntry[];
@@ -377,27 +679,99 @@ export interface ExportApplyResult {
377
679
  failures: ExportApplyFailure[];
378
680
  }
379
681
  export type ExportRequestFn = (method: string, url: string, token: string | undefined, payload?: string) => Promise<unknown>;
682
+ /**
683
+ * Apply an already-built export plan to GitHub, one issue at a time.
684
+ *
685
+ * Each create/update is isolated: a single failed write (e.g. a 422 for a label
686
+ * that does not exist on the repo) is recorded and the loop CONTINUES with the
687
+ * remaining items — it never abandons the rest of the batch. Pure aside from the
688
+ * injected `requestFn`, so the per-item isolation is directly unit-testable
689
+ * without real network I/O.
690
+ */
380
691
  export declare function applyExportPlan(plan: ExportPlanEntry[], repo: string, token: string | undefined, requestFn: ExportRequestFn): Promise<ExportApplyResult>;
692
+ /**
693
+ * Decide the exit status of a completed `export --apply` batch.
694
+ *
695
+ * Per-item continuation is intentional (one bad item never aborts the batch),
696
+ * but the batch as a whole must still report honestly: a non-empty plan that
697
+ * wrote NOTHING while recording at least one failure is a total failure, so it
698
+ * returns a {@link CommandError} to throw; otherwise `undefined` (exit 0).
699
+ *
700
+ * @param plan - The plan that was applied.
701
+ * @param result - What the apply loop actually wrote.
702
+ * @param repo - Target repo, included in the failure message.
703
+ * @returns A `CommandError` to throw on total failure, else `undefined`.
704
+ */
381
705
  export declare function applyOutcomeError(plan: ExportPlanEntry[], result: ExportApplyResult, repo: string): CommandError | undefined;
706
+ /**
707
+ * Build the GitHub Search-API URL for issues in one repo matching a free-text query.
708
+ *
709
+ * Restricted to `type:issue repo:<repo>` so a search never leaks across repos.
710
+ *
711
+ * @param repo - The `owner/repo` to search within.
712
+ * @param query - Free-text query string.
713
+ * @returns The encoded search URL.
714
+ */
382
715
  export declare function buildSearchUrl(repo: string, query: string): string;
716
+ /**
717
+ * Map GitHub search result numbers to local pm-item hits.
718
+ *
719
+ * For every returned issue number, looks up the pm item carrying the matching
720
+ * `gh:repo#N` provenance tag; only locally-present items become hits (the
721
+ * runtime would drop the rest anyway). Score preserves GitHub's ranking,
722
+ * normalized into `(0, 1]` so hits clear pm's default threshold.
723
+ *
724
+ * @param matchedNumbers - Issue numbers GitHub returned, best-first.
725
+ * @param repo - The repo the search ran against.
726
+ * @param itemsByProvenance - Local items keyed `owner/repo#N`.
727
+ * @returns One hit per locally-present matching item.
728
+ */
383
729
  export declare function mapSearchHits(matchedNumbers: number[], repo: string, itemsByProvenance: Map<string, PmItem>): Array<{
384
730
  id: string;
385
731
  score: number;
386
732
  matched_fields: string[];
387
733
  }>;
734
+ /**
735
+ * Resolve the search target repo for the GitHub search provider.
736
+ *
737
+ * An explicit option wins, then the `PM_GITHUB_REPO` env var so a workspace can
738
+ * pin its upstream. Returns `undefined` when neither yields an `owner/repo`.
739
+ *
740
+ * @param options - The raw option object from the search context.
741
+ * @returns The resolved `owner/repo`, or `undefined`.
742
+ */
388
743
  export declare function resolveSearchRepo(options: Record<string, unknown>): string | undefined;
744
+ /**
745
+ * Structured result of `pm github validate`.
746
+ *
747
+ * Reports the resolved token source, whether the `gh` CLI is installed, the
748
+ * current rate-limit snapshot, and (when a repo is given) its reachability.
749
+ * `ok` is the overall pass/fail; `messages` carries the human-readable detail.
750
+ */
389
751
  export interface ValidateReport {
752
+ /** Overall pass/fail for the validation. */
390
753
  ok: boolean;
754
+ /** Whether the `gh` CLI was found on PATH. */
391
755
  gh_cli: boolean;
756
+ /** Whether any GitHub token was resolvable. */
392
757
  token: boolean;
758
+ /** Where the token came from: `env`, `gh`, or `none`. */
393
759
  token_source: "env" | "gh" | "none";
760
+ /** The repo examined, when one was supplied. */
394
761
  repo?: string;
762
+ /** Whether the repo responded 2xx. */
395
763
  repo_accessible?: boolean;
764
+ /** Raw HTTP status the repo check returned. */
396
765
  repo_status?: number;
766
+ /** Remaining requests in the current rate-limit window. */
397
767
  rate_limit_remaining?: number;
768
+ /** Total requests permitted per window. */
398
769
  rate_limit_limit?: number;
770
+ /** Epoch seconds at which the window resets. */
399
771
  rate_limit_reset?: number;
772
+ /** Whether the remaining quota is at/under the low-water mark. */
400
773
  rate_limit_low?: boolean;
774
+ /** Human-readable diagnostic lines. */
401
775
  messages: string[];
402
776
  }
403
777
  /** One project-summary node in a `projectsV2` connection. */
@@ -427,8 +801,15 @@ interface GraphqlListOwnerProjectsData {
427
801
  projectsV2?: GraphqlProjectsV2Connection | null;
428
802
  } | null;
429
803
  }
804
+ /**
805
+ * One page of a `projectsV2` connection as returned by GitHub GraphQL.
806
+ *
807
+ * Carries the page's nodes and the cursor threading needed to fetch the rest.
808
+ */
430
809
  export interface ProjectsV2Page {
810
+ /** Project-summary nodes on this page (null entries are redacted/inaccessible). */
431
811
  nodes?: Array<GraphqlProjectsV2Node | null>;
812
+ /** Relay pagination info for the next page. */
432
813
  pageInfo?: {
433
814
  hasNextPage?: boolean;
434
815
  endCursor?: string;
@@ -436,7 +817,31 @@ export interface ProjectsV2Page {
436
817
  }
437
818
  export declare function collectProjectsV2Pages(fetchPage: (cursor: string | undefined) => Promise<ProjectsV2Page | null | undefined>): Promise<GraphqlProjectsV2Node[]>;
438
819
  export type GraphQLTransport = (query: string, variables: Record<string, unknown>) => Promise<GraphqlListOwnerProjectsData>;
820
+ /**
821
+ * Resolve and paginate the `projectsV2` connection for a user-or-org owner.
822
+ *
823
+ * A login is either a user or an organization, never both; querying both in one
824
+ * request resolves the owner type without an extra round-trip, then this keeps
825
+ * paginating the connection that actually exists so owners with more than 100
826
+ * projects are fully listed instead of silently truncated.
827
+ *
828
+ * @param owner - The user or org login.
829
+ * @param graphQL - Injectable transport (query + variables → data), for tests.
830
+ * @returns Every project-summary node for the owner.
831
+ */
439
832
  export declare function listOwnerProjectsV2Nodes(owner: string, graphQL: GraphQLTransport): Promise<GraphqlProjectsV2Node[]>;
833
+ /**
834
+ * Build the `pm` CLI argv that applies one pull (project → pm) status transition.
835
+ *
836
+ * Pure so the pm lifecycle contract is unit-testable without spawning `pm`:
837
+ * `closed` goes through `pm close`; `canceled` keeps its distinct terminal state
838
+ * via `pm update --status canceled` with `--close-reason`; everything else is a
839
+ * plain `pm update --status`. See the long note above for the pm CLI contracts.
840
+ *
841
+ * @param entry - The pull-plan entry to render argv for.
842
+ * @param pmRoot - Workspace root for the `pm` invocation.
843
+ * @returns The argv array.
844
+ */
440
845
  export declare function buildPullEntryArgs(entry: PullPlanEntryLike, pmRoot: string): string[];
441
846
  interface PullPlanEntryLike {
442
847
  itemId: string;
@@ -445,6 +850,16 @@ interface PullPlanEntryLike {
445
850
  fromStatus: string;
446
851
  toStatus: string;
447
852
  }
853
+ /**
854
+ * Report whether a github subcommand will mutate GitHub.
855
+ *
856
+ * Feeds the host CLI's "mutating command" guard, accounting for the `--dry-run`
857
+ * and `--apply` overrides that turn an otherwise-mutating command into a preview.
858
+ *
859
+ * @param command - The subcommand string (e.g. `github sync`).
860
+ * @param options - The raw option object from the command handler.
861
+ * @returns True when the command will perform real GitHub writes.
862
+ */
448
863
  export declare function isMutatingGithubCommand(command: string, options: Record<string, unknown>): boolean;
449
864
  declare const _default: {
450
865
  name: string;