edsger 0.72.6 → 0.74.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 (33) hide show
  1. package/dist/api/intelligence-normalize.d.ts +25 -0
  2. package/dist/api/intelligence-normalize.js +103 -0
  3. package/dist/api/intelligence.js +113 -10
  4. package/dist/commands/features/index.d.ts +15 -0
  5. package/dist/commands/features/index.js +34 -0
  6. package/dist/commands/pr-resolve/index.d.ts +3 -1
  7. package/dist/commands/pr-resolve/index.js +12 -7
  8. package/dist/commands/pr-review/index.d.ts +3 -1
  9. package/dist/commands/pr-review/index.js +10 -6
  10. package/dist/commands/sync-github-pull-requests/index.d.ts +11 -0
  11. package/dist/commands/sync-github-pull-requests/index.js +42 -0
  12. package/dist/index.js +50 -4
  13. package/dist/phases/features/index.d.ts +65 -0
  14. package/dist/phases/features/index.js +292 -0
  15. package/dist/phases/features/mcp-server.d.ts +61 -0
  16. package/dist/phases/features/mcp-server.js +165 -0
  17. package/dist/phases/features/prompts.d.ts +32 -0
  18. package/dist/phases/features/prompts.js +92 -0
  19. package/dist/phases/features/types.d.ts +34 -0
  20. package/dist/phases/features/types.js +15 -0
  21. package/dist/phases/pr-resolve/index.d.ts +3 -1
  22. package/dist/phases/pr-resolve/index.js +12 -12
  23. package/dist/phases/pr-review/index.d.ts +3 -1
  24. package/dist/phases/pr-review/index.js +13 -16
  25. package/dist/phases/pr-shared/status.d.ts +18 -0
  26. package/dist/phases/pr-shared/status.js +37 -0
  27. package/dist/phases/sync-github-pull-requests/index.d.ts +23 -0
  28. package/dist/phases/sync-github-pull-requests/index.js +210 -0
  29. package/dist/phases/sync-github-pull-requests/state.d.ts +24 -0
  30. package/dist/phases/sync-github-pull-requests/state.js +16 -0
  31. package/dist/phases/sync-github-pull-requests/types.d.ts +22 -0
  32. package/dist/phases/sync-github-pull-requests/types.js +1 -0
  33. package/package.json +3 -3
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shapes the features MCP tools accept and return. The Zod schemas live in
3
+ * mcp-server.ts; these plain TS types are what the rest of the phase
4
+ * (orchestrator, persistence helpers, tests) consumes so they don't have
5
+ * to inflate Zod into their dependency graph.
6
+ */
7
+ // Defensive caps mirroring the DB CHECK constraints from
8
+ // 20260613000000_create_product_features.sql. Keeping the limits here lets
9
+ // the MCP tool reject oversized output with an actionable error message
10
+ // instead of getting a Postgres constraint violation.
11
+ export const FEATURE_NAME_MAX = 200;
12
+ export const FEATURE_DESCRIPTION_MAX = 10_000;
13
+ export const FEATURE_EVIDENCE_MAX = 4000;
14
+ export const FEATURE_REPOS_MAX = 20;
15
+ export const FEATURE_REPO_NAME_MAX = 200;
@@ -4,7 +4,9 @@
4
4
  * replies to every comment on GitHub explaining the decision.
5
5
  */
6
6
  export interface StandalonePRResolveOptions {
7
- productId: string;
7
+ productId?: string;
8
+ /** Set for repo-scoped PRs — routes the status write through Supabase. */
9
+ repositoryId?: string;
8
10
  pullRequestUrl: string;
9
11
  githubToken: string;
10
12
  owner: string;
@@ -6,13 +6,13 @@
6
6
  import { query } from '@anthropic-ai/claude-agent-sdk';
7
7
  import { Octokit } from '@octokit/rest';
8
8
  import { execSync } from 'child_process';
9
- import { callMcpEndpoint } from '../../api/mcp-client.js';
10
9
  import { DEFAULT_MODEL } from '../../constants.js';
11
10
  import { logError, logInfo, logSuccess } from '../../utils/logger.js';
12
11
  import { cleanupIssueRepo } from '../../workspace/workspace-manager.js';
13
12
  import { fetchUnresolvedReviewThreads } from '../code-refine-verification/github.js';
14
13
  import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
15
14
  import { parsePullRequestUrl } from '../pr-shared/context.js';
15
+ import { updatePullRequestStatus } from '../pr-shared/status.js';
16
16
  import { learnFromReviewFeedback } from './checklist-learner.js';
17
17
  import { hasResolveMarker, replyToReviewThread, resolveReviewThread, } from './github-reply.js';
18
18
  import { createResolveSystemPrompt, createResolveUserPrompt, } from './prompts.js';
@@ -221,8 +221,12 @@ export async function resolveStandalonePR(options) {
221
221
  }
222
222
  }
223
223
  logSuccess(`PR resolve completed: ${threadsAddressed} addressed, ${threadsSkipped} skipped, ${threadsErrored} errors`);
224
- // Learn from addressed comments to update code-review checklists
225
- if (options.learn !== false && threadsAddressed > 0 && resolveResult) {
224
+ // Learn from addressed comments to update code-review checklists.
225
+ // Product-scoped only repo-only mode passes learn=false.
226
+ if (options.learn !== false &&
227
+ options.productId &&
228
+ threadsAddressed > 0 &&
229
+ resolveResult) {
226
230
  await learnFromReviewFeedback({
227
231
  productId: options.productId,
228
232
  unresolvedThreads,
@@ -232,15 +236,11 @@ export async function resolveStandalonePR(options) {
232
236
  });
233
237
  }
234
238
  if (prId) {
235
- try {
236
- await callMcpEndpoint('pull_requests/update', {
237
- pull_request_id: prId,
238
- status: 'in_review',
239
- });
240
- }
241
- catch {
242
- // Non-critical
243
- }
239
+ await updatePullRequestStatus({
240
+ prId,
241
+ status: 'in_review',
242
+ repositoryId: options.repositoryId,
243
+ });
244
244
  }
245
245
  // Clean up workspace on success
246
246
  cleanupIssueRepo(repoPath);
@@ -3,7 +3,9 @@
3
3
  * Reviews a GitHub PR and posts review comments, without issue lifecycle dependency.
4
4
  */
5
5
  export interface StandalonePRReviewOptions {
6
- productId: string;
6
+ productId?: string;
7
+ /** Set for repo-scoped PRs — routes the status write through Supabase. */
8
+ repositoryId?: string;
7
9
  pullRequestUrl: string;
8
10
  githubToken: string;
9
11
  owner: string;
@@ -4,19 +4,19 @@
4
4
  */
5
5
  import { query } from '@anthropic-ai/claude-agent-sdk';
6
6
  import { Octokit } from '@octokit/rest';
7
- import { callMcpEndpoint } from '../../api/mcp-client.js';
8
7
  import { DEFAULT_MODEL } from '../../constants.js';
9
8
  import { logError, logInfo } from '../../utils/logger.js';
10
9
  import { buildLineToPositionMap, findClosestPosition, } from '../code-review/diff-utils.js';
11
10
  import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
12
11
  import { fetchStandalonePRContext, formatStandalonePRContextForPrompt, } from '../pr-shared/context.js';
12
+ import { updatePullRequestStatus } from '../pr-shared/status.js';
13
13
  import { createStandaloneReviewSystemPrompt, createStandaloneReviewUserPrompt, } from './prompts.js';
14
14
  /**
15
15
  * Review a standalone PR and post comments to GitHub.
16
16
  */
17
17
  // eslint-disable-next-line complexity
18
18
  export async function reviewStandalonePR(options) {
19
- const { pullRequestUrl, githubToken, verbose, prId } = options;
19
+ const { pullRequestUrl, githubToken, verbose, prId, repositoryId } = options;
20
20
  logInfo(`Starting standalone PR review: ${pullRequestUrl}`);
21
21
  try {
22
22
  // Fetch PR context
@@ -95,7 +95,11 @@ export async function reviewStandalonePR(options) {
95
95
  'Code review completed. No issues found.',
96
96
  });
97
97
  if (prId) {
98
- await updatePRStatus(prId, 'in_review');
98
+ await updatePullRequestStatus({
99
+ prId,
100
+ status: 'in_review',
101
+ repositoryId,
102
+ });
99
103
  }
100
104
  return {
101
105
  status: 'success',
@@ -154,7 +158,11 @@ export async function reviewStandalonePR(options) {
154
158
  body: `${summary || 'Code review completed.'}\n\n**Note**: Some review comments could not be posted because they referenced lines not present in the diff.`,
155
159
  });
156
160
  if (prId) {
157
- await updatePRStatus(prId, 'in_review');
161
+ await updatePullRequestStatus({
162
+ prId,
163
+ status: 'in_review',
164
+ repositoryId,
165
+ });
158
166
  }
159
167
  return {
160
168
  status: 'success',
@@ -179,7 +187,7 @@ export async function reviewStandalonePR(options) {
179
187
  });
180
188
  logInfo(`GitHub review created: ${review.data.html_url}`);
181
189
  if (prId) {
182
- await updatePRStatus(prId, 'in_review');
190
+ await updatePullRequestStatus({ prId, status: 'in_review', repositoryId });
183
191
  }
184
192
  return {
185
193
  status: 'success',
@@ -199,14 +207,3 @@ export async function reviewStandalonePR(options) {
199
207
  };
200
208
  }
201
209
  }
202
- async function updatePRStatus(prId, status) {
203
- try {
204
- await callMcpEndpoint('pull_requests/update', {
205
- pull_request_id: prId,
206
- status,
207
- });
208
- }
209
- catch {
210
- // Non-critical, don't fail the review
211
- }
212
- }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Update a pull_requests row's status, choosing the right transport for the
3
+ * scope:
4
+ *
5
+ * - Repo-scoped PRs (repo-only mode) have no product, so the MCP
6
+ * pull_requests/update endpoint — which authorises by product/issue —
7
+ * can't see them. Write straight through the CLI's RLS-scoped Supabase
8
+ * session instead (team-membership RLS authorises the repo PR).
9
+ * - Product/issue PRs keep using the MCP endpoint.
10
+ *
11
+ * Best-effort: a status update is never allowed to fail the review/resolve.
12
+ */
13
+ export declare function updatePullRequestStatus(opts: {
14
+ prId: string;
15
+ status: string;
16
+ /** When set, the PR is repo-scoped and updated directly via Supabase. */
17
+ repositoryId?: string;
18
+ }): Promise<void>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Update a pull_requests row's status, choosing the right transport for the
3
+ * scope:
4
+ *
5
+ * - Repo-scoped PRs (repo-only mode) have no product, so the MCP
6
+ * pull_requests/update endpoint — which authorises by product/issue —
7
+ * can't see them. Write straight through the CLI's RLS-scoped Supabase
8
+ * session instead (team-membership RLS authorises the repo PR).
9
+ * - Product/issue PRs keep using the MCP endpoint.
10
+ *
11
+ * Best-effort: a status update is never allowed to fail the review/resolve.
12
+ */
13
+ import { callMcpEndpoint } from '../../api/mcp-client.js';
14
+ import { ensureSupabaseSession, getSupabase } from '../../supabase/client.js';
15
+ export async function updatePullRequestStatus(opts) {
16
+ const { prId, status, repositoryId } = opts;
17
+ try {
18
+ if (repositoryId) {
19
+ const ready = await ensureSupabaseSession();
20
+ if (!ready) {
21
+ return;
22
+ }
23
+ await getSupabase()
24
+ .from('pull_requests')
25
+ .update({ status, updated_at: new Date().toISOString() })
26
+ .eq('id', prId);
27
+ return;
28
+ }
29
+ await callMcpEndpoint('pull_requests/update', {
30
+ pull_request_id: prId,
31
+ status,
32
+ });
33
+ }
34
+ catch {
35
+ // Non-critical — don't fail the review/resolve on a status write.
36
+ }
37
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * sync-github-pull-requests phase: pull every pull request from a repository's
3
+ * connected GitHub repo and mirror them into the `pull_requests` table scoped
4
+ * by `repository_id`. Deterministic — no Claude Agent SDK; plain Octokit plus
5
+ * the user's RLS-scoped Supabase session.
6
+ *
7
+ * Idempotent: rows are keyed on (repository_id, pull_request_number). New PRs
8
+ * are inserted, and the GitHub-owned fields (status/title/description) of
9
+ * already-synced PRs are refreshed. Re-running never duplicates a PR.
10
+ */
11
+ import { Octokit } from '@octokit/rest';
12
+ import { type GitHubPullRequestLite, type SyncPullRequestsResult } from './types.js';
13
+ export interface SyncGithubPullRequestsOptions {
14
+ repositoryId: string;
15
+ /** GitHub installation token (or PAT). */
16
+ githubToken: string;
17
+ owner: string;
18
+ repo: string;
19
+ verbose?: boolean;
20
+ }
21
+ /** Pull every pull request from the repo, paginated (state=all). */
22
+ export declare function fetchAllRepoPullRequests(octokit: Octokit, owner: string, repo: string): Promise<GitHubPullRequestLite[]>;
23
+ export declare function syncGithubPullRequests(options: SyncGithubPullRequestsOptions): Promise<SyncPullRequestsResult>;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * sync-github-pull-requests phase: pull every pull request from a repository's
3
+ * connected GitHub repo and mirror them into the `pull_requests` table scoped
4
+ * by `repository_id`. Deterministic — no Claude Agent SDK; plain Octokit plus
5
+ * the user's RLS-scoped Supabase session.
6
+ *
7
+ * Idempotent: rows are keyed on (repository_id, pull_request_number). New PRs
8
+ * are inserted, and the GitHub-owned fields (status/title/description) of
9
+ * already-synced PRs are refreshed. Re-running never duplicates a PR.
10
+ */
11
+ import { retry } from '@octokit/plugin-retry';
12
+ import { throttling } from '@octokit/plugin-throttling';
13
+ import { Octokit } from '@octokit/rest';
14
+ import { ensureSupabaseSession, getSupabase } from '../../supabase/client.js';
15
+ import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
16
+ import { acquireSyncGithubPullRequestsLock, updateSyncGithubPullRequestsState, } from './state.js';
17
+ // The plugins use `@octokit/core` 7.x types, while @octokit/rest@20 hoists
18
+ // to core 5.x at the workspace root. Runtime is fine; the cast tells TS to
19
+ // accept the plugin chain (same pattern as sync-github-issues).
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- octokit version drift across the workspace
21
+ const ResilientOctokit = Octokit.plugin(retry, throttling);
22
+ /** GitHub list-pulls max page size. */
23
+ const PER_PAGE = 100;
24
+ /** Build an Octokit instance with retry + throttling configured for sync. */
25
+ function buildOctokit(token) {
26
+ return new ResilientOctokit({
27
+ auth: token,
28
+ retry: { doNotRetry: [400, 401, 403, 404, 422] },
29
+ throttle: {
30
+ onRateLimit: (retryAfter, options) => {
31
+ logWarning(`GitHub rate limit hit on ${options.method ?? '?'} ${options.url ?? '?'} — retry in ${retryAfter}s`);
32
+ return (options.request?.retryCount ?? 0) < 2;
33
+ },
34
+ onSecondaryRateLimit: (retryAfter, options) => {
35
+ logWarning(`GitHub secondary rate limit on ${options.method ?? '?'} ${options.url ?? '?'} — retry in ${retryAfter}s`);
36
+ return true;
37
+ },
38
+ },
39
+ });
40
+ }
41
+ /** Pull every pull request from the repo, paginated (state=all). */
42
+ export async function fetchAllRepoPullRequests(octokit, owner, repo) {
43
+ const all = [];
44
+ let page = 1;
45
+ for (;;) {
46
+ const { data } = await octokit.request('GET /repos/{owner}/{repo}/pulls', {
47
+ owner,
48
+ repo,
49
+ state: 'all',
50
+ per_page: PER_PAGE,
51
+ page,
52
+ sort: 'created',
53
+ direction: 'asc',
54
+ });
55
+ if (data.length === 0) {
56
+ break;
57
+ }
58
+ all.push(...data);
59
+ if (data.length < PER_PAGE) {
60
+ break;
61
+ }
62
+ page += 1;
63
+ }
64
+ return all;
65
+ }
66
+ /** Map a GitHub PR's open/closed/merged state to our PR status enum. */
67
+ function toStatus(pr) {
68
+ if (pr.merged_at) {
69
+ return 'merged';
70
+ }
71
+ if (pr.state === 'closed') {
72
+ return 'closed';
73
+ }
74
+ return 'pr_opened';
75
+ }
76
+ export async function syncGithubPullRequests(options) {
77
+ const { repositoryId, githubToken, owner, repo, verbose } = options;
78
+ // Guard against two concurrent syncs of the same repo racing into duplicate
79
+ // inserts (the unique index would reject the loser, but failing fast is
80
+ // friendlier than surfacing a constraint error).
81
+ const lock = acquireSyncGithubPullRequestsLock(repositoryId);
82
+ if (!lock) {
83
+ return {
84
+ status: 'error',
85
+ message: 'Another GitHub pull request sync is already running for this repository',
86
+ };
87
+ }
88
+ try {
89
+ updateSyncGithubPullRequestsState(repositoryId, {
90
+ lastAttemptedAt: new Date().toISOString(),
91
+ });
92
+ const ready = await ensureSupabaseSession();
93
+ if (!ready) {
94
+ return {
95
+ status: 'error',
96
+ message: 'Supabase session unavailable. Sign in to the Edsger desktop app to authorize the CLI.',
97
+ };
98
+ }
99
+ const supabase = getSupabase();
100
+ logInfo(`Syncing GitHub pull requests for ${owner}/${repo} → repository ${repositoryId}`);
101
+ const octokit = buildOctokit(githubToken);
102
+ const remotePRs = await fetchAllRepoPullRequests(octokit, owner, repo);
103
+ logInfo(`Fetched ${remotePRs.length} pull requests from GitHub`);
104
+ // Existing repo-scoped PRs, keyed by GitHub PR number, so we never create
105
+ // a duplicate of one that was synced in a previous run.
106
+ const { data: existingRows, error: existingError } = await supabase
107
+ .from('pull_requests')
108
+ .select('id, pull_request_number, status, name, description')
109
+ .eq('repository_id', repositoryId);
110
+ if (existingError) {
111
+ throw new Error(`Failed to read existing pull requests: ${existingError.message}`);
112
+ }
113
+ const existingByNumber = new Map();
114
+ for (const row of existingRows ?? []) {
115
+ if (row.pull_request_number !== null) {
116
+ existingByNumber.set(row.pull_request_number, {
117
+ id: row.id,
118
+ status: row.status,
119
+ name: row.name,
120
+ description: row.description,
121
+ });
122
+ }
123
+ }
124
+ const toInsert = [];
125
+ const toUpdate = [];
126
+ for (const pr of remotePRs) {
127
+ const status = toStatus(pr);
128
+ const existing = existingByNumber.get(pr.number);
129
+ if (!existing) {
130
+ toInsert.push({
131
+ repository_id: repositoryId,
132
+ sequence: pr.number,
133
+ name: pr.title,
134
+ description: pr.body ?? null,
135
+ branch_name: pr.head?.ref ?? null,
136
+ pull_request_url: pr.html_url,
137
+ pull_request_number: pr.number,
138
+ status,
139
+ });
140
+ continue;
141
+ }
142
+ // Refresh GitHub-owned fields when they drift (e.g. PR got merged/closed).
143
+ const patch = {};
144
+ if (existing.status !== status) {
145
+ patch.status = status;
146
+ }
147
+ if (existing.name !== pr.title) {
148
+ patch.name = pr.title;
149
+ }
150
+ if ((existing.description ?? null) !== (pr.body ?? null)) {
151
+ patch.description = pr.body ?? null;
152
+ }
153
+ if (Object.keys(patch).length > 0) {
154
+ toUpdate.push({ id: existing.id, patch });
155
+ }
156
+ }
157
+ let createdCount = 0;
158
+ if (toInsert.length > 0) {
159
+ const { error: insertError } = await supabase
160
+ .from('pull_requests')
161
+ .insert(toInsert);
162
+ if (insertError) {
163
+ throw new Error(`Failed to insert pull requests: ${insertError.message}`);
164
+ }
165
+ createdCount = toInsert.length;
166
+ if (verbose) {
167
+ logInfo(`Inserted ${createdCount} new pull requests`);
168
+ }
169
+ }
170
+ let updatedCount = 0;
171
+ for (const { id, patch } of toUpdate) {
172
+ const { error: updateError } = await supabase
173
+ .from('pull_requests')
174
+ .update({ ...patch, updated_at: new Date().toISOString() })
175
+ .eq('id', id);
176
+ if (updateError) {
177
+ logWarning(`Failed to refresh pull request ${id}: ${updateError.message}`);
178
+ continue;
179
+ }
180
+ updatedCount++;
181
+ }
182
+ const summary = `fetched=${remotePRs.length} created=${createdCount} updated=${updatedCount}`;
183
+ logSuccess(`GitHub PR sync complete: ${summary}`);
184
+ updateSyncGithubPullRequestsState(repositoryId, {
185
+ lastSyncedAt: new Date().toISOString(),
186
+ lastRepoFullName: `${owner}/${repo}`,
187
+ lastError: undefined,
188
+ });
189
+ return {
190
+ status: 'success',
191
+ message: summary,
192
+ repository: `${owner}/${repo}`,
193
+ fetchedCount: remotePRs.length,
194
+ createdCount,
195
+ updatedCount,
196
+ };
197
+ }
198
+ catch (error) {
199
+ const errorMessage = error instanceof Error ? error.message : String(error);
200
+ logError(`GitHub PR sync failed: ${errorMessage}`);
201
+ updateSyncGithubPullRequestsState(repositoryId, { lastError: errorMessage });
202
+ return {
203
+ status: 'error',
204
+ message: `GitHub PR sync failed: ${errorMessage}`,
205
+ };
206
+ }
207
+ finally {
208
+ lock.release();
209
+ }
210
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Per-repository sync state for sync-github-pull-requests. Stored at
3
+ * `~/.edsger/sync-github-pull-requests-state/<repositoryId>.json`.
4
+ *
5
+ * The lock guards against two concurrent syncs of the same repository racing
6
+ * each other into duplicate inserts. State fields track the last run for
7
+ * diagnostics and a future incremental-sync optimisation.
8
+ */
9
+ import { type LockHandle } from '../find-shared/scan-state.js';
10
+ export type { LockHandle };
11
+ export interface SyncGithubPullRequestsState {
12
+ /** ISO timestamp of the last successful sync. */
13
+ lastSyncedAt?: string;
14
+ /** ISO timestamp we last attempted (success or fail). */
15
+ lastAttemptedAt?: string;
16
+ /** Last error message, if the most recent run failed. */
17
+ lastError?: string;
18
+ /** GitHub repo we last synced from, as "owner/repo". */
19
+ lastRepoFullName?: string;
20
+ }
21
+ export declare const loadSyncGithubPullRequestsState: (productId: string) => SyncGithubPullRequestsState;
22
+ export declare const saveSyncGithubPullRequestsState: (productId: string, state: SyncGithubPullRequestsState) => void;
23
+ export declare const updateSyncGithubPullRequestsState: (productId: string, patch: Partial<SyncGithubPullRequestsState>) => SyncGithubPullRequestsState;
24
+ export declare const acquireSyncGithubPullRequestsLock: (productId: string, staleAfterMs?: number) => LockHandle | null;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Per-repository sync state for sync-github-pull-requests. Stored at
3
+ * `~/.edsger/sync-github-pull-requests-state/<repositoryId>.json`.
4
+ *
5
+ * The lock guards against two concurrent syncs of the same repository racing
6
+ * each other into duplicate inserts. State fields track the last run for
7
+ * diagnostics and a future incremental-sync optimisation.
8
+ */
9
+ import { createScanStateModule, } from '../find-shared/scan-state.js';
10
+ const m = createScanStateModule({
11
+ dirName: 'sync-github-pull-requests-state',
12
+ });
13
+ export const loadSyncGithubPullRequestsState = m.load;
14
+ export const saveSyncGithubPullRequestsState = m.save;
15
+ export const updateSyncGithubPullRequestsState = m.update;
16
+ export const acquireSyncGithubPullRequestsLock = m.acquireLock;
@@ -0,0 +1,22 @@
1
+ /** Minimal shape of a GitHub pull request from the list endpoint. */
2
+ export interface GitHubPullRequestLite {
3
+ number: number;
4
+ title: string;
5
+ body: string | null;
6
+ html_url: string;
7
+ state: string;
8
+ draft?: boolean;
9
+ merged_at: string | null;
10
+ updated_at: string;
11
+ head: {
12
+ ref: string;
13
+ };
14
+ }
15
+ export interface SyncPullRequestsResult {
16
+ status: 'success' | 'error';
17
+ message: string;
18
+ repository?: string;
19
+ fetchedCount?: number;
20
+ createdCount?: number;
21
+ updatedCount?: number;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.72.6",
3
+ "version": "0.74.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
@@ -54,8 +54,8 @@
54
54
  "commander": "^12.0.0",
55
55
  "cosmiconfig": "^9.0.0",
56
56
  "dotenv": "^16.4.5",
57
- "edsger-contract": "0.9.2",
58
- "edsger-tools": "0.9.2",
57
+ "edsger-contract": "0.10.0",
58
+ "edsger-tools": "0.10.0",
59
59
  "gray-matter": "^4.0.3",
60
60
  "zod": "^4.0.0"
61
61
  },