@h-rig/github-provider-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158

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.
@@ -15,59 +15,812 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
- // packages/github-provider-plugin/src/credentials.ts
19
- function selectedRepoTokenKey(input) {
20
- return `user:${input.userId}|repo:${input.owner}/${input.repo}|workspace:${input.workspaceId}`;
18
+ // packages/github-provider-plugin/src/issue-analysis.ts
19
+ import { createHash } from "crypto";
20
+ function stableIssueHash(issue) {
21
+ const labels = Array.isArray(issue.labels) ? [...issue.labels].map(String).sort() : [];
22
+ const body = typeof issue.body === "string" ? issue.body : "";
23
+ const title = typeof issue.title === "string" ? issue.title : "";
24
+ return createHash("sha256").update(JSON.stringify({ id: issue.id, title, body, labels, deps: issue.deps, status: issue.status })).digest("hex");
21
25
  }
22
- function cleanToken(value) {
23
- const trimmed = value?.trim() ?? "";
24
- return trimmed.length > 0 ? trimmed : null;
26
+ function renderIssueAnalysisPrompt(input) {
27
+ const issue = input.issue;
28
+ const neighbors = input.neighbors ?? [];
29
+ return [
30
+ "You are Rig issue analysis running inside Pi.",
31
+ "Return JSON only with optional metadataPatch, labelsToAdd, labelsToRemove, and generatedIssues; analyze backlog dependencies, children, readiness, size, risk, and planning.",
32
+ "Preserve all human-authored issue body content. Only propose edits for Rig-owned metadata/status sections, labels, and generated issues.",
33
+ "Generated issues must be concrete, minimal follow-up tasks and will be labeled rig:generated by Rig.",
34
+ "",
35
+ "Issue:",
36
+ JSON.stringify({
37
+ id: issue.id,
38
+ title: issue.title,
39
+ body: issue.body,
40
+ labels: issue.labels,
41
+ deps: issue.deps,
42
+ status: issue.status
43
+ }, null, 2),
44
+ "",
45
+ "Neighbor tasks:",
46
+ JSON.stringify(neighbors.map((task) => ({ id: task.id, title: task.title, status: task.status, deps: task.deps })), null, 2)
47
+ ].join(`
48
+ `);
25
49
  }
26
- function createGitHubCredentialProvider(options = {}) {
27
- const sessionTokens = options.sessionTokens ?? {};
28
- const hostToken = cleanToken(options.hostToken ?? process.env.GH_TOKEN ?? null);
50
+ function isRecord(value) {
51
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
52
+ }
53
+ function stringArray(value) {
54
+ if (!Array.isArray(value))
55
+ return;
56
+ return value.map(String).filter((entry) => entry.trim().length > 0);
57
+ }
58
+ function generatedIssues(value) {
59
+ if (!Array.isArray(value))
60
+ return;
61
+ return value.flatMap((entry) => {
62
+ if (!isRecord(entry) || typeof entry.title !== "string")
63
+ return [];
64
+ return [{
65
+ title: entry.title,
66
+ body: typeof entry.body === "string" ? entry.body : "",
67
+ labels: stringArray(entry.labels) ?? [],
68
+ ...Array.isArray(entry.dependsOn) ? { dependsOn: entry.dependsOn.map(String) } : {}
69
+ }];
70
+ });
71
+ }
72
+ function findJsonLikeText(value) {
73
+ if (typeof value === "string") {
74
+ const trimmed = value.trim();
75
+ if (trimmed.startsWith("{") || trimmed.startsWith("```"))
76
+ return trimmed;
77
+ return null;
78
+ }
79
+ if (Array.isArray(value)) {
80
+ for (const entry of value) {
81
+ const found = findJsonLikeText(entry);
82
+ if (found)
83
+ return found;
84
+ }
85
+ return null;
86
+ }
87
+ if (!isRecord(value))
88
+ return null;
89
+ for (const key of ["text", "content", "message", "output_text", "response", "stdout"]) {
90
+ const found = findJsonLikeText(value[key]);
91
+ if (found)
92
+ return found;
93
+ }
94
+ for (const entry of Object.values(value)) {
95
+ const found = findJsonLikeText(entry);
96
+ if (found)
97
+ return found;
98
+ }
99
+ return null;
100
+ }
101
+ function candidateAnalysisObject(value) {
102
+ if (!isRecord(value))
103
+ return null;
104
+ if (isRecord(value.result))
105
+ return candidateAnalysisObject(value.result) ?? value.result;
106
+ if (isRecord(value.analysis))
107
+ return candidateAnalysisObject(value.analysis) ?? value.analysis;
108
+ if (isRecord(value.metadataPatch) || Array.isArray(value.labelsToAdd) || Array.isArray(value.labelsToRemove) || Array.isArray(value.generatedIssues)) {
109
+ return value;
110
+ }
111
+ const nested = findJsonLikeText(value);
112
+ if (nested && nested !== JSON.stringify(value)) {
113
+ try {
114
+ const parsedNested = JSON.parse(nested.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1]?.trim() ?? nested);
115
+ return candidateAnalysisObject(parsedNested);
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function parseIssueAnalysisResult(raw) {
123
+ let parsed = raw;
124
+ if (typeof raw === "string") {
125
+ const trimmed = raw.trim();
126
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1]?.trim();
127
+ try {
128
+ parsed = JSON.parse(fenced ?? trimmed);
129
+ } catch {
130
+ const lastJsonLine = trimmed.split(/\r?\n/).reverse().find((line) => line.trim().startsWith("{"));
131
+ parsed = lastJsonLine ? JSON.parse(lastJsonLine) : {};
132
+ }
133
+ }
134
+ const candidate = candidateAnalysisObject(parsed);
135
+ if (!candidate)
136
+ return {};
137
+ const result = {};
138
+ if (isRecord(candidate.metadataPatch))
139
+ result.metadataPatch = candidate.metadataPatch;
140
+ const add = stringArray(candidate.labelsToAdd);
141
+ if (add?.length)
142
+ result.labelsToAdd = add;
143
+ const remove = stringArray(candidate.labelsToRemove);
144
+ if (remove?.length)
145
+ result.labelsToRemove = remove;
146
+ const generated = generatedIssues(candidate.generatedIssues);
147
+ if (generated?.length)
148
+ result.generatedIssues = generated;
149
+ return result;
150
+ }
151
+ function createDefaultPiIssueAnalysisCommandRunner() {
152
+ return async (command, args, options) => {
153
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
154
+ const proc = Bun.spawn([command, ...args], {
155
+ stdout: "pipe",
156
+ stderr: "pipe",
157
+ env
158
+ });
159
+ let timedOut = false;
160
+ const timer = setTimeout(() => {
161
+ timedOut = true;
162
+ proc.kill();
163
+ }, options.timeoutMs);
164
+ try {
165
+ const [stdout, stderr, exitCode] = await Promise.all([
166
+ new Response(proc.stdout).text(),
167
+ new Response(proc.stderr).text(),
168
+ proc.exited
169
+ ]);
170
+ return {
171
+ exitCode: timedOut && exitCode === 0 ? 1 : exitCode,
172
+ stdout,
173
+ stderr: timedOut && stderr.trim().length === 0 ? `Pi issue analysis timed out after ${options.timeoutMs}ms` : stderr
174
+ };
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ };
179
+ }
180
+ function createPiIssueAnalyzer(input = {}) {
181
+ const piBinary = input.piBinary ?? process.env.RIG_ISSUE_ANALYSIS_PI_BINARY ?? "pi";
182
+ const timeoutMs = Math.max(1000, Math.trunc(input.timeoutMs ?? Number(process.env.RIG_ISSUE_ANALYSIS_TIMEOUT_MS ?? 120000)));
183
+ const runCommand = input.runCommand ?? createDefaultPiIssueAnalysisCommandRunner();
184
+ return async ({ prompt }) => {
185
+ const args = ["--print", "--mode", "json", "--no-session"];
186
+ const provider = input.provider?.trim() || process.env.RIG_ISSUE_ANALYSIS_PROVIDER?.trim() || process.env.RIG_PI_PROVIDER?.trim();
187
+ const model = input.model?.trim() || process.env.RIG_ISSUE_ANALYSIS_MODEL?.trim() || process.env.RIG_PI_MODEL?.trim() || "openai-codex/gpt-5.5";
188
+ if (provider)
189
+ args.push("--provider", provider);
190
+ if (model)
191
+ args.push("--model", model);
192
+ args.push(prompt);
193
+ const result = await runCommand(piBinary, args, { timeoutMs, ...input.env ? { env: input.env } : {} });
194
+ if (result.exitCode !== 0) {
195
+ throw new Error(`Pi issue analysis failed (exit ${result.exitCode}): ${result.stderr ?? result.stdout}`);
196
+ }
197
+ return parseIssueAnalysisResult(result.stdout);
198
+ };
199
+ }
200
+ function defaultStatusComment(input) {
201
+ const changes = [
202
+ input.result.metadataPatch ? "metadata" : null,
203
+ input.result.labelsToAdd?.length ? `labels added: ${input.result.labelsToAdd.join(", ")}` : null,
204
+ input.result.labelsToRemove?.length ? `labels removed: ${input.result.labelsToRemove.join(", ")}` : null,
205
+ input.result.generatedIssues?.length ? `generated issues: ${input.result.generatedIssues.length}` : null
206
+ ].filter((entry) => Boolean(entry));
207
+ if (changes.length === 0)
208
+ return null;
209
+ return [
210
+ "<!-- rig:status-comment -->",
211
+ "### Rig issue analysis",
212
+ "",
213
+ `Analyzed issue ${input.issue.id}${input.reason ? ` (${input.reason})` : ""}.`,
214
+ "",
215
+ ...changes.map((change) => `- ${change}`)
216
+ ].join(`
217
+ `);
218
+ }
219
+ function uniqueLabels(labels, required = []) {
220
+ return [...new Set([...labels ?? [], ...required].map((label) => label.trim()).filter(Boolean))];
221
+ }
222
+ function createIssueAnalysisWriteBack(input) {
223
+ return async ({ issue, result, reason }) => {
224
+ if (result.metadataPatch && Object.keys(result.metadataPatch).length > 0) {
225
+ if (!input.target.updateTask)
226
+ throw new Error("Issue analysis writeback requires updateTask for metadata patches.");
227
+ await input.target.updateTask(issue.id, { metadata: result.metadataPatch });
228
+ }
229
+ if (result.labelsToAdd?.length) {
230
+ if (!input.target.addLabels)
231
+ throw new Error("Issue analysis writeback requires addLabels for labelsToAdd.");
232
+ await input.target.addLabels(issue.id, uniqueLabels(result.labelsToAdd));
233
+ }
234
+ if (result.labelsToRemove?.length) {
235
+ if (!input.target.removeLabels)
236
+ throw new Error("Issue analysis writeback requires removeLabels for labelsToRemove.");
237
+ await input.target.removeLabels(issue.id, uniqueLabels(result.labelsToRemove));
238
+ }
239
+ const comment = (input.buildStatusComment ?? defaultStatusComment)({ issue, result, ...reason !== undefined ? { reason } : {} });
240
+ if (comment?.trim()) {
241
+ if (!input.target.updateTask)
242
+ throw new Error("Issue analysis writeback requires updateTask for sticky status comments.");
243
+ await input.target.updateTask(issue.id, { comment });
244
+ }
245
+ for (const generated of result.generatedIssues ?? []) {
246
+ if (!input.target.createIssue)
247
+ throw new Error("Issue analysis writeback requires createIssue for generated issues.");
248
+ await input.target.createIssue({
249
+ title: generated.title,
250
+ body: generated.dependsOn?.length ? `${generated.body.trimEnd()}
251
+
252
+ depends-on: ${generated.dependsOn.map((dep) => dep.startsWith("#") ? dep : `#${dep}`).join(", ")}
253
+ ` : generated.body,
254
+ labels: uniqueLabels(generated.labels, ["rig:generated"])
255
+ });
256
+ }
257
+ };
258
+ }
259
+ function sourceWithWriteBackCapabilities(source) {
260
+ const candidate = source;
261
+ if (typeof candidate.updateTask !== "function")
262
+ return null;
29
263
  return {
30
- async resolveGitHubToken(input) {
31
- const owner = input.owner.trim();
32
- const repo = input.repo.trim();
33
- const workspaceId = input.workspaceId.trim();
34
- const userId = input.userId?.trim() ?? "";
35
- if (input.purpose === "selected-repo") {
36
- if (!owner || !repo || !workspaceId || !userId) {
37
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
38
- }
39
- const token = cleanToken(sessionTokens[selectedRepoTokenKey({ owner, repo, workspaceId, userId })]);
40
- if (!token) {
41
- throw new Error("No signed-in GitHub token is available for the selected repo; sign in to GitHub for this workspace.");
264
+ ...typeof candidate.get === "function" ? { get: candidate.get.bind(candidate) } : {},
265
+ updateTask: candidate.updateTask.bind(candidate),
266
+ ...typeof candidate.addLabels === "function" ? { addLabels: candidate.addLabels.bind(candidate) } : {},
267
+ ...typeof candidate.removeLabels === "function" ? { removeLabels: candidate.removeLabels.bind(candidate) } : {},
268
+ ...typeof candidate.createIssue === "function" ? { createIssue: candidate.createIssue.bind(candidate) } : {}
269
+ };
270
+ }
271
+ function issueAnalysisEnabled(config) {
272
+ const issueAnalysis = config.issueAnalysis;
273
+ if (!issueAnalysis)
274
+ return false;
275
+ if (issueAnalysis.enabled !== true)
276
+ return false;
277
+ if (issueAnalysis.mode === "off")
278
+ return false;
279
+ if (issueAnalysis.harness && issueAnalysis.harness !== "pi")
280
+ return false;
281
+ return true;
282
+ }
283
+ function createConfiguredIssueAnalysisRunner(input) {
284
+ if (!issueAnalysisEnabled(input.context.config))
285
+ return null;
286
+ const source = input.context.taskSourceRegistry.list()[0];
287
+ if (!source)
288
+ return null;
289
+ const target = sourceWithWriteBackCapabilities(source);
290
+ if (!target)
291
+ return null;
292
+ const analyzer = input.analyzer ?? createPiIssueAnalyzer({
293
+ ...input.runCommand ? { runCommand: input.runCommand } : {},
294
+ ...input.context.config.issueAnalysis?.model ? { model: input.context.config.issueAnalysis.model } : {}
295
+ });
296
+ const baseWriteBack = createIssueAnalysisWriteBack({ target });
297
+ const service = createIssueAnalysisService({
298
+ analyzer,
299
+ writeBack: async (writeBackInput) => {
300
+ await baseWriteBack(writeBackInput);
301
+ await input.onWriteBack?.();
302
+ }
303
+ });
304
+ return createContinuousIssueAnalysisRunner({
305
+ loadIssues: async () => [...await source.list()],
306
+ service,
307
+ ...input.intervalMs !== undefined ? { intervalMs: input.intervalMs } : {},
308
+ reason: "continuous-issue-analysis",
309
+ ...input.setIntervalFn ? { setIntervalFn: input.setIntervalFn } : {},
310
+ ...input.clearIntervalFn ? { clearIntervalFn: input.clearIntervalFn } : {},
311
+ ...input.onError ? { onError: input.onError } : {}
312
+ });
313
+ }
314
+ function createIssueAnalysisService(input) {
315
+ const analyzedHashes = new Map;
316
+ return {
317
+ async analyze(issues, options = {}) {
318
+ const results = [];
319
+ const neighbors = options.neighbors ?? issues;
320
+ for (const issue of issues) {
321
+ const hash = stableIssueHash(issue);
322
+ if (analyzedHashes.get(issue.id) === hash)
323
+ continue;
324
+ const prompt = renderIssueAnalysisPrompt({ issue, neighbors: neighbors.filter((candidate) => candidate.id !== issue.id) });
325
+ const result = await input.analyzer({ issue, neighbors, prompt });
326
+ analyzedHashes.set(issue.id, hash);
327
+ if (result.metadataPatch || result.labelsToAdd?.length || result.labelsToRemove?.length || result.generatedIssues?.length) {
328
+ await input.writeBack?.({ issue, result, ...options.reason !== undefined ? { reason: options.reason } : {} });
42
329
  }
43
- return { token, source: "signed-in-user" };
330
+ results.push({ issue, result });
44
331
  }
45
- if (hostToken) {
46
- return { token: hostToken, source: "host-admin-fallback" };
47
- }
48
- throw new Error("No host GitHub token is configured for the explicit admin fallback operation.");
332
+ return results;
333
+ },
334
+ clearCache() {
335
+ analyzedHashes.clear();
336
+ }
337
+ };
338
+ }
339
+ function createContinuousIssueAnalysisRunner(input) {
340
+ const intervalMs = Math.max(1000, Math.trunc(input.intervalMs ?? 60000));
341
+ const setIntervalFn = input.setIntervalFn ?? ((callback, ms) => setInterval(() => {
342
+ callback();
343
+ }, ms));
344
+ const clearIntervalFn = input.clearIntervalFn ?? ((timer2) => clearInterval(timer2));
345
+ let timer;
346
+ let running = false;
347
+ let inFlight = null;
348
+ const tick = async (reason = input.reason ?? "continuous") => {
349
+ if (inFlight)
350
+ return inFlight;
351
+ inFlight = (async () => {
352
+ const issues = await input.loadIssues();
353
+ return input.service.analyze(issues, { reason });
354
+ })();
355
+ try {
356
+ return await inFlight;
357
+ } finally {
358
+ inFlight = null;
359
+ }
360
+ };
361
+ return {
362
+ start() {
363
+ if (running)
364
+ return;
365
+ running = true;
366
+ timer = setIntervalFn(async () => {
367
+ try {
368
+ await tick();
369
+ } catch (error) {
370
+ input.onError?.(error);
371
+ }
372
+ }, intervalMs);
373
+ },
374
+ stop() {
375
+ if (!running)
376
+ return;
377
+ running = false;
378
+ if (timer !== undefined)
379
+ clearIntervalFn(timer);
380
+ timer = undefined;
381
+ },
382
+ tick,
383
+ isRunning() {
384
+ return running;
49
385
  }
50
386
  };
51
387
  }
52
- var init_credentials = () => {};
388
+ var init_issue_analysis = () => {};
389
+
390
+ // packages/github-provider-plugin/src/triage-run.ts
391
+ var exports_triage_run = {};
392
+ __export(exports_triage_run, {
393
+ runIssueAnalysisTriage: () => runIssueAnalysisTriage
394
+ });
395
+ import { buildPluginHostContext } from "@rig/core/plugin-host-context";
396
+ function summarizeResults(results) {
397
+ return results.reduce((summary, entry) => {
398
+ if (entry.result.metadataPatch && Object.keys(entry.result.metadataPatch).length > 0) {
399
+ summary.metadataPatches += 1;
400
+ }
401
+ summary.labelsAdded += entry.result.labelsToAdd?.length ?? 0;
402
+ summary.labelsRemoved += entry.result.labelsToRemove?.length ?? 0;
403
+ summary.generatedIssues += entry.result.generatedIssues?.length ?? 0;
404
+ return summary;
405
+ }, { metadataPatches: 0, labelsAdded: 0, labelsRemoved: 0, generatedIssues: 0 });
406
+ }
407
+ async function loadContext(projectRoot) {
408
+ const context = await buildPluginHostContext(projectRoot);
409
+ if (!context)
410
+ return null;
411
+ return {
412
+ config: {
413
+ ...context.config.issueAnalysis ? { issueAnalysis: context.config.issueAnalysis } : {}
414
+ },
415
+ taskSourceRegistry: context.taskSourceRegistry
416
+ };
417
+ }
418
+ async function runIssueAnalysisTriage(options) {
419
+ const reason = options.reason?.trim() || "triage";
420
+ const context = options.context ?? await loadContext(options.projectRoot);
421
+ if (!context) {
422
+ return {
423
+ ok: true,
424
+ enabled: false,
425
+ reason,
426
+ sourceId: null,
427
+ sourceKind: null,
428
+ analyzedIssues: 0,
429
+ metadataPatches: 0,
430
+ labelsAdded: 0,
431
+ labelsRemoved: 0,
432
+ generatedIssues: 0,
433
+ writeBackRefreshes: 0,
434
+ refreshedIssueCount: null,
435
+ skippedReason: "no-config"
436
+ };
437
+ }
438
+ const source = context.taskSourceRegistry.list()[0] ?? null;
439
+ const sourceId = source?.id ?? null;
440
+ const sourceKind = source?.kind ?? null;
441
+ if (!issueAnalysisEnabled(context.config)) {
442
+ return {
443
+ ok: true,
444
+ enabled: false,
445
+ reason,
446
+ sourceId,
447
+ sourceKind,
448
+ analyzedIssues: 0,
449
+ metadataPatches: 0,
450
+ labelsAdded: 0,
451
+ labelsRemoved: 0,
452
+ generatedIssues: 0,
453
+ writeBackRefreshes: 0,
454
+ refreshedIssueCount: null,
455
+ skippedReason: "disabled"
456
+ };
457
+ }
458
+ if (!source) {
459
+ throw new Error("Issue analysis is enabled, but no configured task source is registered.");
460
+ }
461
+ let writeBackRefreshes = 0;
462
+ let refreshedIssueCount = null;
463
+ const refreshSnapshotAfterWriteBack = async () => {
464
+ writeBackRefreshes += 1;
465
+ const refreshed = await source.list();
466
+ refreshedIssueCount = refreshed.length;
467
+ await options.onWriteBack?.();
468
+ };
469
+ const runner = createConfiguredIssueAnalysisRunner({
470
+ projectRoot: options.projectRoot,
471
+ context,
472
+ ...options.analyzer ? { analyzer: options.analyzer } : {},
473
+ ...options.runCommand ? { runCommand: options.runCommand } : {},
474
+ onWriteBack: refreshSnapshotAfterWriteBack
475
+ });
476
+ if (!runner) {
477
+ throw new Error(`Issue analysis is enabled for ${sourceKind ?? "the configured source"}, but that task source does not expose Rig write-back capabilities.`);
478
+ }
479
+ const results = await runner.tick(reason);
480
+ const summary = summarizeResults(results);
481
+ return {
482
+ ok: true,
483
+ enabled: true,
484
+ reason,
485
+ sourceId,
486
+ sourceKind,
487
+ analyzedIssues: results.length,
488
+ ...summary,
489
+ writeBackRefreshes,
490
+ refreshedIssueCount
491
+ };
492
+ }
493
+ var init_triage_run = __esm(() => {
494
+ init_issue_analysis();
495
+ });
496
+
497
+ // packages/github-provider-plugin/src/identity.ts
498
+ import {
499
+ RIG_WORKFLOW_STARTED
500
+ } from "@rig/contracts";
501
+ function stringField(value) {
502
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
503
+ }
504
+ function numericStringField(value) {
505
+ return typeof value === "number" && Number.isInteger(value) ? String(value) : stringField(value);
506
+ }
507
+ function asRecord(value) {
508
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
509
+ }
510
+ function customEntries(entries) {
511
+ const customs = [];
512
+ for (const entry of entries) {
513
+ if (entry.type === "custom")
514
+ customs.push(entry);
515
+ }
516
+ return customs;
517
+ }
518
+ function candidateFromStartedData(data) {
519
+ const record = asRecord(data);
520
+ if (!record)
521
+ return null;
522
+ const owner = asRecord(record.owner);
523
+ const selectedRepo = stringField(record.selectedRepo);
524
+ const githubUserId = stringField(owner?.githubUserId) ?? numericStringField(record.githubUserId) ?? numericStringField(record.userId);
525
+ const login = stringField(owner?.login) ?? stringField(record.login);
526
+ const namespaceKey = stringField(owner?.namespaceKey) ?? stringField(record.namespaceKey) ?? stringField(record.userNamespaceKey);
527
+ if (!selectedRepo && !githubUserId && !login && !namespaceKey)
528
+ return null;
529
+ return { selectedRepo, githubUserId, login, namespaceKey, source: "workflow-metadata" };
530
+ }
531
+ function workflowIdentityCandidate(entries) {
532
+ const customs = customEntries(entries);
533
+ for (let index = customs.length - 1;index >= 0; index -= 1) {
534
+ const entry = customs[index];
535
+ if (entry?.customType !== RIG_WORKFLOW_STARTED)
536
+ continue;
537
+ const candidate = candidateFromStartedData(entry.data);
538
+ if (candidate)
539
+ return candidate;
540
+ }
541
+ return null;
542
+ }
543
+ function ompGitHubIdentityCandidate(ctx) {
544
+ const account = asRecord(ctx.modelRegistry?.authStorage?.getOAuthAccountIdentity?.("github-copilot", ctx.sessionManager.getSessionId()));
545
+ if (!account)
546
+ return null;
547
+ const githubUserId = stringField(account.accountId);
548
+ const login = stringField(account.login) ?? stringField(account.email);
549
+ if (!githubUserId && !login)
550
+ return null;
551
+ return {
552
+ githubUserId,
553
+ login,
554
+ namespaceKey: githubUserId ? `gh:${githubUserId}` : undefined,
555
+ source: "omp-github-copilot-auth"
556
+ };
557
+ }
558
+ function envField(name) {
559
+ return stringField(process.env[name]);
560
+ }
561
+ function envIdentityCandidate() {
562
+ const selectedRepo = envField("RIG_SELECTED_REPO");
563
+ const githubUserId = envField("RIG_GITHUB_USER_ID");
564
+ const login = envField("RIG_GITHUB_LOGIN");
565
+ const namespaceKey = envField("RIG_GITHUB_NAMESPACE_KEY") ?? (githubUserId ? `gh:${githubUserId}` : undefined);
566
+ if (!selectedRepo && !githubUserId && !login && !namespaceKey)
567
+ return null;
568
+ return {
569
+ selectedRepo,
570
+ githubUserId,
571
+ login,
572
+ namespaceKey,
573
+ source: "rig-public-identity-env"
574
+ };
575
+ }
576
+ function mergeSource(sources, source) {
577
+ if (source && !sources.includes(source))
578
+ sources.push(source);
579
+ }
580
+ function completeIdentity(candidate, sources) {
581
+ const selectedRepo = stringField(candidate.selectedRepo);
582
+ const githubUserId = stringField(candidate.githubUserId);
583
+ const login = stringField(candidate.login);
584
+ const namespaceKey = stringField(candidate.namespaceKey) ?? (githubUserId ? `gh:${githubUserId}` : undefined);
585
+ if (!selectedRepo || !githubUserId || !login || !namespaceKey)
586
+ return null;
587
+ const uniqueSources = [];
588
+ for (const value of sources) {
589
+ if (!uniqueSources.includes(value))
590
+ uniqueSources.push(value);
591
+ }
592
+ return {
593
+ selectedRepo,
594
+ owner: { githubUserId, login, namespaceKey },
595
+ source: uniqueSources.join("+")
596
+ };
597
+ }
598
+ function resolveRigIdentity(ctx) {
599
+ const workflow = workflowIdentityCandidate(ctx.sessionManager.getBranch());
600
+ const omp = ompGitHubIdentityCandidate(ctx);
601
+ const env = envIdentityCandidate();
602
+ const sources = [];
603
+ const selectedRepo = workflow?.selectedRepo ?? env?.selectedRepo;
604
+ const githubUserId = workflow?.githubUserId ?? omp?.githubUserId ?? env?.githubUserId;
605
+ const login = workflow?.login ?? omp?.login ?? env?.login;
606
+ const namespaceKey = workflow?.namespaceKey ?? omp?.namespaceKey ?? env?.namespaceKey;
607
+ if (workflow && (workflow.selectedRepo || workflow.githubUserId || workflow.login || workflow.namespaceKey))
608
+ mergeSource(sources, workflow.source);
609
+ if (omp && (!workflow?.githubUserId || !workflow?.login || !workflow?.namespaceKey))
610
+ mergeSource(sources, omp.source);
611
+ if (env && (!workflow?.selectedRepo && env.selectedRepo || !workflow?.githubUserId && !omp?.githubUserId && env.githubUserId || !workflow?.login && !omp?.login && env.login || !workflow?.namespaceKey && !omp?.namespaceKey && env.namespaceKey))
612
+ mergeSource(sources, env.source);
613
+ return completeIdentity({ selectedRepo, githubUserId, login, namespaceKey, source: sources.join("+") }, sources);
614
+ }
615
+ function resolveRigIdentityFilter(ctx) {
616
+ const identity = resolveRigIdentity(ctx);
617
+ if (!identity)
618
+ return null;
619
+ return {
620
+ cwd: ctx.sessionManager.getCwd(),
621
+ selectedRepo: identity.selectedRepo,
622
+ githubUserId: identity.owner.githubUserId,
623
+ namespaceKey: identity.owner.namespaceKey
624
+ };
625
+ }
626
+ var init_identity = () => {};
627
+
628
+ // packages/github-provider-plugin/src/identity-env.ts
629
+ var exports_identity_env = {};
630
+ __export(exports_identity_env, {
631
+ stripRunChildCredentialEnv: () => stripRunChildCredentialEnv,
632
+ resolveRigIdentityFilter: () => resolveRigIdentityFilter,
633
+ resolveRigIdentity: () => resolveRigIdentity,
634
+ resolveIdentityEnv: () => resolveIdentityEnv,
635
+ identityFilterFromEnv: () => identityFilterFromEnv,
636
+ applyIdentityEnv: () => applyIdentityEnv,
637
+ RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS: () => RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS
638
+ });
639
+ import { existsSync, readFileSync } from "fs";
640
+ import { resolve } from "path";
641
+ function stringField2(value) {
642
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
643
+ }
644
+ function githubUserId(value) {
645
+ if (typeof value === "number" && Number.isInteger(value))
646
+ return String(value);
647
+ return stringField2(value);
648
+ }
649
+ function readJsonRecord(path) {
650
+ if (!existsSync(path))
651
+ return null;
652
+ try {
653
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
654
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
655
+ } catch {
656
+ return null;
657
+ }
658
+ }
659
+ function resolveIdentityEnv(workspaceRoot) {
660
+ const stateDir = resolve(workspaceRoot, ".rig", "state");
661
+ const auth = readJsonRecord(resolve(stateDir, "github-auth.json"));
662
+ const connection = readJsonRecord(resolve(stateDir, "connection.json"));
663
+ const projectLink = readJsonRecord(resolve(stateDir, "project-link.json"));
664
+ const values = {};
665
+ const selectedRepo = stringField2(auth?.selectedRepo) ?? stringField2(connection?.project) ?? stringField2(projectLink?.repoSlug);
666
+ const userId = githubUserId(auth?.userId);
667
+ const login = stringField2(auth?.login);
668
+ const namespaceKey = stringField2(auth?.userNamespaceKey);
669
+ if (selectedRepo)
670
+ values.RIG_SELECTED_REPO = selectedRepo;
671
+ if (userId)
672
+ values.RIG_GITHUB_USER_ID = userId;
673
+ if (login)
674
+ values.RIG_GITHUB_LOGIN = login;
675
+ if (namespaceKey)
676
+ values.RIG_GITHUB_NAMESPACE_KEY = namespaceKey;
677
+ return values;
678
+ }
679
+ function applyIdentityEnv(workspaceRoot) {
680
+ const previous = new Map;
681
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS])
682
+ previous.set(key, process.env[key]);
683
+ const stateDir = resolve(workspaceRoot, ".rig", "state");
684
+ const auth = readJsonRecord(resolve(stateDir, "github-auth.json"));
685
+ const connection = readJsonRecord(resolve(stateDir, "connection.json"));
686
+ const projectLink = readJsonRecord(resolve(stateDir, "project-link.json"));
687
+ const values = { ...resolveIdentityEnv(workspaceRoot) };
688
+ values.RIG_GITHUB_AUTH_STATE_FILE = resolve(stateDir, "github-auth.json");
689
+ const hasLocalIdentityState = Boolean(auth || connection || projectLink);
690
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS]) {
691
+ const value = values[key];
692
+ if (value)
693
+ process.env[key] = value;
694
+ else if (hasLocalIdentityState)
695
+ delete process.env[key];
696
+ }
697
+ return () => {
698
+ for (const key of [...PUBLIC_IDENTITY_ENV_KEYS, ...RIG_STATE_ENV_KEYS]) {
699
+ const value = previous.get(key);
700
+ if (value === undefined)
701
+ delete process.env[key];
702
+ else
703
+ process.env[key] = value;
704
+ }
705
+ };
706
+ }
707
+ function identityFilterFromEnv() {
708
+ return {
709
+ ...process.env.RIG_SELECTED_REPO ? { selectedRepo: process.env.RIG_SELECTED_REPO } : {},
710
+ ...process.env.RIG_GITHUB_USER_ID ? { githubUserId: process.env.RIG_GITHUB_USER_ID } : {},
711
+ ...process.env.RIG_GITHUB_NAMESPACE_KEY ? { namespaceKey: process.env.RIG_GITHUB_NAMESPACE_KEY } : {}
712
+ };
713
+ }
714
+ function stripRunChildCredentialEnv(env = process.env) {
715
+ const next = { ...env };
716
+ for (const key of RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS)
717
+ delete next[key];
718
+ return next;
719
+ }
720
+ var PUBLIC_IDENTITY_ENV_KEYS, RIG_STATE_ENV_KEYS, RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS;
721
+ var init_identity_env = __esm(() => {
722
+ init_identity();
723
+ PUBLIC_IDENTITY_ENV_KEYS = [
724
+ "RIG_SELECTED_REPO",
725
+ "RIG_GITHUB_USER_ID",
726
+ "RIG_GITHUB_LOGIN",
727
+ "RIG_GITHUB_NAMESPACE_KEY"
728
+ ];
729
+ RIG_STATE_ENV_KEYS = [
730
+ "RIG_GITHUB_AUTH_STATE_FILE"
731
+ ];
732
+ RUN_CHILD_STRIPPED_CREDENTIAL_ENV_KEYS = [
733
+ "RIG_GITHUB_TOKEN",
734
+ "RIG_GITHUB_SELECTED_TOKEN",
735
+ "GH_TOKEN",
736
+ "GITHUB_TOKEN"
737
+ ];
738
+ });
53
739
 
54
740
  // packages/github-provider-plugin/src/service.ts
55
741
  var exports_service = {};
56
742
  __export(exports_service, {
57
743
  githubProviderService: () => githubProviderService
58
744
  });
745
+ import { createGitHubCredentialProvider } from "@rig/github-lib";
59
746
  var githubProviderService;
60
747
  var init_service = __esm(() => {
61
- init_credentials();
62
748
  githubProviderService = {
63
749
  createCredentialProvider: (options) => createGitHubCredentialProvider(options)
64
750
  };
65
751
  });
66
752
 
67
753
  // packages/github-provider-plugin/src/plugin.ts
754
+ import { spawnSync } from "child_process";
68
755
  import { definePlugin } from "@rig/core/config";
69
- import { GITHUB_PROVIDER_CAPABILITY_ID } from "@rig/contracts";
756
+ import { defineCapability } from "@rig/core/capability";
757
+ import {
758
+ GITHUB_PROVIDER_CAPABILITY_ID,
759
+ ISSUE_TRIAGE,
760
+ RUN_IDENTITY_ENV
761
+ } from "@rig/contracts";
762
+ var IssueTriageCap = defineCapability(ISSUE_TRIAGE);
763
+ var issueTriageService = async () => {
764
+ const { runIssueAnalysisTriage: runIssueAnalysisTriage2 } = await Promise.resolve().then(() => (init_triage_run(), exports_triage_run));
765
+ return {
766
+ runTriage: (input) => runIssueAnalysisTriage2({ projectRoot: input.projectRoot, ...input.reason !== undefined ? { reason: input.reason } : {} })
767
+ };
768
+ };
769
+ var RunIdentityEnvCap = defineCapability(RUN_IDENTITY_ENV);
770
+ var runIdentityEnvService = async () => {
771
+ const {
772
+ resolveIdentityEnv: resolveIdentityEnv2,
773
+ applyIdentityEnv: applyIdentityEnv2,
774
+ identityFilterFromEnv: identityFilterFromEnv2,
775
+ stripRunChildCredentialEnv: stripRunChildCredentialEnv2,
776
+ resolveRigIdentity: resolveRigIdentity2,
777
+ resolveRigIdentityFilter: resolveRigIdentityFilter2
778
+ } = await Promise.resolve().then(() => (init_identity_env(), exports_identity_env));
779
+ return {
780
+ resolveIdentityEnv: resolveIdentityEnv2,
781
+ applyIdentityEnv: applyIdentityEnv2,
782
+ identityFilterFromEnv: identityFilterFromEnv2,
783
+ stripRunChildCredentialEnv: stripRunChildCredentialEnv2,
784
+ resolveRigIdentity: resolveRigIdentity2,
785
+ resolveRigIdentityFilter: resolveRigIdentityFilter2
786
+ };
787
+ };
70
788
  var GITHUB_PROVIDER_PLUGIN_NAME = "@rig/github-provider-plugin";
789
+ function parseGitHubSlugFromRemote(remoteUrl) {
790
+ const match = remoteUrl.trim().match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
791
+ return match ? `${match[1]}/${match[2]}` : null;
792
+ }
793
+ function detectGitHubRepoSlug(projectRoot) {
794
+ try {
795
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], {
796
+ encoding: "utf8",
797
+ timeout: 5000
798
+ });
799
+ if (result.status !== 0 || result.error)
800
+ return null;
801
+ return parseGitHubSlugFromRemote(result.stdout.trim());
802
+ } catch {
803
+ return null;
804
+ }
805
+ }
806
+ function githubConfigDefaults(context) {
807
+ const slug = context.repoSlug ?? detectGitHubRepoSlug(context.projectRoot);
808
+ if (!slug)
809
+ return null;
810
+ const [owner, repo] = slug.split("/", 2);
811
+ if (!owner || !repo)
812
+ return null;
813
+ return {
814
+ project: { name: slug, repo: slug },
815
+ taskSource: { kind: "github-issues", owner, repo, state: "open" },
816
+ github: { issueUpdates: "lifecycle", projects: { enabled: false } },
817
+ standard: {
818
+ githubWorkspaceId: slug,
819
+ githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user"
820
+ },
821
+ issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" }
822
+ };
823
+ }
71
824
  var githubProviderPlugin = definePlugin({
72
825
  name: GITHUB_PROVIDER_PLUGIN_NAME,
73
826
  version: "0.0.0-alpha.1",
@@ -78,8 +831,14 @@ var githubProviderPlugin = definePlugin({
78
831
  title: "GitHub SCM provider",
79
832
  description: "Resolve GitHub credentials for the configured SCM provider.",
80
833
  run: async () => (await Promise.resolve().then(() => (init_service(), exports_service))).githubProviderService
81
- }
82
- ]
834
+ },
835
+ IssueTriageCap.provide(issueTriageService, { title: "GitHub issue-analysis triage" }),
836
+ RunIdentityEnvCap.provide(runIdentityEnvService, {
837
+ title: "GitHub/Rig run identity env",
838
+ description: "Resolve GitHub/Rig identity, hydrate public identity env, and strip run-scoped credential tokens."
839
+ })
840
+ ],
841
+ config: { defaults: githubConfigDefaults }
83
842
  }
84
843
  });
85
844
  function createGitHubProviderPlugin() {
@@ -87,7 +846,9 @@ function createGitHubProviderPlugin() {
87
846
  }
88
847
  var plugin_default = githubProviderPlugin;
89
848
  export {
849
+ parseGitHubSlugFromRemote,
90
850
  githubProviderPlugin,
851
+ detectGitHubRepoSlug,
91
852
  plugin_default as default,
92
853
  createGitHubProviderPlugin,
93
854
  GITHUB_PROVIDER_PLUGIN_NAME