@doist/doistbot-cli 1.0.10 → 1.0.12
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/dist/actions/review-usage.js +4 -4
- package/dist/actions/review.js +31 -21
- package/dist/config.js +61 -16
- package/dist/index.js +8 -2
- package/dist/terminal.js +4 -0
- package/package.json +10 -9
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +1 -1
- package/sandbox/dist/core/datadog-metrics.js +7 -2
- package/sandbox/dist/core/label-parsing.js +1 -22
- package/sandbox/dist/core/pi-execution-limits-extension.js +79 -0
- package/sandbox/dist/core/pi-progress-logging.js +4 -0
- package/sandbox/dist/core/pi.js +286 -67
- package/sandbox/dist/core/repository-map.js +1 -220
- package/sandbox/dist/core/repository-resolver.js +1 -166
- package/sandbox/dist/providers/config.js +1 -1
- package/sandbox/dist/providers/helpers.js +3 -0
- package/sandbox/dist/providers/pi-review.js +53 -3
- package/sandbox/dist/providers/schemas.js +7 -0
- package/sandbox/dist/providers/types.js +4 -1
- package/sandbox/dist/tasks/chat/chat.js +95 -45
- package/sandbox/dist/tasks/chat/prompt.js +5 -0
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +1 -0
- package/sandbox/dist/tasks/issue-summarize/model.js +4 -4
- package/sandbox/dist/tasks/issue-triage/context.js +2 -0
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +2 -1
- package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +4 -7
- package/sandbox/dist/tasks/issue-triage/hero-group-map.js +65 -41
- package/sandbox/dist/tasks/issue-triage/output.js +1 -1
- package/sandbox/dist/tasks/issue-triage/resolution.js +1 -0
- package/sandbox/dist/tasks/issue-triage/shared.js +2 -12
- package/sandbox/dist/tasks/issue-triage/triage.js +2 -0
- package/sandbox/dist/tasks/review/diff.js +1 -1
- package/sandbox/dist/tasks/review/engines/multi-focus.js +47 -12
- package/sandbox/dist/tasks/review/local-git.js +62 -8
- package/sandbox/dist/tasks/review/prompt.js +2 -0
- package/sandbox/dist/tasks/review/review-summary.js +1 -1
- package/sandbox/dist/tasks/review/summary-model.js +2 -2
- package/sandbox/dist/tasks/review/translation-review-policy.js +37 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +1 -1
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +18 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
2
2
|
import { logger } from '../../../core/logging.js';
|
|
3
|
+
import { toPiInvocationTelemetryFields } from '../../../core/pi.js';
|
|
3
4
|
import { genAiProviderName, withPhase } from '../../../core/tracing.js';
|
|
4
5
|
import { convertFindingsToComments } from '../comment-mapper.js';
|
|
5
6
|
import { buildReviewOutputFindings } from '../finding-output.js';
|
|
@@ -7,16 +8,17 @@ import { buildMultiFocusPrompt, REVIEW_FOCUS_DEFINITIONS, } from '../multi-focus
|
|
|
7
8
|
import { buildFallbackSummary, summarizeReviewFindings } from '../review-summary.js';
|
|
8
9
|
import { parseSeverity } from '../severity.js';
|
|
9
10
|
import { buildGeminiFlashReviewSummaryModel } from '../summary-model.js';
|
|
11
|
+
import { filterTranslationSyncOnlyFindings } from '../translation-review-policy.js';
|
|
10
12
|
import { dedupeMultiFocusFindings } from './dedupe.js';
|
|
11
13
|
import { buildGeminiFlashCredential, getApiKeys, getAvailableModels, getEnabledReviewModels, getOpenRouterReviewCredential, getProviderFailures, requestModelReview, } from './shared.js';
|
|
12
14
|
const DEFAULT_REVIEW_MODELS_BY_FOCUS = {
|
|
13
|
-
general: ['deepseek', '
|
|
14
|
-
security: ['deepseek', '
|
|
15
|
-
reuse: ['deepseek', '
|
|
16
|
-
quality: ['deepseek', '
|
|
17
|
-
efficiency: ['deepseek', '
|
|
18
|
-
tests: ['deepseek', '
|
|
19
|
-
standards: ['deepseek', '
|
|
15
|
+
general: ['deepseek', 'zai', 'openai'],
|
|
16
|
+
security: ['deepseek', 'zai', 'openai'],
|
|
17
|
+
reuse: ['deepseek', 'zai', 'openai'],
|
|
18
|
+
quality: ['deepseek', 'zai', 'openai'],
|
|
19
|
+
efficiency: ['deepseek', 'zai', 'openai'],
|
|
20
|
+
tests: ['deepseek', 'zai'],
|
|
21
|
+
standards: ['deepseek', 'zai'],
|
|
20
22
|
};
|
|
21
23
|
// Default Gemini-flash pass on failure. tests/standards keep no fallback unless
|
|
22
24
|
// a caller opts into a wider fallback set.
|
|
@@ -28,7 +30,17 @@ const DEFAULT_GEMINI_FLASH_FALLBACK_FOCUSES = [
|
|
|
28
30
|
'efficiency',
|
|
29
31
|
];
|
|
30
32
|
// Open models whose failed pass triggers that fallback.
|
|
31
|
-
const GEMINI_FLASH_FALLBACK_MODELS = ['deepseek', '
|
|
33
|
+
const GEMINI_FLASH_FALLBACK_MODELS = ['deepseek', 'zai'];
|
|
34
|
+
function setReviewLatencyAttributes(span, result) {
|
|
35
|
+
const telemetry = result.telemetry;
|
|
36
|
+
if (!telemetry)
|
|
37
|
+
return;
|
|
38
|
+
const attributes = Object.fromEntries(Object.entries(toPiInvocationTelemetryFields(telemetry)).map(([name, value]) => [
|
|
39
|
+
`review.${name}`,
|
|
40
|
+
value,
|
|
41
|
+
]));
|
|
42
|
+
span.setAttributes(attributes);
|
|
43
|
+
}
|
|
32
44
|
function lowPrioritySummaryNotice(count) {
|
|
33
45
|
return count === 1
|
|
34
46
|
? 'I also left one optional follow-up note in the details below.'
|
|
@@ -60,6 +72,9 @@ async function runFocusPass({ focus, model, basePrompt, standardsSection, apiKey
|
|
|
60
72
|
if (result?.requestModel) {
|
|
61
73
|
span.setAttributes({ 'gen_ai.request.model': result.requestModel });
|
|
62
74
|
}
|
|
75
|
+
if (result) {
|
|
76
|
+
setReviewLatencyAttributes(span, result);
|
|
77
|
+
}
|
|
63
78
|
if (!result) {
|
|
64
79
|
const error = `No review result returned for ${model}`;
|
|
65
80
|
span.setStatus({ code: SpanStatusCode.ERROR, message: error });
|
|
@@ -326,8 +341,26 @@ export async function runMultiFocusReviewEngine({ ctx, prompt, lineLookup, prTit
|
|
|
326
341
|
providerFailures,
|
|
327
342
|
};
|
|
328
343
|
}
|
|
329
|
-
const
|
|
330
|
-
const
|
|
344
|
+
const modelFindings = successfulRuns.flatMap((run) => run.findings);
|
|
345
|
+
const { findings: allFindings, droppedFindings: translationSyncFindings } = filterTranslationSyncOnlyFindings({
|
|
346
|
+
repoOwner: ctx.owner,
|
|
347
|
+
repoName: ctx.repo,
|
|
348
|
+
findings: modelFindings,
|
|
349
|
+
});
|
|
350
|
+
if (translationSyncFindings.length > 0) {
|
|
351
|
+
logger.info('Filtered translation sync-only findings', {
|
|
352
|
+
repository: `${ctx.owner}/${ctx.repo}`,
|
|
353
|
+
droppedCount: translationSyncFindings.length,
|
|
354
|
+
droppedFindingIds: translationSyncFindings.map((finding) => finding.id),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const suppressedFindings = new Set(translationSyncFindings);
|
|
358
|
+
const reviewObservationRuns = successfulRuns.map((run) => {
|
|
359
|
+
const hasSuppressedFinding = run.findings.some((finding) => suppressedFindings.has(finding));
|
|
360
|
+
const hasKeptFinding = run.findings.some((finding) => !suppressedFindings.has(finding));
|
|
361
|
+
return hasSuppressedFinding && !hasKeptFinding ? { ...run, summary: '' } : run;
|
|
362
|
+
});
|
|
363
|
+
const reviewObservations = collectReviewObservations(reviewObservationRuns);
|
|
331
364
|
let dedupedFindings = allFindings;
|
|
332
365
|
try {
|
|
333
366
|
dedupedFindings = await dedupeMultiFocusFindings({
|
|
@@ -347,7 +380,7 @@ export async function runMultiFocusReviewEngine({ ctx, prompt, lineLookup, prTit
|
|
|
347
380
|
const routedFindings = routeLowPriorityFindings(dedupedFindings, lowPriorityFindingPlacement);
|
|
348
381
|
const geminiSummaryFallback = buildGeminiFlashReviewSummaryModel(apiKeys.gemini);
|
|
349
382
|
const summaryModel = ctx.reviewSummaryModel ??
|
|
350
|
-
(apiKeys.
|
|
383
|
+
(apiKeys.zai ? { provider: 'zai', credential: apiKeys.zai } : undefined);
|
|
351
384
|
const review = await buildMultiFocusReviewOutput({
|
|
352
385
|
inlineFindings: routedFindings.inlineFindings,
|
|
353
386
|
summaryFindings: routedFindings.summaryFindings,
|
|
@@ -366,7 +399,9 @@ export async function runMultiFocusReviewEngine({ ctx, prompt, lineLookup, prTit
|
|
|
366
399
|
supportModels: allAvailableModels,
|
|
367
400
|
focuses: focusDefinitions.map((focus) => focus.name),
|
|
368
401
|
successfulRuns: successfulRuns.length,
|
|
369
|
-
findingsCount:
|
|
402
|
+
findingsCount: modelFindings.length,
|
|
403
|
+
findingsAfterPolicyCount: allFindings.length,
|
|
404
|
+
translationSyncFindingsCount: translationSyncFindings.length,
|
|
370
405
|
dedupedFindingsCount: dedupedFindings.length,
|
|
371
406
|
inlineFindingsCount: routedFindings.inlineFindings.length,
|
|
372
407
|
summaryOnlyFindingsCount: routedFindings.summaryFindings.length,
|
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import {} from './diff.js';
|
|
4
|
+
import { MAX_DIFF_CHARS } from './diff.js';
|
|
5
5
|
const GIT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
6
6
|
export function createGitRunner(workspaceDir) {
|
|
7
|
-
return (args) =>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
return (args, options) => {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync('git', ['-C', workspaceDir, '-c', 'core.quotePath=false', ...args], {
|
|
10
|
+
encoding: 'utf8',
|
|
11
|
+
maxBuffer: options?.maxBufferBytes ?? GIT_MAX_BUFFER_BYTES,
|
|
12
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const failure = error;
|
|
17
|
+
const stdout = typeof failure.stdout === 'string'
|
|
18
|
+
? failure.stdout
|
|
19
|
+
: Buffer.isBuffer(failure.stdout)
|
|
20
|
+
? failure.stdout.toString('utf8')
|
|
21
|
+
: '';
|
|
22
|
+
if (failure.code === 'ENOBUFS' &&
|
|
23
|
+
options?.returnPartialOutputOnMaxBuffer === true &&
|
|
24
|
+
options.maxBufferBytes !== undefined) {
|
|
25
|
+
return stdout.slice(0, options.maxBufferBytes);
|
|
26
|
+
}
|
|
27
|
+
if (typeof failure.status === 'number' &&
|
|
28
|
+
options?.allowedExitCodes?.includes(failure.status)) {
|
|
29
|
+
return stdout;
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
12
34
|
}
|
|
13
35
|
export function tryGit(runner, args) {
|
|
14
36
|
try {
|
|
@@ -75,7 +97,7 @@ function unquoteGitPath(rawPath) {
|
|
|
75
97
|
unquoted = trimmed.slice(1, -1);
|
|
76
98
|
}
|
|
77
99
|
}
|
|
78
|
-
return unquoted.replace(/^[ab]\//, '');
|
|
100
|
+
return unquoted.replace(/^[ab]\//, '').replace(/^\.\//, '');
|
|
79
101
|
}
|
|
80
102
|
function readDiffGitPath(input, startIndex) {
|
|
81
103
|
let index = startIndex;
|
|
@@ -162,6 +184,38 @@ export function parseGitDiffToFiles(diff) {
|
|
|
162
184
|
flushCurrent();
|
|
163
185
|
return files;
|
|
164
186
|
}
|
|
187
|
+
function getUntrackedReviewFiles(runner) {
|
|
188
|
+
const untrackedPaths = runner(['ls-files', '--others', '--exclude-standard', '-z'])
|
|
189
|
+
.split('\0')
|
|
190
|
+
.filter(Boolean);
|
|
191
|
+
if (untrackedPaths.length === 0) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
const files = [];
|
|
195
|
+
let remainingDiffChars = MAX_DIFF_CHARS;
|
|
196
|
+
for (const filename of untrackedPaths) {
|
|
197
|
+
if (remainingDiffChars <= 0) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
// The two-file form also works with Git versions before 2.51.
|
|
201
|
+
const diff = runner([
|
|
202
|
+
'diff',
|
|
203
|
+
'--no-index',
|
|
204
|
+
'--no-ext-diff',
|
|
205
|
+
'--no-color',
|
|
206
|
+
'--',
|
|
207
|
+
'/dev/null',
|
|
208
|
+
`./${filename}`,
|
|
209
|
+
], {
|
|
210
|
+
allowedExitCodes: [1],
|
|
211
|
+
maxBufferBytes: remainingDiffChars,
|
|
212
|
+
returnPartialOutputOnMaxBuffer: true,
|
|
213
|
+
});
|
|
214
|
+
files.push(...parseGitDiffToFiles(diff));
|
|
215
|
+
remainingDiffChars -= diff.length;
|
|
216
|
+
}
|
|
217
|
+
return files;
|
|
218
|
+
}
|
|
165
219
|
export function getLocalReviewFiles({ runner, source, }) {
|
|
166
220
|
if (source === 'staged') {
|
|
167
221
|
const diff = runner(['diff', '--staged', '--no-ext-diff', '--no-color']);
|
|
@@ -183,7 +237,7 @@ export function getLocalReviewFiles({ runner, source, }) {
|
|
|
183
237
|
const mergeBase = runner(['merge-base', baseBranch, 'HEAD']).trim();
|
|
184
238
|
const diff = runner(['diff', '--no-ext-diff', '--no-color', mergeBase]);
|
|
185
239
|
return {
|
|
186
|
-
files: parseGitDiffToFiles(diff),
|
|
240
|
+
files: [...parseGitDiffToFiles(diff), ...getUntrackedReviewFiles(runner)],
|
|
187
241
|
baseBranch,
|
|
188
242
|
};
|
|
189
243
|
}
|
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { logger } from '../../core/logging.js';
|
|
5
|
+
import { buildTranslationReviewGuidance } from './translation-review-policy.js';
|
|
5
6
|
const MAX_BODY_CHARS = 4000;
|
|
6
7
|
let cachedStandardsSection = null;
|
|
7
8
|
const REVIEW_TASK_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -75,5 +76,6 @@ export function buildReviewPromptVariables({ repoOwner, repoName, prNumber, prTi
|
|
|
75
76
|
PR_CONVERSATION: formatPrConversation(prConversation),
|
|
76
77
|
DIFF_SECTION: fullDiffSection,
|
|
77
78
|
STANDARDS_SECTION: standardsSection,
|
|
79
|
+
TRANSLATION_REVIEW_GUIDANCE: buildTranslationReviewGuidance(repoOwner, repoName),
|
|
78
80
|
};
|
|
79
81
|
}
|
|
@@ -51,7 +51,7 @@ Keep it human and compact: the opener should be 1 sentence, or 2 only if the PR
|
|
|
51
51
|
Respond with only valid JSON in this exact shape:
|
|
52
52
|
{"summary":"<your summary>","comments":[]}`;
|
|
53
53
|
}
|
|
54
|
-
export async function summarizeReviewFindings({ credential, provider = '
|
|
54
|
+
export async function summarizeReviewFindings({ credential, provider = 'zai', modelName, fallbackModels = [], prTitle, prBody, findings, reviewObservations, workspaceDir, requestReview = requestReviewSummary, }) {
|
|
55
55
|
const prompt = buildReviewSummaryPrompt({
|
|
56
56
|
prTitle,
|
|
57
57
|
prBody,
|
|
@@ -56,7 +56,7 @@ function getRequestFailureReason(result) {
|
|
|
56
56
|
? 'no_assistant_response'
|
|
57
57
|
: 'request_failed';
|
|
58
58
|
}
|
|
59
|
-
export async function requestReviewSummary({ credential, provider = '
|
|
59
|
+
export async function requestReviewSummary({ credential, provider = 'zai', modelName, prompt, workspaceDir, }) {
|
|
60
60
|
const resolvedCredential = resolveReviewProviderCredential(credential);
|
|
61
61
|
if (!resolvedCredential) {
|
|
62
62
|
return failedReviewSummaryRequest({ reason: 'missing_credential' });
|
|
@@ -93,7 +93,7 @@ export async function requestReviewSummary({ credential, provider = 'grok', mode
|
|
|
93
93
|
}
|
|
94
94
|
async function runReviewSummaryRequest({ provider, resolvedCredential, resolvedModelName, logModelName, prompt, workspaceDir, }) {
|
|
95
95
|
try {
|
|
96
|
-
const thinkingLevel = provider === '
|
|
96
|
+
const thinkingLevel = provider === 'zai' ? 'high' : DEFAULT_REVIEW_SUMMARY_THINKING_LEVEL;
|
|
97
97
|
logger.info('Review summary model request', {
|
|
98
98
|
provider,
|
|
99
99
|
modelName: logModelName,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const ENGLISH_SOURCE_FIRST_REPOSITORIES = new Set([
|
|
2
|
+
'doist/todoist-web',
|
|
3
|
+
'doist/todoist-android',
|
|
4
|
+
'doist/todoist-ios',
|
|
5
|
+
'doist/todoist-electron',
|
|
6
|
+
'doist/todoist-macos',
|
|
7
|
+
]);
|
|
8
|
+
export function usesEnglishSourceFirstTranslations(repoOwner, repoName) {
|
|
9
|
+
return ENGLISH_SOURCE_FIRST_REPOSITORIES.has(`${repoOwner}/${repoName}`.toLowerCase());
|
|
10
|
+
}
|
|
11
|
+
export function buildTranslationReviewGuidance(repoOwner, repoName) {
|
|
12
|
+
if (!usesEnglishSourceFirstTranslations(repoOwner, repoName)) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
return `## Repository-specific Translation Workflow
|
|
16
|
+
|
|
17
|
+
This repository uses an English-source-first workflow: feature PRs change the English source strings, automation sends them to the repository's translation service, and translated locale files arrive through a later automated sync.
|
|
18
|
+
|
|
19
|
+
- Do not create a finding or summary note merely because translated locale files or generated locale bundles are unchanged in the feature PR.
|
|
20
|
+
- Do not ask the author to pull, update, generate, or commit translations manually. If the PR description already mentions the translation-service follow-up, including Transifex, do not repeat it.
|
|
21
|
+
- Continue to report concrete i18n implementation errors introduced by the diff, such as incorrect translation API or gettext usage, broken interpolation placeholders, pluralization or context mistakes, and patterns that prevent source-string extraction.`;
|
|
22
|
+
}
|
|
23
|
+
export function isTranslationSyncOnlyFinding(repoOwner, repoName, finding) {
|
|
24
|
+
return (usesEnglishSourceFirstTranslations(repoOwner, repoName) &&
|
|
25
|
+
finding.translationResolution === 'automated_sync_resolves');
|
|
26
|
+
}
|
|
27
|
+
export function filterTranslationSyncOnlyFindings({ repoOwner, repoName, findings, }) {
|
|
28
|
+
const keptFindings = [];
|
|
29
|
+
const droppedFindings = [];
|
|
30
|
+
for (const finding of findings) {
|
|
31
|
+
const destination = isTranslationSyncOnlyFinding(repoOwner, repoName, finding)
|
|
32
|
+
? droppedFindings
|
|
33
|
+
: keptFindings;
|
|
34
|
+
destination.push(finding);
|
|
35
|
+
}
|
|
36
|
+
return { findings: keptFindings, droppedFindings };
|
|
37
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from 'doistbot-logging';
|
|
2
2
|
import type { Octokit } from 'octokit';
|
|
3
3
|
export declare const CONFIG_FILE_PATH = ".github/doistbot.yml";
|
|
4
|
-
export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "
|
|
4
|
+
export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "zai"];
|
|
5
5
|
export type DefaultReviewModel = (typeof DEFAULT_REVIEW_MODELS)[number];
|
|
6
6
|
export type RepoConfig = {
|
|
7
7
|
review?: {
|
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { load as yamlLoad } from 'js-yaml';
|
|
4
4
|
export const CONFIG_FILE_PATH = '.github/doistbot.yml';
|
|
5
|
-
export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', '
|
|
5
|
+
export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'zai'];
|
|
6
6
|
function parseRepoConfigContent(content) {
|
|
7
7
|
return yamlLoad(content);
|
|
8
8
|
}
|
|
@@ -73,6 +73,8 @@ $DIFF_SECTION
|
|
|
73
73
|
- Keep code ranges tight and comments easy to understand without close reading.
|
|
74
74
|
- **Do not flag dependency or tooling versions as nonexistent, outdated, or "too new."** Your training data has a cutoff, so you can't know which versions of GitHub Actions, npm/PyPI/etc. packages, or their APIs are currently published. Never tell the author a version doesn't exist, is wrong, or should be downgraded based on your own knowledge — and never claim a library feature or API is unavailable because you assume the project uses an older major version. Raise a version-related finding only when the diff or checked-out repo contains concrete contradicting evidence (e.g., the manifest/lockfile pins a different version, or the referenced symbol is absent from the dependency's actual source on disk).
|
|
75
75
|
|
|
76
|
+
$TRANSLATION_REVIEW_GUIDANCE
|
|
77
|
+
|
|
76
78
|
## Writing Style
|
|
77
79
|
|
|
78
80
|
- Write clearly, concisely, and directly. Be engaging without sounding generic.
|
|
@@ -97,6 +99,20 @@ At the beginning of each finding title, tag it with a priority level:
|
|
|
97
99
|
|
|
98
100
|
## Output Format
|
|
99
101
|
|
|
102
|
+
For every finding, answer this internal classification question: assuming the repository's normal
|
|
103
|
+
translation automation completes successfully, would the issue be fully resolved without changing
|
|
104
|
+
application code, source-string keys, extraction configuration, lookup behavior, placeholders,
|
|
105
|
+
pluralization, or locale context?
|
|
106
|
+
|
|
107
|
+
- Use `automated_sync_resolves` only when the answer is clearly yes.
|
|
108
|
+
- Use `code_change_required_or_uncertain` for all non-translation findings, concrete i18n
|
|
109
|
+
correctness problems, and any uncertain case.
|
|
110
|
+
- Keep this classification out of the finding body.
|
|
111
|
+
- Missing or stale translated content, requests to pull or commit locale artifacts, and advice to
|
|
112
|
+
wait for the automated sync are `automated_sync_resolves` when no code-level problem remains.
|
|
113
|
+
- Broken extraction, keys, lookups, placeholders, pluralization, or locale handling are
|
|
114
|
+
`code_change_required_or_uncertain`, even when their symptom is a missing translation.
|
|
115
|
+
|
|
100
116
|
Return strict JSON in this exact shape:
|
|
101
117
|
|
|
102
118
|
```json
|
|
@@ -107,7 +123,8 @@ Return strict JSON in this exact shape:
|
|
|
107
123
|
"path": "relative file path exactly as shown in the diff above",
|
|
108
124
|
"line": 42,
|
|
109
125
|
"side": "RIGHT",
|
|
110
|
-
"body": "[P2] Brief description of the issue and actionable fix."
|
|
126
|
+
"body": "[P2] Brief description of the issue and actionable fix.",
|
|
127
|
+
"translationResolution": "code_change_required_or_uncertain"
|
|
111
128
|
}
|
|
112
129
|
]
|
|
113
130
|
}
|