@link-assistant/hive-mind 2.11.4 → 2.11.6
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 +14 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +13 -10
- package/src/bidirectional-interactive.lib.mjs +2 -1
- package/src/claude.lib.mjs +13 -14
- package/src/codex.lib.mjs +29 -70
- package/src/codex.run-diagnostics.lib.mjs +117 -0
- package/src/formal-ai-runtime.lib.mjs +439 -0
- package/src/formal-ai.lib.mjs +143 -41
- package/src/gemini.lib.mjs +6 -8
- package/src/git.lib.mjs +10 -1
- package/src/github-error-reporter.lib.mjs +2 -1
- package/src/github-rate-limit.lib.mjs +13 -0
- package/src/github-terminal-state.lib.mjs +7 -1
- package/src/github.lib.mjs +16 -8
- package/src/opencode.lib.mjs +11 -10
- package/src/post-finish-sanitization-sweep.lib.mjs +4 -3
- package/src/quiet-probe.lib.mjs +72 -0
- package/src/qwen.lib.mjs +8 -7
- package/src/solve.auto-continue.lib.mjs +2 -1
- package/src/solve.auto-pr.lib.mjs +11 -5
- package/src/solve.branch-errors.lib.mjs +2 -1
- package/src/solve.execution.lib.mjs +2 -1
- package/src/solve.feedback.lib.mjs +18 -13
- package/src/solve.fork-sync.lib.mjs +2 -1
- package/src/solve.repo-setup.lib.mjs +40 -4
- package/src/solve.repository.lib.mjs +4 -3
- package/src/solve.results.lib.mjs +3 -2
- package/src/solve.validation.lib.mjs +7 -0
- package/src/token-sanitization.lib.mjs +7 -2
package/src/gemini.lib.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Iss
|
|
|
18
18
|
const __geminiBuildSolveResumeCmd = (argv, sessionId, tempDir) => (sessionId && argv?.url ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: 'gemini', model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null);
|
|
19
19
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
20
20
|
import { defaultModels, geminiModels, isFormalAiModel } from './models/index.mjs';
|
|
21
|
-
import { logPreparedToolCommand,
|
|
21
|
+
import { isPrepareOnly, logPreparedToolCommand, resolveFormalAiToolExecution } from './formal-ai.lib.mjs';
|
|
22
22
|
import { buildFormalAiPricingInfo } from './formal-ai-pricing.lib.mjs'; // Issue #2119
|
|
23
23
|
import { checkPlaywrightMcpPackageAvailability } from './playwright-mcp.lib.mjs';
|
|
24
24
|
import { classifyRetryableError, prepareRetryAfterError, waitWithCountdown } from './tool-retry.lib.mjs';
|
|
@@ -412,11 +412,9 @@ export const executeGeminiCommand = async params => {
|
|
|
412
412
|
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
413
413
|
|
|
414
414
|
const mappedModel = mapModelToId(argv.model || defaultModels.gemini);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
toolPath: geminiPath,
|
|
419
|
-
});
|
|
415
|
+
// Issue #2130: Formal AI runs the native CLI against a local Formal AI server (no argv wrapper).
|
|
416
|
+
const toolInvocation = await resolveFormalAiToolExecution({ tool: 'gemini', model: argv.model || defaultModels.gemini, toolPath: geminiPath, workdir: tempDir, log, verbose: argv.verbose, prepareOnly: isPrepareOnly(argv) });
|
|
417
|
+
const geminiEnv = { ...process.env, ...toolInvocation.env };
|
|
420
418
|
const combinedPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
|
|
421
419
|
|
|
422
420
|
if (argv.resume) {
|
|
@@ -426,7 +424,6 @@ export const executeGeminiCommand = async params => {
|
|
|
426
424
|
// Issue #1809: build args via shared helper so verbose/sandbox/include-dirs
|
|
427
425
|
// toggles stay consistent between the logged command and the real invocation.
|
|
428
426
|
const geminiArgList = buildGeminiArgs(argv, mappedModel, { tempDir, workspaceTmpDir });
|
|
429
|
-
const fullGeminiArgList = [...toolInvocation.args, ...geminiArgList];
|
|
430
427
|
const fullCommand = `(cd ${shellQuote(tempDir)} && ${toolInvocation.displayCommand} ${geminiArgList.map(shellQuote).join(' ')} <<< <prompt>)`;
|
|
431
428
|
|
|
432
429
|
const preparedResult = await logPreparedToolCommand({ argv, fullCommand, log, formatAligned });
|
|
@@ -449,7 +446,8 @@ export const executeGeminiCommand = async params => {
|
|
|
449
446
|
cwd: tempDir,
|
|
450
447
|
stdin: combinedPrompt,
|
|
451
448
|
mirror: false,
|
|
452
|
-
|
|
449
|
+
env: geminiEnv,
|
|
450
|
+
})`${toolInvocation.command} ${geminiArgList}`;
|
|
453
451
|
|
|
454
452
|
await log(`${formatAligned('📋', 'Command details:', '')}`);
|
|
455
453
|
await log(formatAligned('📂', 'Working directory:', tempDir, 2));
|
package/src/git.lib.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
|
+
|
|
5
|
+
import { quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
4
6
|
import { promisify } from 'util';
|
|
5
7
|
|
|
6
8
|
const execAsync = promisify(exec);
|
|
@@ -86,7 +88,14 @@ export const getGitVersion = async (execFunc = execAsync, currentVersion) => {
|
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
// Helper function for async git operations with zx
|
|
89
|
-
|
|
91
|
+
//
|
|
92
|
+
// Issue #2130: every probe below is quiet. They answer questions the caller
|
|
93
|
+
// asks about its own checkout ("is this a git repo?", "what is HEAD?"), and
|
|
94
|
+
// their bare answers - a `.git` path, a tag, a short SHA - were mirrored into
|
|
95
|
+
// the log attached to the pull request with nothing to explain them. The
|
|
96
|
+
// version this function computes is logged in words by the caller.
|
|
97
|
+
export const getGitVersionAsync = async (dollar, currentVersion) => {
|
|
98
|
+
const $ = quietProbe(dollar);
|
|
90
99
|
// First check if we're in a git repository to avoid "fatal: not a git repository" errors
|
|
91
100
|
// Redirect stderr to /dev/null at shell level to prevent error messages from appearing
|
|
92
101
|
try {
|
|
@@ -17,6 +17,7 @@ if (typeof globalThis.use === 'undefined') {
|
|
|
17
17
|
const fs = (await use('fs')).promises;
|
|
18
18
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
19
19
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
20
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
20
21
|
const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
21
22
|
const GITHUB_ISSUE_BODY_MAX_SIZE = 60000;
|
|
22
23
|
const GITHUB_FILE_MAX_SIZE = 10 * 1024 * 1024;
|
|
@@ -53,7 +54,7 @@ export const promptUserForIssueCreation = async errorMessage => {
|
|
|
53
54
|
*/
|
|
54
55
|
const getCurrentGitHubUser = async () => {
|
|
55
56
|
try {
|
|
56
|
-
const result = await
|
|
57
|
+
const result = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
57
58
|
if (result.exitCode === 0) {
|
|
58
59
|
const user = result.stdout.toString().trim();
|
|
59
60
|
if (user) return user;
|
|
@@ -449,6 +449,13 @@ export const execGhWithRetry = async (command, options = {}) => {
|
|
|
449
449
|
* const { $: rawDollar } = await use('command-stream');
|
|
450
450
|
* const $ = wrapDollarWithGhRetry(rawDollar);
|
|
451
451
|
*
|
|
452
|
+
* The wrapper also forwards command-stream's options-call form, so the
|
|
453
|
+
* "do not mirror this probe" idiom (see src/quiet-probe.lib.mjs) keeps working
|
|
454
|
+
* on a wrapped `$` - issue #2130, where read-only probes dumped raw API
|
|
455
|
+
* payloads into the log that gets attached to the pull request:
|
|
456
|
+
*
|
|
457
|
+
* await $(QUIET_PROBE)`gh api repos/${owner}/${repo}`;
|
|
458
|
+
*
|
|
452
459
|
* @template T
|
|
453
460
|
* @param {(strings: TemplateStringsArray, ...values: unknown[]) => Promise<T>} dollar
|
|
454
461
|
* @param {object} [options] - forwarded to ghWithRateLimitRetry per call.
|
|
@@ -456,6 +463,12 @@ export const execGhWithRetry = async (command, options = {}) => {
|
|
|
456
463
|
*/
|
|
457
464
|
export const wrapDollarWithGhRetry = (dollar, options = {}) => {
|
|
458
465
|
const wrapped = (strings, ...values) => {
|
|
466
|
+
// Options-call form: `$({ mirror: false })` returns a new tag bound to
|
|
467
|
+
// those options. Template literals always arrive as an array of quasis, so
|
|
468
|
+
// a plain object here is unambiguous.
|
|
469
|
+
if (strings && !Array.isArray(strings) && typeof strings === 'object') {
|
|
470
|
+
return wrapDollarWithGhRetry(dollar(strings), options);
|
|
471
|
+
}
|
|
459
472
|
// Reconstruct the literal command for inspection (sufficient — leading
|
|
460
473
|
// `gh ` is what we care about).
|
|
461
474
|
let preview = '';
|
|
@@ -21,7 +21,13 @@ const getDefaultCommandRunner = async () => {
|
|
|
21
21
|
const use = globalThis.use;
|
|
22
22
|
const { $: rawDollar } = await use('command-stream');
|
|
23
23
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
24
|
-
|
|
24
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs');
|
|
25
|
+
// Issue #2130: these are existence probes. Their raw payloads (a ~33 KB pull
|
|
26
|
+
// request object, once per watch iteration) and their expected "gh: Not Found
|
|
27
|
+
// (HTTP 404)" answers were mirrored into the attached log, where a handled
|
|
28
|
+
// probe reads as an unexplained error. Every outcome is reported by the
|
|
29
|
+
// caller in words, so the raw output is captured but never mirrored.
|
|
30
|
+
defaultCommandRunner = wrapDollarWithGhRetry(rawDollar(QUIET_PROBE));
|
|
25
31
|
return defaultCommandRunner;
|
|
26
32
|
};
|
|
27
33
|
|
package/src/github.lib.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { buildCostInfoString } from './github-cost-info.lib.mjs';
|
|
|
19
19
|
export { buildCostInfoString };
|
|
20
20
|
// #1756: route gh exec calls through transient + rate-limit retry wrapper
|
|
21
21
|
import { execGhWithRetry } from './github-rate-limit.lib.mjs';
|
|
22
|
+
import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
22
23
|
// Issue #1625: Named marker constants (single source of truth) + in-memory
|
|
23
24
|
// tracking for tool-posted comments. See tool-comments.lib.mjs for design.
|
|
24
25
|
import { SOLUTION_DRAFT_LOG_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, postTrackedComment, postTrackedCommentFromFile } from './tool-comments.lib.mjs';
|
|
@@ -43,8 +44,11 @@ export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
|
|
|
43
44
|
const { $ } = await use('command-stream');
|
|
44
45
|
|
|
45
46
|
try {
|
|
46
|
-
//
|
|
47
|
-
|
|
47
|
+
// Issue #2130: this is an existence probe, and "absent" is the answer the
|
|
48
|
+
// caller is usually looking for. Mirroring the command would print the whole
|
|
49
|
+
// contents payload on a hit and `gh: Not Found (HTTP 404)` on a miss, which
|
|
50
|
+
// reads as a failure in the log even though nothing went wrong.
|
|
51
|
+
const result = await $(QUIET_PROBE)`gh api repos/${owner}/${repo}/contents/${fileName}?ref=${branchName}`;
|
|
48
52
|
return result.code === 0;
|
|
49
53
|
} catch (error) {
|
|
50
54
|
// File doesn't exist or access error - this is expected behavior
|
|
@@ -66,8 +70,10 @@ export const checkGitHubPermissions = async () => {
|
|
|
66
70
|
const { $ } = await use('command-stream');
|
|
67
71
|
try {
|
|
68
72
|
await log('\n🔐 Checking GitHub authentication and permissions...');
|
|
69
|
-
// Get auth status including token scopes
|
|
70
|
-
|
|
73
|
+
// Get auth status including token scopes.
|
|
74
|
+
// Issue #2130: capture without mirroring - the parsed summary below is what
|
|
75
|
+
// belongs in the log, not gh's raw account/token/scope block.
|
|
76
|
+
const authStatusResult = await $(QUIET_PROBE)`gh auth status 2>&1`;
|
|
71
77
|
const authOutput = authStatusResult.stdout.toString() + authStatusResult.stderr.toString();
|
|
72
78
|
if (authStatusResult.code !== 0 || authOutput.includes('not logged into any GitHub hosts')) {
|
|
73
79
|
await log('❌ GitHub authentication error: Not logged in', { level: 'error' });
|
|
@@ -183,7 +189,7 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
|
|
|
183
189
|
await log('');
|
|
184
190
|
// Get current user to suggest their fork
|
|
185
191
|
try {
|
|
186
|
-
const userResult = await
|
|
192
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
187
193
|
if (userResult.code === 0) {
|
|
188
194
|
const currentUser = userResult.stdout.toString().trim();
|
|
189
195
|
await log(' Run this command:', { level: 'error' });
|
|
@@ -614,7 +620,7 @@ ${logContent}
|
|
|
614
620
|
// Issue #1173: Use public upload for public repos, private for private repos
|
|
615
621
|
let isPublicRepo = true;
|
|
616
622
|
try {
|
|
617
|
-
const repoVisibilityResult = await
|
|
623
|
+
const repoVisibilityResult = await $(QUIET_PROBE)`gh api repos/${owner}/${repo} --jq .visibility`;
|
|
618
624
|
if (repoVisibilityResult.code === 0) {
|
|
619
625
|
const visibility = repoVisibilityResult.stdout.toString().trim();
|
|
620
626
|
isPublicRepo = visibility === 'public';
|
|
@@ -972,7 +978,9 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
|
|
|
972
978
|
await log(`🔍 Fetching issues from GitHub Project #${projectNumber} (owner: ${owner}, status: ${statusFilter})`);
|
|
973
979
|
// Check for project scope in GitHub CLI authentication
|
|
974
980
|
try {
|
|
975
|
-
|
|
981
|
+
// Issue #2130: --show-token prints the token in clear text; mirroring it
|
|
982
|
+
// would put a live credential on stdout and in the log file.
|
|
983
|
+
const authStatus = await $(QUIET_PROBE)`gh auth status --show-token`;
|
|
976
984
|
if (!authStatus.stdout.includes('project')) {
|
|
977
985
|
throw new Error('Missing project scope. Run: gh auth refresh -s project');
|
|
978
986
|
}
|
|
@@ -1431,7 +1439,7 @@ export async function handlePRNotFoundError({ prNumber, owner, repo, argv, shoul
|
|
|
1431
1439
|
export async function detectRepositoryVisibility(owner, repo) {
|
|
1432
1440
|
try {
|
|
1433
1441
|
// Issue #1536: retry on transient network errors
|
|
1434
|
-
const visibilityResult = await ghCmdRetry(() =>
|
|
1442
|
+
const visibilityResult = await ghCmdRetry(() => $(QUIET_PROBE)`gh api repos/${owner}/${repo} --jq .visibility`, { label: `visibility ${owner}/${repo}` });
|
|
1435
1443
|
if (visibilityResult.code === 0) {
|
|
1436
1444
|
const visibility = visibilityResult.stdout.toString().trim();
|
|
1437
1445
|
const isPublic = visibility === 'public';
|
package/src/opencode.lib.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import { timeouts, retryLimits } from './config.lib.mjs';
|
|
|
20
20
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
21
21
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
22
22
|
import { opencodeModels, defaultModels } from './models/index.mjs';
|
|
23
|
-
import { logPreparedToolCommand,
|
|
23
|
+
import { isPrepareOnly, logPreparedToolCommand, resolveFormalAiToolExecution } from './formal-ai.lib.mjs';
|
|
24
24
|
import { checkPlaywrightMcpPackageAvailability, getOpenCodePlaywrightMcpDisableEnv } from './playwright-mcp.lib.mjs';
|
|
25
25
|
import { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage as parseOpenCodeTokenUsage } from './agent-token-usage.lib.mjs';
|
|
26
26
|
import { createJsonStreamScanner } from './json-stream.lib.mjs';
|
|
@@ -234,11 +234,9 @@ export const executeOpenCodeCommand = async params => {
|
|
|
234
234
|
|
|
235
235
|
// Map model alias to full ID
|
|
236
236
|
const mappedModel = mapModelToId(argv.model);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
toolPath: opencodePath,
|
|
241
|
-
});
|
|
237
|
+
// Issue #2130: Formal AI runs the native CLI against a local Formal AI server (no argv wrapper).
|
|
238
|
+
const toolInvocation = await resolveFormalAiToolExecution({ tool: 'opencode', model: argv.model, toolPath: opencodePath, workdir: tempDir, log, verbose: argv.verbose, prepareOnly: isPrepareOnly(argv), env: opencodeEnv });
|
|
239
|
+
Object.assign(opencodeEnv, toolInvocation.env);
|
|
242
240
|
const streamingTokenUsage = createAgentTokenUsage();
|
|
243
241
|
|
|
244
242
|
// Build opencode command arguments
|
|
@@ -310,14 +308,14 @@ export const executeOpenCodeCommand = async params => {
|
|
|
310
308
|
mirror: false,
|
|
311
309
|
env: opencodeEnv,
|
|
312
310
|
});
|
|
313
|
-
execCommand =
|
|
311
|
+
execCommand = commandRunner`cat ${promptFile} | ${toolInvocation.command} run --format json --session ${argv.resume} --model ${mappedModel}`;
|
|
314
312
|
} else {
|
|
315
313
|
const commandRunner = $({
|
|
316
314
|
cwd: tempDir,
|
|
317
315
|
mirror: false,
|
|
318
316
|
env: opencodeEnv,
|
|
319
317
|
});
|
|
320
|
-
execCommand =
|
|
318
|
+
execCommand = commandRunner`cat ${promptFile} | ${toolInvocation.command} run --format json --model ${mappedModel}`;
|
|
321
319
|
}
|
|
322
320
|
|
|
323
321
|
await log(`${formatAligned('📋', 'Command details:', '')}`);
|
|
@@ -355,8 +353,11 @@ export const executeOpenCodeCommand = async params => {
|
|
|
355
353
|
accumulateAgentStepFinishUsage(streamingTokenUsage, data);
|
|
356
354
|
// Track text content for result summary
|
|
357
355
|
// OpenCode outputs text via 'text', 'assistant', 'message', or 'result' type events
|
|
358
|
-
|
|
359
|
-
|
|
356
|
+
// Issue #2130: OpenCode-derived CLIs nest assistant text under `part`
|
|
357
|
+
// (`{"type":"text","part":{"type":"text","text":"…"}}`) with no
|
|
358
|
+
// top-level `data.text`, which left `resultSummary` null.
|
|
359
|
+
if (data.type === 'text' && (data.text || data.part?.text)) {
|
|
360
|
+
lastTextContent = data.text || data.part.text;
|
|
360
361
|
} else if (data.type === 'assistant' && data.message?.content) {
|
|
361
362
|
const content = Array.isArray(data.message.content) ? data.message.content : [data.message.content];
|
|
362
363
|
for (const item of content) {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { sanitizeForPublication, getSanitizationStats } from './token-sanitization.lib.mjs';
|
|
25
25
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): caller passes $ already wrapped through wrapDollarWithGhRetry
|
|
26
|
+
import { quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Determine the bot's gh login name. The function returns null on any error
|
|
@@ -33,7 +34,7 @@ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-l
|
|
|
33
34
|
*/
|
|
34
35
|
const detectBotLogin = async $ => {
|
|
35
36
|
try {
|
|
36
|
-
const result = await
|
|
37
|
+
const result = await quietProbe($)`gh api user --jq .login`;
|
|
37
38
|
if (result && result.code === 0 && result.stdout) {
|
|
38
39
|
const login = result.stdout.toString().trim();
|
|
39
40
|
return login || null;
|
|
@@ -61,7 +62,7 @@ export const sweepPrConversationComments = async ({ $, owner, repo, prNumber, bo
|
|
|
61
62
|
const stats = { scanned: 0, edited: 0, errors: 0 };
|
|
62
63
|
let response;
|
|
63
64
|
try {
|
|
64
|
-
response = await
|
|
65
|
+
response = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`;
|
|
65
66
|
} catch (err) {
|
|
66
67
|
await log(`⚠️ post-finish sweep: failed to list comments: ${err.message || err}`);
|
|
67
68
|
stats.errors++;
|
|
@@ -120,7 +121,7 @@ export const sweepPrDescription = async ({ $, owner, repo, prNumber, log = async
|
|
|
120
121
|
const stats = { scanned: 0, edited: 0, errors: 0 };
|
|
121
122
|
let response;
|
|
122
123
|
try {
|
|
123
|
-
response = await
|
|
124
|
+
response = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
124
125
|
} catch (err) {
|
|
125
126
|
await log(`⚠️ post-finish sweep: failed to fetch PR ${prNumber}: ${err.message || err}`);
|
|
126
127
|
stats.errors++;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Read-only probes that must not be mirrored into the attached log.
|
|
4
|
+
*
|
|
5
|
+
* Issue #2130 — the solver mirrors every child process's output to stdout, and
|
|
6
|
+
* `src/lib.mjs` copies stdout into the log file that is later attached to the
|
|
7
|
+
* pull request. That is the right default for the AI tool's own output, but it
|
|
8
|
+
* also dumped the raw answers of the solver's internal probes between its own
|
|
9
|
+
* sentences, where they read as unexplained output or as errors:
|
|
10
|
+
*
|
|
11
|
+
* - `gh api repos/OWNER/REPO/pulls/N` — a ~33 KB JSON object, once per watch
|
|
12
|
+
* iteration (12 copies in the log captured for this issue);
|
|
13
|
+
* - `gh api .../comments --paginate` — up to 46 KB per restart;
|
|
14
|
+
* - `gh auth status --show-token` — a live credential in clear text;
|
|
15
|
+
* - bare one-word answers such as a login, `public`, `I_kwDO...`, or a commit
|
|
16
|
+
* SHA, with nothing around them to say what asked the question.
|
|
17
|
+
*
|
|
18
|
+
* Every one of those call sites already reports what it learned in words, so
|
|
19
|
+
* the raw output is captured for the caller but never mirrored.
|
|
20
|
+
*
|
|
21
|
+
* @module quiet-probe
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* command-stream options for a probe whose raw output stays out of the log.
|
|
26
|
+
* `capture: true` keeps `result.stdout` available to the caller.
|
|
27
|
+
*
|
|
28
|
+
* const result = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
29
|
+
*/
|
|
30
|
+
export const QUIET_PROBE = Object.freeze({ mirror: false, capture: true });
|
|
31
|
+
|
|
32
|
+
const quietProbeCache = new WeakMap();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Bind {@link QUIET_PROBE} to a `$` that arrived as a function argument,
|
|
36
|
+
* degrading to the tag itself when that `$` does not implement command-stream's
|
|
37
|
+
* options-call form.
|
|
38
|
+
*
|
|
39
|
+
* Many helpers take `$` as an injected parameter, and the doubles supplied by
|
|
40
|
+
* callers (tests, in particular) are plain tagged templates that throw on
|
|
41
|
+
* `$({ ... })`. Suppressing mirrored output is a readability improvement, never
|
|
42
|
+
* a correctness requirement, so a `$` that cannot be configured is used as-is
|
|
43
|
+
* rather than turned into a crash.
|
|
44
|
+
*
|
|
45
|
+
* const result = await quietProbe($)`gh api user --jq .login`;
|
|
46
|
+
*
|
|
47
|
+
* @param {Function} dollar - a command-stream `$` or a tagged-template double.
|
|
48
|
+
* @returns {Function} `dollar` bound to the quiet options, or `dollar` itself.
|
|
49
|
+
*/
|
|
50
|
+
export const quietProbe = dollar => {
|
|
51
|
+
if (typeof dollar !== 'function') return dollar;
|
|
52
|
+
const cached = quietProbeCache.get(dollar);
|
|
53
|
+
if (cached) return cached;
|
|
54
|
+
let bound = dollar;
|
|
55
|
+
try {
|
|
56
|
+
const candidate = dollar(QUIET_PROBE);
|
|
57
|
+
// A tag that ignores the options call may return a promise for a command it
|
|
58
|
+
// never should have run; swallow it so it cannot surface as an unhandled
|
|
59
|
+
// rejection, and keep the original tag.
|
|
60
|
+
if (typeof candidate?.catch === 'function') candidate.catch(() => {});
|
|
61
|
+
if (typeof candidate === 'function') bound = candidate;
|
|
62
|
+
} catch {
|
|
63
|
+
/* not option-callable - use the tag unchanged */
|
|
64
|
+
}
|
|
65
|
+
quietProbeCache.set(dollar, bound);
|
|
66
|
+
return bound;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
QUIET_PROBE,
|
|
71
|
+
quietProbe,
|
|
72
|
+
};
|
package/src/qwen.lib.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { timeouts, retryLimits } from './config.lib.mjs';
|
|
|
19
19
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
20
20
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
21
21
|
import { qwenModels, defaultModels, isFormalAiModel } from './models/index.mjs';
|
|
22
|
-
import { logPreparedToolCommand,
|
|
22
|
+
import { buildFormalAiEnvExports, isPrepareOnly, logPreparedToolCommand, resolveFormalAiToolExecution } from './formal-ai.lib.mjs';
|
|
23
23
|
import { buildFormalAiPricingInfo } from './formal-ai-pricing.lib.mjs'; // Issue #2119
|
|
24
24
|
import { checkPlaywrightMcpPackageAvailability } from './playwright-mcp.lib.mjs';
|
|
25
25
|
import { classifyRetryableError, prepareRetryAfterError, waitWithCountdown } from './tool-retry.lib.mjs';
|
|
@@ -503,15 +503,15 @@ export const executeQwenCommand = async params => {
|
|
|
503
503
|
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
504
504
|
|
|
505
505
|
const mappedModel = mapModelToId(argv.model || defaultModels.qwen);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
toolPath: qwenPath,
|
|
510
|
-
});
|
|
506
|
+
// Issue #2130: Formal AI runs the native CLI against a local Formal AI server (no argv wrapper).
|
|
507
|
+
const toolInvocation = await resolveFormalAiToolExecution({ tool: 'qwen', model: argv.model || defaultModels.qwen, toolPath: qwenPath, workdir: tempDir, log, verbose: argv.verbose, prepareOnly: isPrepareOnly(argv) });
|
|
508
|
+
const qwenEnv = { ...process.env, ...toolInvocation.env };
|
|
511
509
|
const resumeSession = argv.resume || null;
|
|
512
510
|
const resumeArgs = resumeSession ? ` --resume ${shellQuote(resumeSession)}` : '';
|
|
513
511
|
const appendSystemPromptArg = systemPrompt ? ` --append-system-prompt "$(cat ${shellQuote(systemPromptFile)})"` : '';
|
|
514
|
-
|
|
512
|
+
// Issue #2130: re-export the Formal AI environment inside the `sh -lc` script so a
|
|
513
|
+
// stale `formal-ai with --global` block in the operator profile cannot override it.
|
|
514
|
+
const commandScript = `${buildFormalAiEnvExports(toolInvocation.env)}cd ${shellQuote(tempDir)} && ${toolInvocation.displayCommand} --model ${shellQuote(mappedModel)} --output-format stream-json --yolo${resumeArgs}${appendSystemPromptArg} --prompt "$(cat ${shellQuote(promptFile)})"`;
|
|
515
515
|
const fullCommand = `(cd "${tempDir}" && ${toolInvocation.displayCommand} --model "${mappedModel}" --output-format stream-json --yolo${resumeSession ? ` --resume "${resumeSession}"` : ''}${systemPrompt ? ` --append-system-prompt "$(cat "${systemPromptFile}")"` : ''} --prompt "$(cat "${promptFile}")")`;
|
|
516
516
|
|
|
517
517
|
const preparedResult = await logPreparedToolCommand({ argv, fullCommand, log, formatAligned });
|
|
@@ -521,6 +521,7 @@ export const executeQwenCommand = async params => {
|
|
|
521
521
|
const execCommand = dollar({
|
|
522
522
|
cwd: tempDir,
|
|
523
523
|
mirror: false,
|
|
524
|
+
env: qwenEnv,
|
|
524
525
|
})`sh -lc ${commandScript}`;
|
|
525
526
|
|
|
526
527
|
await log(`${formatAligned('📋', 'Command details:', '')}`);
|
|
@@ -16,6 +16,7 @@ const use = globalThis.use;
|
|
|
16
16
|
// Use command-stream for consistent $ behavior across runtimes
|
|
17
17
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
18
18
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
19
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
19
20
|
const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
20
21
|
// Import shared library functions
|
|
21
22
|
const lib = await import('./lib.mjs');
|
|
@@ -522,7 +523,7 @@ export const processAutoContinueForIssue = async (argv, isIssueUrl, urlNumber, o
|
|
|
522
523
|
// When in fork mode, check for existing branches in the fork
|
|
523
524
|
try {
|
|
524
525
|
// Get current user to determine fork name
|
|
525
|
-
const userResult = await
|
|
526
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
526
527
|
if (userResult.code === 0) {
|
|
527
528
|
const currentUser = userResult.stdout.toString().trim();
|
|
528
529
|
// Determine fork name based on --prefix-fork-name-with-owner-name option
|
|
@@ -10,6 +10,7 @@ import { emitForkAwareDiagnostic } from './solve.auto-pr-fork-diagnostic.lib.mjs
|
|
|
10
10
|
import { handleCompareApiNotReady } from './solve.auto-pr-compare-readiness.lib.mjs'; // Issue #1829: decides whether a failed compare-API readiness poll is fatal (fork mismatch / 0 commits) or a transient diff-render failure to degrade past.
|
|
11
11
|
|
|
12
12
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry, execGhWithRetry, isTransientCompareApiError } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller. Issue #1756: execGhWithRetry retries on transient 5xx (504) too. Issue #1829: isTransientCompareApiError lets the compare-API readiness gate degrade gracefully on transient diff-render failures.
|
|
13
|
+
import { quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
13
14
|
import { stagePlaceholderFileOrExplain, explainNothingStagedAndThrow } from './solve.auto-pr-placeholder.lib.mjs'; // Issue #1825: handles the seed placeholder when the target repo gitignores it.
|
|
14
15
|
import { sanitizeForPublication, writeSanitizedPublicationFile } from './token-sanitization.lib.mjs';
|
|
15
16
|
|
|
@@ -432,7 +433,7 @@ Proceed.
|
|
|
432
433
|
// Determine fork name based on --prefix-fork-name-with-owner-name option
|
|
433
434
|
const forkRepoName = argv.prefixForkNameWithOwnerName ? `${owner}-${repo}` : repo;
|
|
434
435
|
try {
|
|
435
|
-
const userResult = await
|
|
436
|
+
const userResult = await quietProbe($)`gh api user --jq .login`;
|
|
436
437
|
if (userResult.code === 0) {
|
|
437
438
|
currentUser = userResult.stdout.toString().trim();
|
|
438
439
|
const userForkName = `${currentUser}/${forkRepoName}`;
|
|
@@ -1131,9 +1132,14 @@ ${prBody}`,
|
|
|
1131
1132
|
// the queries are assembled in JS and passed as one argument.
|
|
1132
1133
|
const repositorySelector = `repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(repo)})`;
|
|
1133
1134
|
|
|
1134
|
-
// First, get the node IDs for both the issue and the PR
|
|
1135
|
+
// First, get the node IDs for both the issue and the PR.
|
|
1136
|
+
// Issue #2130: these three reads are quiet. Their bare answers
|
|
1137
|
+
// ("I_kwDO...", "PR_kwDO...", "1") were mirrored into the
|
|
1138
|
+
// attached log with nothing around them; the node IDs are
|
|
1139
|
+
// re-logged below under --verbose and the link result is
|
|
1140
|
+
// reported in words either way.
|
|
1135
1141
|
const issueNodeQuery = `query { ${repositorySelector} { issue(number: ${issueNumber}) { id } } }`;
|
|
1136
|
-
const issueNodeResult = await
|
|
1142
|
+
const issueNodeResult = await quietProbe($)`gh api graphql -f query=${issueNodeQuery} --jq .data.repository.issue.id`;
|
|
1137
1143
|
|
|
1138
1144
|
if (issueNodeResult.code !== 0) {
|
|
1139
1145
|
throw new Error(`Failed to get issue node ID: ${issueNodeResult.stderr}`);
|
|
@@ -1143,7 +1149,7 @@ ${prBody}`,
|
|
|
1143
1149
|
await log(` Issue node ID: ${issueNodeId}`, { verbose: true });
|
|
1144
1150
|
|
|
1145
1151
|
const prNodeQuery = `query { ${repositorySelector} { pullRequest(number: ${localPrNumber}) { id } } }`;
|
|
1146
|
-
const prNodeResult = await
|
|
1152
|
+
const prNodeResult = await quietProbe($)`gh api graphql -f query=${prNodeQuery} --jq .data.repository.pullRequest.id`;
|
|
1147
1153
|
|
|
1148
1154
|
if (prNodeResult.code !== 0) {
|
|
1149
1155
|
throw new Error(`Failed to get PR node ID: ${prNodeResult.stderr}`);
|
|
@@ -1160,7 +1166,7 @@ ${prBody}`,
|
|
|
1160
1166
|
|
|
1161
1167
|
// Let's verify the link was created
|
|
1162
1168
|
const linkCheckQuery = `query { ${repositorySelector} { pullRequest(number: ${localPrNumber}) { closingIssuesReferences(first: 10) { nodes { number } } } } }`;
|
|
1163
|
-
const linkCheckResult = await
|
|
1169
|
+
const linkCheckResult = await quietProbe($)`gh api graphql -f query=${linkCheckQuery} --jq '.data.repository.pullRequest.closingIssuesReferences.nodes[].number'`;
|
|
1164
1170
|
|
|
1165
1171
|
if (linkCheckResult.code === 0) {
|
|
1166
1172
|
const linkedIssues = parseClosingIssueNumbers(linkCheckResult.stdout);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { reportError } from './sentry.lib.mjs';
|
|
11
11
|
|
|
12
12
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
13
|
+
import { quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
13
14
|
export async function handleBranchCheckoutError({ branchName, prNumber, errorOutput, issueUrl, owner, repo, tempDir, argv, formatAligned, log, $ }) {
|
|
14
15
|
// Check if this is a PR from a fork
|
|
15
16
|
let isForkPR = false;
|
|
@@ -67,7 +68,7 @@ export async function handleBranchCheckoutError({ branchName, prNumber, errorOut
|
|
|
67
68
|
|
|
68
69
|
// Check if the current user has a fork of this repository
|
|
69
70
|
try {
|
|
70
|
-
const userResult = await
|
|
71
|
+
const userResult = await quietProbe($)`gh api user --jq .login`;
|
|
71
72
|
if (userResult.code === 0) {
|
|
72
73
|
const currentUser = userResult.stdout.toString().trim();
|
|
73
74
|
// Determine fork name based on --prefix-fork-name-with-owner-name option
|
|
@@ -15,6 +15,7 @@ const use = globalThis.use;
|
|
|
15
15
|
// Use command-stream for consistent $ behavior across runtimes
|
|
16
16
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
17
17
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
18
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
18
19
|
const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
19
20
|
const os = (await use('os')).default;
|
|
20
21
|
const path = (await use('path')).default;
|
|
@@ -86,7 +87,7 @@ export const setupRepository = async (argv, owner, repo) => {
|
|
|
86
87
|
await log(`${formatAligned('', 'Checking fork status...', '')}\n`);
|
|
87
88
|
|
|
88
89
|
// Get current user
|
|
89
|
-
const userResult = await
|
|
90
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
90
91
|
if (userResult.code !== 0) {
|
|
91
92
|
await log(`${formatAligned('❌', 'Error:', 'Failed to get current user')}`);
|
|
92
93
|
process.exit(1);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { reportError } from './sentry.lib.mjs';
|
|
8
8
|
|
|
9
9
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
10
|
+
import { QUIET_PROBE, quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
10
11
|
// Issue #1827: tool-generated comments (markers + in-memory tracked IDs) must
|
|
11
12
|
// not count as feedback in watch/continue mode, mirroring checkForNonBotComments.
|
|
12
13
|
import { isToolGeneratedComment, isToolTrackedCommentId } from './tool-comments.lib.mjs';
|
|
@@ -22,7 +23,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
22
23
|
|
|
23
24
|
// Get current GitHub user to filter out own comments
|
|
24
25
|
try {
|
|
25
|
-
const userResult = await
|
|
26
|
+
const userResult = await quietProbe($)`gh api user --jq .login`;
|
|
26
27
|
if (userResult.code === 0) {
|
|
27
28
|
currentUser = userResult.stdout.toString().trim();
|
|
28
29
|
await log(formatAligned('👤', 'Current user:', currentUser, 2));
|
|
@@ -63,7 +64,11 @@ export const detectAndCountFeedback = async params => {
|
|
|
63
64
|
|
|
64
65
|
// Get the last commit timestamp from the PR branch
|
|
65
66
|
let lastCommitTime = null;
|
|
66
|
-
|
|
67
|
+
// Issue #2130: quiet. These probes answer "when was the last commit?", and
|
|
68
|
+
// the answer is logged in words below; mirroring them put bare ISO dates
|
|
69
|
+
// and `git log`'s "unknown revision" complaint - the expected outcome for
|
|
70
|
+
// a branch that has never been pushed - into the attached log.
|
|
71
|
+
const git$ = repositoryPath ? $({ cwd: repositoryPath, ...QUIET_PROBE }) : quietProbe($);
|
|
67
72
|
let lastCommitResult = await git$`git log -1 --format="%aI" origin/${branchName}`;
|
|
68
73
|
if (lastCommitResult.code !== 0) {
|
|
69
74
|
// Fallback to local branch if remote doesn't exist
|
|
@@ -76,7 +81,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
76
81
|
} else {
|
|
77
82
|
// Fallback: Get last commit time from GitHub API
|
|
78
83
|
try {
|
|
79
|
-
const prCommitsResult = await
|
|
84
|
+
const prCommitsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/commits --paginate --jq 'last.commit.author.date'`;
|
|
80
85
|
if (prCommitsResult.code === 0 && prCommitsResult.stdout) {
|
|
81
86
|
lastCommitTime = new Date(prCommitsResult.stdout.toString().trim());
|
|
82
87
|
await log(formatAligned('📅', 'Last commit time (from API):', lastCommitTime.toISOString(), 2));
|
|
@@ -109,14 +114,14 @@ export const detectAndCountFeedback = async params => {
|
|
|
109
114
|
let prConversationComments = [];
|
|
110
115
|
|
|
111
116
|
// Get PR code review comments (use --paginate to get all comments, not just first page)
|
|
112
|
-
const prReviewCommentsResult = await
|
|
117
|
+
const prReviewCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate`;
|
|
113
118
|
if (prReviewCommentsResult.code === 0) {
|
|
114
119
|
prReviewComments = JSON.parse(prReviewCommentsResult.stdout.toString());
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
// Get PR conversation comments (PR is also an issue)
|
|
118
123
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
119
|
-
const prConversationCommentsResult = await
|
|
124
|
+
const prConversationCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`;
|
|
120
125
|
if (prConversationCommentsResult.code === 0) {
|
|
121
126
|
prConversationComments = JSON.parse(prConversationCommentsResult.stdout.toString());
|
|
122
127
|
}
|
|
@@ -156,7 +161,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
156
161
|
|
|
157
162
|
// Count new issue comments after last commit
|
|
158
163
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
159
|
-
const issueCommentsResult = await
|
|
164
|
+
const issueCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
160
165
|
if (issueCommentsResult.code === 0) {
|
|
161
166
|
const issueComments = JSON.parse(issueCommentsResult.stdout.toString());
|
|
162
167
|
const filteredIssueComments = issueComments.filter(comment => {
|
|
@@ -242,7 +247,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
242
247
|
// started) should be considered feedback.
|
|
243
248
|
try {
|
|
244
249
|
// Check PR description edit time
|
|
245
|
-
const prDetailsResult = await
|
|
250
|
+
const prDetailsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
246
251
|
if (prDetailsResult.code === 0) {
|
|
247
252
|
const prDetails = JSON.parse(prDetailsResult.stdout.toString());
|
|
248
253
|
const prUpdatedAt = new Date(prDetails.updated_at);
|
|
@@ -265,7 +270,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
265
270
|
|
|
266
271
|
// Check issue description edit time if we have an issue
|
|
267
272
|
if (issueNumber) {
|
|
268
|
-
const issueDetailsResult = await
|
|
273
|
+
const issueDetailsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${issueNumber}`;
|
|
269
274
|
if (issueDetailsResult.code === 0) {
|
|
270
275
|
const issueDetails = JSON.parse(issueDetailsResult.stdout.toString());
|
|
271
276
|
const issueUpdatedAt = new Date(issueDetails.updated_at);
|
|
@@ -300,12 +305,12 @@ export const detectAndCountFeedback = async params => {
|
|
|
300
305
|
|
|
301
306
|
// 3. Check for new commits on default branch
|
|
302
307
|
try {
|
|
303
|
-
const defaultBranchResult = await
|
|
308
|
+
const defaultBranchResult = await quietProbe($)`gh api repos/${owner}/${repo}`;
|
|
304
309
|
if (defaultBranchResult.code === 0) {
|
|
305
310
|
const repoData = JSON.parse(defaultBranchResult.stdout.toString());
|
|
306
311
|
const defaultBranch = repoData.default_branch;
|
|
307
312
|
|
|
308
|
-
const commitsResult = await
|
|
313
|
+
const commitsResult = await quietProbe($)`gh api repos/${owner}/${repo}/commits --paginate --field sha=${defaultBranch} --field since=${lastCommitTime.toISOString()}`;
|
|
309
314
|
if (commitsResult.code === 0) {
|
|
310
315
|
const commits = JSON.parse(commitsResult.stdout.toString());
|
|
311
316
|
if (commits.length > 0) {
|
|
@@ -353,10 +358,10 @@ export const detectAndCountFeedback = async params => {
|
|
|
353
358
|
|
|
354
359
|
// 6. Check for failed PR checks
|
|
355
360
|
try {
|
|
356
|
-
const prHeadResult = await
|
|
361
|
+
const prHeadResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.head.sha'`;
|
|
357
362
|
if (prHeadResult.code === 0) {
|
|
358
363
|
const prHeadSha = prHeadResult.stdout.toString().trim();
|
|
359
|
-
const checksResult = await
|
|
364
|
+
const checksResult = await quietProbe($)`gh api repos/${owner}/${repo}/commits/${prHeadSha}/check-runs --paginate --slurp`;
|
|
360
365
|
const checkRuns = checksResult.code === 0 ? JSON.parse(checksResult.stdout.toString() || '[]').flatMap(page => page.check_runs || []) : [];
|
|
361
366
|
const failedChecks = checkRuns.filter(check => check.conclusion === 'failure' && new Date(check.completed_at) > lastCommitTime);
|
|
362
367
|
|
|
@@ -379,7 +384,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
379
384
|
|
|
380
385
|
// 7. Check for review requests with changes requested
|
|
381
386
|
try {
|
|
382
|
-
const reviewsResult = await
|
|
387
|
+
const reviewsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews --paginate`;
|
|
383
388
|
if (reviewsResult.code === 0) {
|
|
384
389
|
const reviews = JSON.parse(reviewsResult.stdout.toString());
|
|
385
390
|
const changesRequestedReviews = reviews.filter(review => review.state === 'CHANGES_REQUESTED' && new Date(review.submitted_at) > lastCommitTime);
|