@doist/doistbot-cli 1.0.1 → 1.0.3

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 (54) hide show
  1. package/dist/actions/review-usage.js +20 -0
  2. package/dist/actions/review.js +126 -10
  3. package/dist/actions/setup.js +3 -2
  4. package/dist/index.js +18 -8
  5. package/dist/runtime.js +3 -3
  6. package/dist/update-check.js +1 -1
  7. package/package.json +6 -5
  8. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +1 -0
  9. package/sandbox/dist/core/bot-logins.js +13 -0
  10. package/sandbox/dist/core/retry.js +29 -0
  11. package/sandbox/dist/core/shared.js +2 -2
  12. package/sandbox/dist/main.js +3 -0
  13. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +1 -7
  14. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +157 -28
  15. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +190 -0
  16. package/sandbox/dist/tasks/issue-triage/fix-failure-reasons.js +2 -0
  17. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.js +464 -0
  18. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.types.js +1 -0
  19. package/sandbox/dist/tasks/issue-triage/fix-loop/fake-effects.js +65 -0
  20. package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +110 -0
  21. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +227 -0
  22. package/sandbox/dist/tasks/issue-triage/fix-loop/state-machine.js +165 -0
  23. package/sandbox/dist/tasks/issue-triage/hero-group-map.js +32 -0
  24. package/sandbox/dist/tasks/issue-triage/markers.js +2 -0
  25. package/sandbox/dist/tasks/issue-triage/output.js +24 -3
  26. package/sandbox/dist/tasks/issue-triage/pr-creator.js +72 -30
  27. package/sandbox/dist/tasks/issue-triage/repo-config.js +13 -0
  28. package/sandbox/dist/tasks/issue-triage/triage.js +31 -10
  29. package/sandbox/dist/tasks/issue-triage/types.js +1 -0
  30. package/sandbox/dist/tasks/review/author-format.js +14 -0
  31. package/sandbox/dist/tasks/review/author-profile.js +67 -0
  32. package/sandbox/dist/tasks/review/check-run.js +8 -1
  33. package/sandbox/dist/tasks/review/comment-mapper.js +17 -12
  34. package/sandbox/dist/tasks/review/conversation-context.js +174 -20
  35. package/sandbox/dist/tasks/review/council/summary.js +141 -12
  36. package/sandbox/dist/tasks/review/engines/council.js +19 -3
  37. package/sandbox/dist/tasks/review/engines/dedupe.js +37 -28
  38. package/sandbox/dist/tasks/review/engines/multi-focus.js +123 -6
  39. package/sandbox/dist/tasks/review/engines/shared.js +28 -5
  40. package/sandbox/dist/tasks/review/finding-output.js +18 -0
  41. package/sandbox/dist/tasks/review/github-comment-format.js +32 -0
  42. package/sandbox/dist/tasks/review/local.js +1 -0
  43. package/sandbox/dist/tasks/review/multi-focus-prompt.js +0 -1
  44. package/sandbox/dist/tasks/review/review.js +12 -2
  45. package/sandbox/dist/tasks/review/runner.js +36 -2
  46. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +1 -0
  47. package/sandbox/review-prompt.md +18 -1
  48. package/sandbox/src/tasks/review/prompts/review-focus-prompts/efficiency.md +10 -6
  49. package/sandbox/src/tasks/review/prompts/review-focus-prompts/general.md +2 -0
  50. package/sandbox/src/tasks/review/prompts/review-focus-prompts/quality.md +15 -1
  51. package/sandbox/src/tasks/review/prompts/review-focus-prompts/standards.md +2 -0
  52. package/sandbox/src/tasks/review/prompts/review-focus-prompts/tests.md +14 -1
  53. package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +31 -4
  54. package/sandbox/src/tasks/review/prompts/review-focus-prompts/reuse.md +0 -13
@@ -0,0 +1,20 @@
1
+ const REVIEW_USAGE_COMMANDS = ['doistbot review [staged]', 'db review [staged]'];
2
+ const REVIEW_SETUP_COMMANDS = [
3
+ 'doistbot review',
4
+ 'doistbot review staged',
5
+ 'db review',
6
+ 'db review staged',
7
+ ];
8
+ export function formatReviewUsage() {
9
+ return `Usage: ${formatCommandList(REVIEW_USAGE_COMMANDS)}`;
10
+ }
11
+ export function formatSetupCompleteMessage() {
12
+ return `Setup complete. Run ${formatCommandList(REVIEW_SETUP_COMMANDS)} from a git repository.`;
13
+ }
14
+ function formatCommandList(commands) {
15
+ const quotedCommands = commands.map((command) => `\`${command}\``);
16
+ if (quotedCommands.length <= 2) {
17
+ return quotedCommands.join(' or ');
18
+ }
19
+ return `${quotedCommands.slice(0, -1).join(', ')}, or ${quotedCommands.at(-1)}`;
20
+ }
@@ -1,7 +1,9 @@
1
1
  import { getApiKeys } from '../config.js';
2
2
  import { getRepositoryRoot, loadSandboxFormatModule, loadSandboxReviewModule } from '../runtime.js';
3
3
  import { formatReviewProgress, renderAlert, renderMarkdown, withSpinner, writeLine, } from '../terminal.js';
4
+ import { formatReviewUsage } from './review-usage.js';
4
5
  const LOCAL_REVIEW_SOURCE_STAGED = 'staged';
6
+ const JSON_SCHEMA_VERSION = 1;
5
7
  const PROVIDER_DISPLAY_NAMES = {
6
8
  gemini: 'Gemini',
7
9
  openai: 'OpenAI Codex',
@@ -41,14 +43,17 @@ function getReadableProviderFailure(failure) {
41
43
  ?.trim();
42
44
  return `${PROVIDER_DISPLAY_NAMES[failure.model]} failed: ${firstLine || 'unknown error'}`;
43
45
  }
44
- function getReadableProviderFailures(failures) {
46
+ function getReadableProviderFailureEntries(failures) {
45
47
  const messagesByModel = new Map();
46
48
  for (const failure of failures) {
47
49
  if (!messagesByModel.has(failure.model)) {
48
50
  messagesByModel.set(failure.model, getReadableProviderFailure(failure));
49
51
  }
50
52
  }
51
- return [...messagesByModel.values()];
53
+ return Array.from(messagesByModel, ([model, message]) => ({ model, message }));
54
+ }
55
+ function getReadableProviderFailures(failures) {
56
+ return getReadableProviderFailureEntries(failures).map(({ message }) => message);
52
57
  }
53
58
  function formatProviderFailureMessage(failures) {
54
59
  const messages = getReadableProviderFailures(failures);
@@ -86,19 +91,130 @@ function handleSkippedReview(result) {
86
91
  function formatReviewOutput({ formatReviewForTerminal, review, }) {
87
92
  return formatReviewForTerminal({ review });
88
93
  }
89
- export async function runReview(sourceArg) {
90
- if (sourceArg !== undefined && sourceArg !== LOCAL_REVIEW_SOURCE_STAGED) {
91
- throw new Error('Usage: doistbot review [staged]');
94
+ function getJsonStatus(result) {
95
+ function mapping(statusMapping, canTrustCleanReview = statusMapping.status === 'reviewed') {
96
+ return { mapping: statusMapping, canTrustCleanReview };
92
97
  }
93
- const source = sourceArg === LOCAL_REVIEW_SOURCE_STAGED ? LOCAL_REVIEW_SOURCE_STAGED : 'base';
98
+ if (result.reviewResult.status === 'skipped') {
99
+ switch (result.reviewResult.reason) {
100
+ case 'empty_review':
101
+ case 'no_models':
102
+ case 'no_review':
103
+ return mapping({
104
+ status: 'unverified',
105
+ verified: false,
106
+ unverifiedReason: result.reviewResult.reason,
107
+ }, false);
108
+ default:
109
+ return mapping({
110
+ status: 'skipped',
111
+ verified: false,
112
+ skipReason: result.reviewResult.reason,
113
+ }, false);
114
+ }
115
+ }
116
+ return mapping({ status: 'reviewed', verified: true });
117
+ }
118
+ function finalizeJsonStatus({ initialStatus, findingCount, providerFailureCount, }) {
119
+ if (initialStatus.canTrustCleanReview && providerFailureCount > 0 && findingCount === 0) {
120
+ return { status: 'unverified', verified: false, unverifiedReason: 'provider_failures' };
121
+ }
122
+ return initialStatus.mapping;
123
+ }
124
+ function getReviewForJson(result) {
125
+ if (result.reviewResult.status === 'reviewed') {
126
+ return result.reviewResult.review;
127
+ }
128
+ return { summary: '', comments: [] };
129
+ }
130
+ function hasLocation(finding) {
131
+ return typeof finding.path === 'string' && typeof finding.line === 'number';
132
+ }
133
+ function formatReviewJsonFinding(finding) {
134
+ return {
135
+ body: finding.body,
136
+ severity: finding.severity,
137
+ ...(hasLocation(finding)
138
+ ? {
139
+ location: {
140
+ path: finding.path,
141
+ line: finding.line,
142
+ ...(finding.side ? { side: finding.side } : {}),
143
+ },
144
+ }
145
+ : {}),
146
+ };
147
+ }
148
+ function getReviewFindingsForJson(review) {
149
+ if (review.findings) {
150
+ return review.findings;
151
+ }
152
+ return review.comments.map((comment) => ({
153
+ body: comment.body,
154
+ severity: null,
155
+ placement: 'inline',
156
+ path: comment.path,
157
+ line: comment.line,
158
+ side: comment.side,
159
+ }));
160
+ }
161
+ function getReviewFindingCount(review) {
162
+ return getReviewFindingsForJson(review).length;
163
+ }
164
+ function formatReviewForJson(result) {
165
+ const review = getReviewForJson(result);
166
+ const reviewFindings = getReviewFindingsForJson(review);
167
+ const findings = reviewFindings.map(formatReviewJsonFinding);
168
+ const statusMapping = finalizeJsonStatus({
169
+ initialStatus: getJsonStatus(result),
170
+ findingCount: findings.length,
171
+ providerFailureCount: result.reviewResult.providerFailures.length,
172
+ });
173
+ return {
174
+ schemaVersion: JSON_SCHEMA_VERSION,
175
+ ...statusMapping,
176
+ reviewSource: result.source,
177
+ repository: {
178
+ owner: result.owner,
179
+ repo: result.repo,
180
+ branch: result.branch,
181
+ baseBranch: result.baseBranch,
182
+ },
183
+ review: {
184
+ count: findings.length,
185
+ findings,
186
+ },
187
+ providerFailures: getReadableProviderFailureEntries(result.reviewResult.providerFailures),
188
+ };
189
+ }
190
+ async function runLocalReviewForCli({ source, onProgress, }) {
94
191
  const reviewModule = await loadSandboxReviewModule();
95
- const result = await withSpinner(source === LOCAL_REVIEW_SOURCE_STAGED
96
- ? 'Reviewing staged changes...'
97
- : 'Reviewing local changes...', async (status) => await reviewModule.runLocalReview({
192
+ return await reviewModule.runLocalReview({
98
193
  workspaceDir: getRepositoryRoot(),
99
194
  source,
100
195
  apiKeys: await getApiKeys(),
101
196
  suppressLogs: true,
197
+ onProgress,
198
+ });
199
+ }
200
+ export async function runReview(sourceArg, options = {}) {
201
+ if (sourceArg !== undefined && sourceArg !== LOCAL_REVIEW_SOURCE_STAGED) {
202
+ throw new Error(formatReviewUsage());
203
+ }
204
+ const source = sourceArg === LOCAL_REVIEW_SOURCE_STAGED ? LOCAL_REVIEW_SOURCE_STAGED : 'base';
205
+ if (options.json === true) {
206
+ const result = await runLocalReviewForCli({ source });
207
+ const output = formatReviewForJson(result);
208
+ writeLine(JSON.stringify(output, null, 2));
209
+ if (output.status === 'unverified') {
210
+ process.exitCode = 1;
211
+ }
212
+ return;
213
+ }
214
+ const result = await withSpinner(source === LOCAL_REVIEW_SOURCE_STAGED
215
+ ? 'Reviewing staged changes...'
216
+ : 'Reviewing local changes...', async (status) => await runLocalReviewForCli({
217
+ source,
102
218
  onProgress: status.enabled
103
219
  ? (event) => status.update(formatReviewProgress(event))
104
220
  : undefined,
@@ -109,7 +225,7 @@ export async function runReview(sourceArg) {
109
225
  }
110
226
  const formatModule = await loadSandboxFormatModule();
111
227
  if (result.reviewResult.providerFailures.length > 0 &&
112
- result.reviewResult.review.comments.length === 0) {
228
+ getReviewFindingCount(result.reviewResult.review) === 0) {
113
229
  throw new Error(formatProviderFailureMessage(result.reviewResult.providerFailures));
114
230
  }
115
231
  const output = formatReviewOutput({
@@ -1,13 +1,14 @@
1
1
  import { checkGit, checkPiDependency, checkSandboxBuild } from '../runtime.js';
2
2
  import { renderDiagnosticCheck, writeLine } from '../terminal.js';
3
3
  import { runAuthSetup } from './auth.js';
4
+ import { formatSetupCompleteMessage } from './review-usage.js';
4
5
  export async function runSetup() {
5
- writeLine('Checking DoistBot CLI prerequisites...');
6
+ writeLine('Checking Doistbot CLI prerequisites...');
6
7
  await renderDiagnosticCheck('git', checkGit);
7
8
  await renderDiagnosticCheck('pi', checkPiDependency);
8
9
  await renderDiagnosticCheck('sandbox build', checkSandboxBuild);
9
10
  writeLine();
10
11
  await runAuthSetup();
11
12
  writeLine();
12
- writeLine('Setup complete. Run `doistbot review` or `doistbot review staged` from a git repository.');
13
+ writeLine(formatSetupCompleteMessage());
13
14
  }
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { Command } from 'commander';
5
+ import { Command, Option } from 'commander';
6
6
  import { AUTH_LOGOUT_PROVIDER_USAGE, runAuthLogout, runAuthSetup, runAuthStatus, } from './actions/auth.js';
7
7
  import { runDoctor } from './actions/doctor.js';
8
8
  import { runReview } from './actions/review.js';
@@ -34,18 +34,27 @@ export function createProgram({ runAuthLogout: authLogout = runAuthLogout, runAu
34
34
  automaticUpdateCommands.add(command);
35
35
  return command;
36
36
  }
37
+ function shouldRunAutomaticUpdateCheck(actionCommand) {
38
+ if (!automaticUpdateCommands.has(actionCommand)) {
39
+ return false;
40
+ }
41
+ if (actionCommand.name() === 'review' && actionCommand.opts().json) {
42
+ return false;
43
+ }
44
+ return true;
45
+ }
37
46
  program
38
47
  .name('doistbot')
39
- .description('DoistBot local self-review CLI')
48
+ .description('Doistbot local self-review CLI')
40
49
  .version(PACKAGE_VERSION)
41
50
  .hook('postAction', async (_thisCommand, actionCommand) => {
42
- if (automaticUpdateCommands.has(actionCommand)) {
51
+ if (shouldRunAutomaticUpdateCheck(actionCommand)) {
43
52
  await automaticUpdateCheck();
44
53
  }
45
54
  });
46
55
  withAutomaticUpdateCheck(program
47
56
  .command('doctor')
48
- .description('Diagnose local DoistBot CLI setup')
57
+ .description('Diagnose local Doistbot CLI setup')
49
58
  .action(async () => await doctor()));
50
59
  withAutomaticUpdateCheck(program
51
60
  .command('setup')
@@ -53,7 +62,7 @@ export function createProgram({ runAuthLogout: authLogout = runAuthLogout, runAu
53
62
  .action(async () => await setup()));
54
63
  const authCommand = withAutomaticUpdateCheck(program
55
64
  .command('auth')
56
- .description('Authenticate DoistBot CLI with local review providers')
65
+ .description('Authenticate Doistbot CLI with local review providers')
57
66
  .action(async () => await authSetup()));
58
67
  withAutomaticUpdateCheck(authCommand
59
68
  .command('status')
@@ -65,15 +74,16 @@ export function createProgram({ runAuthLogout: authLogout = runAuthLogout, runAu
65
74
  .action(async () => await authSetup()));
66
75
  withAutomaticUpdateCheck(authCommand
67
76
  .command('logout [provider]')
68
- .description(`Clear stored DoistBot CLI auth for ${AUTH_LOGOUT_PROVIDER_USAGE}`)
77
+ .description(`Clear stored Doistbot CLI auth for ${AUTH_LOGOUT_PROVIDER_USAGE}`)
69
78
  .action(async (provider) => await authLogout(provider)));
70
79
  withAutomaticUpdateCheck(program
71
80
  .command('review [source]')
72
81
  .description('Run a local self-review. Use `staged` to review only staged changes.')
73
- .action(async (source) => await review(source)));
82
+ .addOption(new Option('--json', 'Print machine-readable JSON instead of terminal-formatted markdown').hideHelp())
83
+ .action(async (source, options) => await review(source, { json: options?.json === true })));
74
84
  program
75
85
  .command('update')
76
- .description('Check whether a newer DoistBot CLI version is available')
86
+ .description('Check whether a newer Doistbot CLI version is available')
77
87
  .action(async () => await updateCheck());
78
88
  return program;
79
89
  }
package/dist/runtime.js CHANGED
@@ -53,10 +53,10 @@ async function loadBundledModule(modulePath, missingMessage) {
53
53
  return (await import(pathToFileURL(modulePath).href));
54
54
  }
55
55
  export async function loadSandboxReviewModule() {
56
- return loadBundledModule(getSandboxReviewModulePath(), 'Sandbox review runner is missing. Rebuild or reinstall the DoistBot CLI package.');
56
+ return loadBundledModule(getSandboxReviewModulePath(), 'Sandbox review runner is missing. Rebuild or reinstall the Doistbot CLI package.');
57
57
  }
58
58
  export async function loadSandboxFormatModule() {
59
- return loadBundledModule(getSandboxFormatModulePath(), 'Sandbox review formatter is missing. Rebuild or reinstall the DoistBot CLI package.');
59
+ return loadBundledModule(getSandboxFormatModulePath(), 'Sandbox review formatter is missing. Rebuild or reinstall the Doistbot CLI package.');
60
60
  }
61
61
  export function runGit(args, cwd = process.cwd()) {
62
62
  return execFileSync('git', args, {
@@ -89,7 +89,7 @@ export function checkSandboxBuild() {
89
89
  const modulePath = getSandboxReviewModulePath();
90
90
  return fs.existsSync(modulePath)
91
91
  ? { ok: true, message: 'sandbox review runner is built' }
92
- : { ok: false, message: 'rebuild or reinstall the DoistBot CLI package' };
92
+ : { ok: false, message: 'rebuild or reinstall the Doistbot CLI package' };
93
93
  }
94
94
  export function checkPiDependency() {
95
95
  try {
@@ -38,7 +38,7 @@ export async function notifyIfCliUpdateAvailable({ cachePath = getDefaultUpdateC
38
38
  timeoutMs,
39
39
  });
40
40
  if (!latestVersion) {
41
- write('Unable to check for DoistBot CLI updates.');
41
+ write('Unable to check for Doistbot CLI updates.');
42
42
  return;
43
43
  }
44
44
  if (!isNewerVersion(latestVersion, currentVersion)) {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@doist/doistbot-cli",
3
- "version": "1.0.1",
4
- "description": "Local self-review CLI for DoistBot",
3
+ "version": "1.0.3",
4
+ "description": "Local self-review CLI for Doistbot",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
+ "db": "dist/index.js",
8
9
  "doistbot": "dist/index.js"
9
10
  },
10
11
  "keywords": [
@@ -41,8 +42,8 @@
41
42
  "dependencies": {
42
43
  "@inquirer/checkbox": "5.1.5",
43
44
  "@inquirer/password": "5.0.13",
44
- "@mariozechner/pi-ai": "0.73.0",
45
- "@mariozechner/pi-coding-agent": "0.73.0",
45
+ "@mariozechner/pi-ai": "0.73.1",
46
+ "@mariozechner/pi-coding-agent": "0.73.1",
46
47
  "@napi-rs/keyring": "1.3.0",
47
48
  "ajv": "8.20.0",
48
49
  "chalk": "4.1.2",
@@ -52,6 +53,6 @@
52
53
  "yocto-spinner": "1.2.0"
53
54
  },
54
55
  "devDependencies": {
55
- "vitest": "4.1.5"
56
+ "vitest": "4.1.6"
56
57
  }
57
58
  }
@@ -28,6 +28,7 @@ export type RepoConfig = {
28
28
  issue_triage?: {
29
29
  enable_auto_triage?: boolean;
30
30
  enable_auto_fix?: boolean;
31
+ use_fix_loop?: boolean;
31
32
  max_changed_files?: number;
32
33
  max_changed_lines?: number;
33
34
  };
@@ -6,9 +6,22 @@ export const KNOWN_DOISTBOT_LOGINS = new Set([
6
6
  'doistbot-app',
7
7
  'doistbot-app[bot]',
8
8
  ]);
9
+ export const KNOWN_AUTOMATION_LOGINS = new Set([
10
+ ...KNOWN_DOISTBOT_LOGINS,
11
+ 'dependabot',
12
+ 'dependabot[bot]',
13
+ 'renovate',
14
+ 'renovate[bot]',
15
+ ]);
9
16
  export function isKnownDoistbotLogin(login) {
10
17
  if (!login) {
11
18
  return false;
12
19
  }
13
20
  return KNOWN_DOISTBOT_LOGINS.has(login.toLowerCase());
14
21
  }
22
+ export function isKnownAutomationLogin(login) {
23
+ if (!login) {
24
+ return false;
25
+ }
26
+ return KNOWN_AUTOMATION_LOGINS.has(login.toLowerCase());
27
+ }
@@ -0,0 +1,29 @@
1
+ /** Default per-attempt delays for retrying transient HTTP failures. */
2
+ export const HTTP_RETRY_DELAYS_MS = [250, 500, 1000];
3
+ /** True for HTTP errors that are typically worth retrying: 429 and any 5xx. */
4
+ function isTransientHttpError(error) {
5
+ const status = error?.status;
6
+ return typeof status === 'number' && (status === 429 || (status >= 500 && status < 600));
7
+ }
8
+ /**
9
+ * Retries `fn` on errors classified as retryable, sleeping between attempts.
10
+ * Total attempts = 1 + delaysMs.length. Throws immediately on a non-retryable
11
+ * error, or after the budget is exhausted (re-throwing the last error).
12
+ *
13
+ * Defaults match HTTP usage (429 / 5xx with exponential backoff). Pass
14
+ * `delaysMs` / `isRetryable` to retry against a different signal.
15
+ */
16
+ export async function withRetry(fn, delaysMs = HTTP_RETRY_DELAYS_MS, isRetryable = isTransientHttpError) {
17
+ let attempt = 0;
18
+ while (true) {
19
+ try {
20
+ return await fn();
21
+ }
22
+ catch (error) {
23
+ if (!isRetryable(error) || attempt >= delaysMs.length)
24
+ throw error;
25
+ await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
26
+ attempt++;
27
+ }
28
+ }
29
+ }
@@ -44,8 +44,8 @@ export function parseBaseContext() {
44
44
  return {
45
45
  owner: requiredEnv('REPO_OWNER'),
46
46
  repo: requiredEnv('REPO_NAME'),
47
- prNumber: Number(requiredEnv('PR_NUMBER')),
48
- installationId: Number(requiredEnv('INSTALLATION_ID')),
47
+ prNumber: requiredPositiveInt('PR_NUMBER'),
48
+ installationId: requiredPositiveInt('INSTALLATION_ID'),
49
49
  appId: requiredEnv('APP_ID'),
50
50
  privateKey: requiredEnv('PRIVATE_KEY'),
51
51
  githubToken: process.env.GITHUB_TOKEN?.trim() || undefined,
@@ -12,6 +12,9 @@ switch (taskType) {
12
12
  case 'issue-fix-retry':
13
13
  await import('./tasks/issue-fix-retry/fix-retry.js');
14
14
  break;
15
+ case 'fix-loop-promotion':
16
+ await import('./tasks/issue-triage/fix-loop/promotion-task.js');
17
+ break;
15
18
  case 'persist-logs': {
16
19
  const { main } = await import('./tasks/persist-logs/persist-logs.js');
17
20
  await main();
@@ -161,13 +161,7 @@ async function main() {
161
161
  octokit: writeOctokit,
162
162
  triageOutput,
163
163
  fixAssessment,
164
- clonedRepos: clonedRepositories.map(({ repository, localPath }) => ({
165
- owner: repository.owner,
166
- repo: repository.repo,
167
- fullName: repository.fullName,
168
- localPath,
169
- repository,
170
- })),
164
+ clonedRepos: clonedRepositories,
171
165
  issueTitle: issue.title ?? 'Untitled issue',
172
166
  issueUrl: issue.html_url ?? getIssueUrl(ctx.owner, ctx.repo, ctx.issueNumber),
173
167
  ghToken: issueInstallationAuth.token,