@yemi33/minions 0.1.1691 → 0.1.1692

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,8 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1691 (2026-05-04)
3
+ ## 0.1.1692 (2026-05-04)
4
4
 
5
5
  ### Features
6
+ - stop Azure auth for GitHub agents (#2005)
7
+ - recover stalled runtime resumes (#2004)
6
8
  - preserve doc-chat response fragments (#2003)
7
9
 
8
10
  ## 0.1.1689 (2026-05-03)
package/README.md CHANGED
@@ -310,6 +310,10 @@ Agents inherit MCP servers directly from `~/.claude.json` as Claude Code process
310
310
 
311
311
  Manually refresh with `minions mcp-sync`.
312
312
 
313
+ ### GitHub Users
314
+
315
+ For GitHub repos, install and authenticate the [GitHub CLI](https://cli.github.com/). Agents should use `gh` for GitHub PR creation, PR lookup, comments, reviews, issues, and workflow checks. If GitHub or Copilot auth fails, refresh GitHub credentials with `gh auth status` and `gh auth login`, or provide `GH_TOKEN`/`COPILOT_GITHUB_TOKEN` from the environment. Azure DevOps authentication and tooling paths do not apply to GitHub repo work.
316
+
313
317
  ### Azure DevOps Users
314
318
 
315
319
  For the best experience with ADO repos, install the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) with the Azure DevOps extension. Agents should use the `az` CLI first for Azure DevOps operations such as PR creation, PR lookup, comments, reviewers, work items, and pipelines. Use the Azure DevOps MCP fallback only when `az` is unavailable in the environment or insufficient for a specific action.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-04T01:58:05.343Z"
4
+ "cachedAt": "2026-05-04T01:58:58.614Z"
5
5
  }
@@ -407,16 +407,27 @@ function renderPlaybook(type, vars) {
407
407
  const dispatchProject = (vars.repo_id && projects.find(p => p.repositoryId === vars.repo_id))
408
408
  || (vars.repo_name && projects.find(p => p.repoName === vars.repo_name))
409
409
  || projects[0] || {};
410
+ const renderProject = {
411
+ ...dispatchProject,
412
+ adoOrg: vars.ado_org || vars.adoOrg || dispatchProject.adoOrg,
413
+ adoProject: vars.ado_project || vars.adoProject || dispatchProject.adoProject,
414
+ repoName: vars.repo_name || vars.repoName || dispatchProject.repoName,
415
+ repoHost: vars.repo_host || vars.repoHost || dispatchProject.repoHost,
416
+ };
417
+ const repoHost = getRepoHost(renderProject);
410
418
  const projectVars = {
411
- project_name: dispatchProject.name || 'Unknown Project',
412
- ado_org: dispatchProject.adoOrg || 'Unknown',
413
- ado_project: dispatchProject.adoProject || 'Unknown',
414
- repo_name: dispatchProject.repoName || 'Unknown',
415
- pr_create_instructions: getPrCreateInstructions(dispatchProject),
416
- pr_comment_instructions: getPrCommentInstructions(dispatchProject),
417
- pr_fetch_instructions: getPrFetchInstructions(dispatchProject),
418
- pr_vote_instructions: getPrVoteInstructions(dispatchProject),
419
- repo_host_label: getRepoHostLabel(dispatchProject),
419
+ project_name: renderProject.name || 'Unknown Project',
420
+ ado_org: renderProject.adoOrg || 'Unknown',
421
+ ado_project: renderProject.adoProject || 'Unknown',
422
+ repo_name: renderProject.repoName || 'Unknown',
423
+ repo_host: repoHost,
424
+ ado_shared_rules: repoHost === 'ado' ? '1' : '',
425
+ github_shared_rules: repoHost === 'github' ? '1' : '',
426
+ pr_create_instructions: getPrCreateInstructions(renderProject),
427
+ pr_comment_instructions: getPrCommentInstructions(renderProject),
428
+ pr_fetch_instructions: getPrFetchInstructions(renderProject),
429
+ pr_vote_instructions: getPrVoteInstructions(renderProject),
430
+ repo_host_label: getRepoHostLabel(renderProject),
420
431
  };
421
432
  const allVars = { ...projectVars, ...vars };
422
433
 
@@ -640,6 +651,7 @@ function buildBaseVars(agentId, config, project) {
640
651
  ado_org: project?.adoOrg || 'Unknown',
641
652
  ado_project: project?.adoProject || 'Unknown',
642
653
  repo_name: project?.repoName || 'Unknown',
654
+ repo_host: getRepoHost(project),
643
655
  main_branch: project?.mainBranch || 'main',
644
656
  date: dateStamp(),
645
657
  };
@@ -545,7 +545,7 @@ function parseError(rawOutput) {
545
545
  const lower = text.toLowerCase();
546
546
 
547
547
  if (/not authenticated|copilot login|please.*log.*in|401|403 forbidden|unauthorized/i.test(text)) {
548
- return { message: 'Copilot authentication failed', code: 'auth-failure', retriable: false };
548
+ return { message: 'Copilot/GitHub authentication failed. Run `gh auth login` or provide GH_TOKEN/COPILOT_GITHUB_TOKEN with Copilot access.', code: 'auth-failure', retriable: false };
549
549
  }
550
550
  if (/rate limit|too many requests|\b429\b/i.test(text)) {
551
551
  return { message: 'Copilot rate limit hit', code: 'rate-limit', retriable: true };
package/engine/shared.js CHANGED
@@ -751,6 +751,7 @@ const ENGINE_DEFAULTS = {
751
751
  inboxConsolidateThreshold: 5,
752
752
  agentTimeout: 18000000, // 5h
753
753
  heartbeatTimeout: 300000, // 5min — stale-orphan grace after process tracking is lost
754
+ resumeHeartbeatTimeout: 300000, // 5min — max wait for a resumed runtime to emit its first output
754
755
  // Per-type stale-orphan overrides (merged with config.engine.heartbeatTimeouts at runtime — see timeout.js).
755
756
  // Heavy work types (multi-file edits, builds, test suites, full verify cycles) routinely go quiet for
756
757
  // longer than the 5-min default when the engine has lost their tracked handle (e.g. across an engine
@@ -1310,10 +1311,13 @@ function projectPrPath(project) {
1310
1311
  }
1311
1312
 
1312
1313
  function resolveProjectForPrPath(filePath, config = null) {
1313
- const resolvedPath = path.resolve(filePath);
1314
+ const resolvedPaths = new Set([path.resolve(filePath)]);
1315
+ if (filePath && !path.isAbsolute(filePath)) {
1316
+ resolvedPaths.add(path.resolve(MINIONS_DIR, filePath));
1317
+ }
1314
1318
  const projects = getProjects(config);
1315
1319
  for (const project of projects) {
1316
- if (path.resolve(projectPrPath(project)) === resolvedPath) return project;
1320
+ if (resolvedPaths.has(path.resolve(projectPrPath(project)))) return project;
1317
1321
  }
1318
1322
  if (projects.length === 1) return projects[0];
1319
1323
  return null;
@@ -129,6 +129,10 @@ function normalizeRuntimeExit(code, signal) {
129
129
  return 1;
130
130
  }
131
131
 
132
+ function shouldInjectAdoTokenEnv(env = process.env) {
133
+ return String(env.MINIONS_REPO_HOST || '').trim().toLowerCase() === 'ado';
134
+ }
135
+
132
136
  function injectAdoTokenEnv(env, { execSync: _execSync, acquireToken, warn = (msg) => process.stderr.write(msg + '\n') } = {}) {
133
137
  let result;
134
138
  try {
@@ -149,6 +153,11 @@ function injectAdoTokenEnv(env, { execSync: _execSync, acquireToken, warn = (msg
149
153
  return true;
150
154
  }
151
155
 
156
+ function injectAdoTokenEnvForRepoHost(env, opts) {
157
+ if (!shouldInjectAdoTokenEnv(env)) return false;
158
+ return injectAdoTokenEnv(env, opts);
159
+ }
160
+
152
161
  const PROCESS_EXIT_SENTINEL_FLUSH_TIMEOUT_MS = 2000;
153
162
 
154
163
  function formatProcessExitSentinel(exitCode, signal) {
@@ -251,7 +260,7 @@ function main() {
251
260
  const { promptFile, sysPromptFile, runtimeName, opts, passthrough } = parsed;
252
261
 
253
262
  const env = cleanChildEnv();
254
- injectAdoTokenEnv(env);
263
+ injectAdoTokenEnvForRepoHost(env);
255
264
 
256
265
  let runtime;
257
266
  try { runtime = resolveRuntime(runtimeName); }
@@ -399,6 +408,6 @@ function main() {
399
408
  });
400
409
  }
401
410
 
402
- module.exports = { parseSpawnArgs, buildSpawnInvocation, normalizeRuntimeExit, injectAdoTokenEnv, writeProcessExitSentinel, computeAddDirs };
411
+ module.exports = { parseSpawnArgs, buildSpawnInvocation, normalizeRuntimeExit, shouldInjectAdoTokenEnv, injectAdoTokenEnv, injectAdoTokenEnvForRepoHost, writeProcessExitSentinel, computeAddDirs };
403
412
 
404
413
  if (require.main === module) main();
package/engine/timeout.js CHANGED
@@ -9,7 +9,7 @@ const queries = require('./queries');
9
9
  const steering = require('./steering');
10
10
 
11
11
  const { safeRead, safeWrite, safeJson, mutateJsonFileLocked, getProjects, projectWorkItemsPath, log, ts,
12
- ENGINE_DEFAULTS, ENGINE_DIR, WI_STATUS, WORK_TYPE, DISPATCH_RESULT, AGENT_STATUS } = shared;
12
+ ENGINE_DEFAULTS, ENGINE_DIR, WI_STATUS, WORK_TYPE, DISPATCH_RESULT, AGENT_STATUS, FAILURE_CLASS } = shared;
13
13
  const { getDispatch, getAgentStatus } = queries;
14
14
  const AGENTS_DIR = queries.AGENTS_DIR;
15
15
  const MINIONS_DIR = shared.MINIONS_DIR;
@@ -258,6 +258,7 @@ function checkTimeouts(config) {
258
258
 
259
259
  const timeout = config.engine?.agentTimeout || ENGINE_DEFAULTS.agentTimeout;
260
260
  const defaultStaleOrphanTimeout = config.engine?.heartbeatTimeout || ENGINE_DEFAULTS.heartbeatTimeout;
261
+ const runtimeResumeHeartbeatTimeout = config.engine?.resumeHeartbeatTimeout || ENGINE_DEFAULTS.resumeHeartbeatTimeout || defaultStaleOrphanTimeout;
261
262
 
262
263
  // Optional per-type stale-orphan timeouts: merge ENGINE_DEFAULTS ← config overrides.
263
264
  const perTypeStaleOrphanTimeouts = { ...ENGINE_DEFAULTS.heartbeatTimeouts, ...(config.engine?.heartbeatTimeouts || {}) };
@@ -274,8 +275,10 @@ function checkTimeouts(config) {
274
275
  }
275
276
 
276
277
  // 2. Stale-orphan check — for ALL active dispatch items (catches lost process handles after restart).
277
- // Silence is not a failure for tracked live processes: long CLI commands can legitimately
278
- // produce no stdout/stderr for extended periods.
278
+ // Silence is not a failure for tracked live processes once a runtime has emitted output:
279
+ // long CLI commands can legitimately produce no stdout/stderr for extended periods.
280
+ // The exception is a resumed runtime that has not produced its first stdout/stderr
281
+ // heartbeat after spawn; that is the "alive but stuck in --resume" failure mode.
279
282
  const dispatchData = getDispatch();
280
283
  const deadItems = [];
281
284
  const legacyAnnotationClears = new Set();
@@ -332,8 +335,9 @@ function checkTimeouts(config) {
332
335
  const liveLogPath = path.join(AGENTS_DIR, item.agent, 'live-output.log');
333
336
  let lastActivity = item.started_at ? new Date(item.started_at).getTime() : 0;
334
337
 
335
- // live-output.log mtime is only used for stale-orphan cleanup and completion recovery.
336
- // It is not used as an output-silence timeout for live tracked processes.
338
+ // live-output.log mtime is used for stale-orphan cleanup, completion recovery,
339
+ // and the resume first-output watchdog. It is not a general output-silence
340
+ // timeout for live tracked processes.
337
341
  try {
338
342
  const stat = fs.statSync(liveLogPath);
339
343
  lastActivity = Math.max(lastActivity, stat.mtimeMs);
@@ -394,6 +398,23 @@ function checkTimeouts(config) {
394
398
  if (procInfo?._steeringAt && Date.now() - procInfo._steeringAt < 60000) continue;
395
399
 
396
400
  if (processAlive) {
401
+ if (procInfo?._runtimeResumeAwaitingFirstOutput) {
402
+ const resumeStartedAt = Number(procInfo._runtimeResumeAt || 0);
403
+ const resumeHeartbeatAt = Math.max(lastActivity, resumeStartedAt);
404
+ const resumeSilentMs = Date.now() - resumeHeartbeatAt;
405
+ if (resumeSilentMs > runtimeResumeHeartbeatTimeout) {
406
+ const resumeSilentSec = Math.round(resumeSilentMs / 1000);
407
+ const reason = `Runtime resume stalled — no output heartbeat for ${resumeSilentSec}s`;
408
+ log('warn', `Runtime resume stalled: ${item.agent} (${item.id}) — no output heartbeat for ${resumeSilentSec}s; killing and retrying fresh`);
409
+ dispatch().updateAgentStatus(item.id, AGENT_STATUS.TIMED_OUT, reason);
410
+ try { fs.appendFileSync(liveLogPath, `\n[runtime-resume-timeout] ${reason}. Killing this resume attempt and retrying with a fresh session.\n`); } catch { /* optional */ }
411
+ // Clear the cached session so retry does not re-enter the same stuck --resume path.
412
+ try { shared.safeUnlink(path.join(AGENTS_DIR, item.agent, 'session.json')); } catch {}
413
+ activeProcesses.delete(item.id);
414
+ shared.killGracefully(procInfo.proc, 5000);
415
+ deadItems.push({ item, reason, failureClass: FAILURE_CLASS.TIMEOUT });
416
+ }
417
+ }
397
418
  continue;
398
419
  }
399
420
 
@@ -462,8 +483,8 @@ function checkTimeouts(config) {
462
483
  }
463
484
 
464
485
  // Clean up dead items
465
- for (const { item, reason } of deadItems) {
466
- completeDispatch(item.id, DISPATCH_RESULT.ERROR, reason);
486
+ for (const { item, reason, failureClass } of deadItems) {
487
+ completeDispatch(item.id, DISPATCH_RESULT.ERROR, reason, '', failureClass ? { failureClass } : {});
467
488
  }
468
489
 
469
490
  // Clear legacy blocking-tool annotations; process liveness no longer depends on tool parsing.
package/engine.js CHANGED
@@ -135,7 +135,7 @@ const { getRouting, parseRoutingTable, getRoutingTableCached, getMonthlySpend,
135
135
  const { renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS,
136
136
  buildSystemPrompt, buildAgentContext, selectPlaybook,
137
137
  buildBaseVars, buildPrDispatch, resolveTaskContext,
138
- getRepoHostLabel, getRepoHostToolRule } = require('./engine/playbook');
138
+ getRepoHost, getRepoHostLabel, getRepoHostToolRule } = require('./engine/playbook');
139
139
 
140
140
  // sanitizeBranch imported from shared.js
141
141
 
@@ -975,13 +975,16 @@ async function spawnAgent(dispatchItem, config) {
975
975
  // Spawn the claude process
976
976
  const childEnv = shared.cleanChildEnv();
977
977
  if (completionReportPath) childEnv.MINIONS_COMPLETION_REPORT = completionReportPath;
978
+ childEnv.MINIONS_REPO_HOST = getRepoHost(project);
978
979
 
979
- // Inject cached ADO token so agents skip re-authentication (#998)
980
- // getAdoToken() returns cached token (30-min TTL) or null never blocks on browser auth
981
- try {
982
- const adoToken = await getAdoToken();
983
- if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
984
- } catch { /* non-fatal — agent can still authenticate on its own */ }
980
+ if (getRepoHost(project) === 'ado') {
981
+ // Inject cached ADO token so ADO agents skip re-authentication (#998).
982
+ // getAdoToken() returns cached token (30-min TTL) or null — never blocks on browser auth.
983
+ try {
984
+ const adoToken = await getAdoToken();
985
+ if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
986
+ } catch { /* non-fatal — agent can still authenticate on its own */ }
987
+ }
985
988
 
986
989
  // Spawn via wrapper script — node directly (no bash intermediary)
987
990
  // spawn-agent.js handles CLAUDECODE env cleanup and claude binary resolution
@@ -1052,6 +1055,10 @@ async function spawnAgent(dispatchItem, config) {
1052
1055
  startedAt,
1053
1056
  runtimeName,
1054
1057
  sessionId: cachedSessionId,
1058
+ ...(cachedSessionId ? {
1059
+ _runtimeResumeAt: Date.now(),
1060
+ _runtimeResumeAwaitingFirstOutput: true,
1061
+ } : {}),
1055
1062
  _pendingSteeringFiles: pendingSteering.entries,
1056
1063
  };
1057
1064
  activeProcesses.set(id, initialProcInfo);
@@ -1063,6 +1070,12 @@ async function spawnAgent(dispatchItem, config) {
1063
1070
  let _trustCheckDone = false;
1064
1071
  const _spawnTime = Date.now();
1065
1072
 
1073
+ function markRuntimeResumeOutputSeen(procInfo) {
1074
+ if (!procInfo?._runtimeResumeAwaitingFirstOutput) return;
1075
+ procInfo._runtimeResumeAwaitingFirstOutput = false;
1076
+ procInfo.lastRealOutputAt = Date.now();
1077
+ }
1078
+
1066
1079
  proc.stdout.on('data', (data) => {
1067
1080
  const chunk = data.toString();
1068
1081
  realActivityMap.set(id, Date.now());
@@ -1086,6 +1099,7 @@ async function spawnAgent(dispatchItem, config) {
1086
1099
  // Capture sessionId early for mid-session steering. Claude emits session_id;
1087
1100
  // Copilot emits sessionId, so use the runtime-neutral steering helper.
1088
1101
  const procInfo = activeProcesses.get(id);
1102
+ markRuntimeResumeOutputSeen(procInfo);
1089
1103
  captureSessionIdFromStdoutChunk(agentId, id, branchName, runtime, procInfo, chunk, sessionCaptureState);
1090
1104
 
1091
1105
  ackPendingSteeringFiles(agentId, procInfo, chunk);
@@ -1096,6 +1110,7 @@ async function spawnAgent(dispatchItem, config) {
1096
1110
  realActivityMap.set(id, Date.now());
1097
1111
  if (stderr.length < MAX_OUTPUT) stderr += chunk.slice(0, MAX_OUTPUT - stderr.length);
1098
1112
  try { fs.appendFileSync(liveOutputPath, '[stderr] ' + chunk); } catch { /* optional */ }
1113
+ markRuntimeResumeOutputSeen(activeProcesses.get(id));
1099
1114
  });
1100
1115
 
1101
1116
  async function onAgentClose(code) {
@@ -1188,11 +1203,14 @@ async function spawnAgent(dispatchItem, config) {
1188
1203
  const childEnv = shared.cleanChildEnv();
1189
1204
  if (completionReportPath) childEnv.MINIONS_COMPLETION_REPORT = completionReportPath;
1190
1205
  childEnv.MINIONS_LIVE_OUTPUT_PATH = liveOutputPath;
1191
- // Inject cached ADO token for steering session too (#998)
1192
- try {
1193
- const adoToken = await getAdoToken();
1194
- if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
1195
- } catch { /* non-fatal */ }
1206
+ childEnv.MINIONS_REPO_HOST = getRepoHost(project);
1207
+ if (getRepoHost(project) === 'ado') {
1208
+ // Inject cached ADO token for steering session too (#998)
1209
+ try {
1210
+ const adoToken = await getAdoToken();
1211
+ if (adoToken) childEnv.MINIONS_ADO_TOKEN = adoToken;
1212
+ } catch { /* non-fatal */ }
1213
+ }
1196
1214
  let resumeProc;
1197
1215
  try {
1198
1216
  resumeProc = runFile(process.execPath, [spawnScript, steerPromptPath, sysPromptPath, ...resumeArgs], {
@@ -1220,7 +1238,8 @@ async function spawnAgent(dispatchItem, config) {
1220
1238
  startedAt: procInfo.startedAt,
1221
1239
  runtimeName,
1222
1240
  sessionId: steerSessionId,
1223
- lastRealOutputAt: Date.now(),
1241
+ _runtimeResumeAt: Date.now(),
1242
+ _runtimeResumeAwaitingFirstOutput: true,
1224
1243
  _pendingSteeringFiles: mergePendingSteeringEntries(
1225
1244
  procInfo._pendingSteeringFiles,
1226
1245
  pendingForResume.entries,
@@ -1239,6 +1258,7 @@ async function spawnAgent(dispatchItem, config) {
1239
1258
  if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
1240
1259
  try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
1241
1260
  const resumeInfo = activeProcesses.get(id);
1261
+ markRuntimeResumeOutputSeen(resumeInfo);
1242
1262
  captureSessionIdFromStdoutChunk(agentId, id, branchName, runtime, resumeInfo, chunk, sessionCaptureState);
1243
1263
  ackPendingSteeringFiles(agentId, resumeInfo, chunk);
1244
1264
  });
@@ -1247,6 +1267,7 @@ async function spawnAgent(dispatchItem, config) {
1247
1267
  realActivityMap.set(id, Date.now());
1248
1268
  if (stderr.length < MAX_OUTPUT) stderr += chunk.slice(0, MAX_OUTPUT - stderr.length);
1249
1269
  try { fs.appendFileSync(liveOutputPath, '[stderr] ' + chunk); } catch { /* optional */ }
1270
+ markRuntimeResumeOutputSeen(activeProcesses.get(id));
1250
1271
  });
1251
1272
 
1252
1273
  // Re-wire close handler for the resumed process
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1691",
3
+ "version": "0.1.1692",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"
@@ -78,6 +78,38 @@ Concretely:
78
78
  - If you skipped local validation, say so in the completion JSON (e.g. `tests: skipped — relying on PR pipeline`) and still exit.
79
79
  - Holding a slot to watch a pipeline is wasted capacity; the engine has its own pipeline-monitoring path.
80
80
 
81
+ {{#github_shared_rules}}
82
+ ## Checking PR and Build Status
83
+
84
+ When asked to check build status, CI results, or review state for a PR:
85
+
86
+ **Preferred — read cached state (refreshed every `prPollStatusEvery` ticks, default ~12 min when engine is running):**
87
+ Find the PR in `projects/<project-name>/pull-requests.json` by `prNumber`. Key fields:
88
+ - `buildStatus` — `passing` | `failing` | `running` | `none`
89
+ - `buildFailReason` — failing check/pipeline name when `buildStatus` is `failing`; inspect live CI logs yourself for details
90
+ - `reviewStatus` — `approved` | `changes-requested` | `waiting` | `pending`
91
+ - `status` — `active` | `merged` | `abandoned`
92
+ - `url` — link to the PR on GitHub
93
+
94
+ **Live status (when engine isn't running or you need up-to-the-moment results):**
95
+ ```bash
96
+ gh pr view <prNumber> --json number,title,state,mergeable,reviewDecision,headRefName,baseRefName,statusCheckRollup --repo OWNER/REPO
97
+ ```
98
+
99
+ ## GitHub Tooling and Auth
100
+
101
+ For GitHub repo operations, use GitHub MCP tools or the `gh` CLI. Prefer commands such as `gh pr create`, `gh pr view`, `gh pr comment`, `gh pr review --comment`, `gh issue view`, and `gh run view`.
102
+
103
+ If GitHub or Copilot auth fails, check GitHub/Copilot credentials only:
104
+ - `gh auth status`
105
+ - `gh auth login`
106
+ - `gh auth token` (for token visibility checks only; do not paste tokens into logs)
107
+ - Set `GH_TOKEN` or `COPILOT_GITHUB_TOKEN` only when the environment already provides an appropriate GitHub token.
108
+
109
+ Only GitHub/Copilot authentication guidance applies to GitHub repository work.
110
+ {{/github_shared_rules}}
111
+
112
+ {{#ado_shared_rules}}
81
113
  ## Checking PR and Build Status
82
114
 
83
115
  When asked to check build status, CI results, or review state for a PR:
@@ -107,3 +139,4 @@ Output is JSON with the same fields. Exit 0 on success, 1 if not found.
107
139
  For Azure DevOps repo operations, use the `az` CLI first. Prefer commands such as `az repos pr create`, `az repos pr show`, `az repos pr list`, `az repos pr comment`, `az repos pr reviewer`, `az boards work-item`, and `az pipelines` after setting defaults with `az devops configure`.
108
140
 
109
141
  Use ADO MCP fallback tools (`mcp__azure-ado__*`) only when `az` is unavailable in the environment or insufficient for a specific operation. Do not choose MCP first just because it exists, and do not use `gh` for Azure DevOps repositories.
142
+ {{/ado_shared_rules}}