@link-assistant/hive-mind 1.2.11 → 1.4.0
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 +28 -1
- package/package.json +1 -1
- package/src/git.lib.mjs +198 -0
- package/src/solve.auto-pr.lib.mjs +21 -14
- package/src/solve.config.lib.mjs +11 -1
- package/src/solve.validation.lib.mjs +75 -0
- package/src/solve.watch.lib.mjs +54 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4a476ae: Add separate log comment for each auto-restart session with cost estimation
|
|
8
|
+
- Each auto-restart iteration now uploads its own session log with cost estimation to the PR
|
|
9
|
+
- Log comments use "Auto-restart X/Y Log" format instead of generic "Solution Draft Log"
|
|
10
|
+
- Issue #1107
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 3239fa1: Add git identity validation to prevent commit failures
|
|
15
|
+
- Added `checkGitIdentity()` and `validateGitIdentity()` functions to validate git user configuration
|
|
16
|
+
- Added git identity check to `performSystemChecks()` that runs before any work begins
|
|
17
|
+
- Added `--auto-gh-configuration-repair` option that uses external `gh-setup-git-identity` command for automatic repair
|
|
18
|
+
- Added unit tests for identity validation
|
|
19
|
+
|
|
20
|
+
This fix prevents the "fatal: empty ident name" error that occurs when git user.name and user.email are not configured. When git identity is missing, users now see a clear error message with instructions for fixing it. The auto-repair feature requires the external [gh-setup-git-identity](https://github.com/link-foundation/gh-setup-git-identity) package to be installed.
|
|
21
|
+
|
|
22
|
+
## 1.3.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- a403c0e: Add --auto-gitkeep-file option to automatically fallback to .gitkeep when CLAUDE.md is in .gitignore
|
|
27
|
+
|
|
28
|
+
This feature pre-checks if CLAUDE.md would be ignored by .gitignore BEFORE creating the file, preventing the "paths are ignored by one of your .gitignore files" error. When detected, automatically switches to .gitkeep mode. Enabled by default (--auto-gitkeep-file=true).
|
|
29
|
+
|
|
3
30
|
## 1.2.11
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
|
@@ -952,7 +979,7 @@
|
|
|
952
979
|
|
|
953
980
|
This feature allows users to choose which file type to use for PR creation:
|
|
954
981
|
- `--claude-file` (default: true): Use CLAUDE.md file for task details
|
|
955
|
-
- `--gitkeep-file` (default: false
|
|
982
|
+
- `--gitkeep-file` (default: false): Use .gitkeep file instead
|
|
956
983
|
|
|
957
984
|
The flags are mutually exclusive:
|
|
958
985
|
- Using `--gitkeep-file` automatically disables `--claude-file`
|
package/package.json
CHANGED
package/src/git.lib.mjs
CHANGED
|
@@ -134,6 +134,201 @@ export const getGitVersionAsync = async ($, currentVersion) => {
|
|
|
134
134
|
return currentVersion;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Validates git user identity configuration
|
|
139
|
+
* Returns an object with validation status and identity info
|
|
140
|
+
*
|
|
141
|
+
* Git commits require both user.name and user.email to be set.
|
|
142
|
+
* This function checks both global (~/.gitconfig) and local (.git/config) configurations.
|
|
143
|
+
*
|
|
144
|
+
* See: https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup
|
|
145
|
+
* Related error: "fatal: empty ident name (for <>) not allowed"
|
|
146
|
+
*
|
|
147
|
+
* @param {function} execFunc - The exec function to use (for testing)
|
|
148
|
+
* @returns {Promise<{isValid: boolean, name: string|null, email: string|null, scope: string|null, error: string|null}>}
|
|
149
|
+
*/
|
|
150
|
+
export const checkGitIdentity = async (execFunc = execAsync) => {
|
|
151
|
+
const result = {
|
|
152
|
+
isValid: false,
|
|
153
|
+
name: null,
|
|
154
|
+
email: null,
|
|
155
|
+
scope: null, // 'global', 'local', or 'none'
|
|
156
|
+
error: null,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Check for user.name
|
|
161
|
+
try {
|
|
162
|
+
const { stdout: nameStdout } = await execFunc('git config user.name', {
|
|
163
|
+
encoding: 'utf8',
|
|
164
|
+
env: process.env,
|
|
165
|
+
});
|
|
166
|
+
result.name = nameStdout.trim() || null;
|
|
167
|
+
} catch {
|
|
168
|
+
// user.name not set
|
|
169
|
+
result.name = null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check for user.email
|
|
173
|
+
try {
|
|
174
|
+
const { stdout: emailStdout } = await execFunc('git config user.email', {
|
|
175
|
+
encoding: 'utf8',
|
|
176
|
+
env: process.env,
|
|
177
|
+
});
|
|
178
|
+
result.email = emailStdout.trim() || null;
|
|
179
|
+
} catch {
|
|
180
|
+
// user.email not set
|
|
181
|
+
result.email = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Determine scope (check if local config exists)
|
|
185
|
+
if (result.name || result.email) {
|
|
186
|
+
try {
|
|
187
|
+
const { stdout: scopeStdout } = await execFunc('git config --show-origin user.name', {
|
|
188
|
+
encoding: 'utf8',
|
|
189
|
+
env: process.env,
|
|
190
|
+
});
|
|
191
|
+
// Output format: "file:/path/to/config\tvalue"
|
|
192
|
+
if (scopeStdout.includes('.git/config')) {
|
|
193
|
+
result.scope = 'local';
|
|
194
|
+
} else if (scopeStdout.includes('.gitconfig') || scopeStdout.includes('/etc/gitconfig')) {
|
|
195
|
+
result.scope = 'global';
|
|
196
|
+
} else {
|
|
197
|
+
result.scope = 'global';
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
result.scope = 'none';
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
result.scope = 'none';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Both name and email must be non-empty for valid git identity
|
|
207
|
+
// Empty string is also invalid (git rejects it)
|
|
208
|
+
result.isValid = !!(result.name && result.name.length > 0 && result.email && result.email.length > 0);
|
|
209
|
+
|
|
210
|
+
if (!result.isValid) {
|
|
211
|
+
const missing = [];
|
|
212
|
+
if (!result.name || result.name.length === 0) missing.push('user.name');
|
|
213
|
+
if (!result.email || result.email.length === 0) missing.push('user.email');
|
|
214
|
+
result.error = `Git identity incomplete: missing ${missing.join(' and ')}`;
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
result.error = `Failed to check git identity: ${error.message}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Validates git user identity and returns detailed error message if invalid
|
|
225
|
+
* Uses zx's $ for async execution
|
|
226
|
+
*
|
|
227
|
+
* @param {function} $ - The zx $ function
|
|
228
|
+
* @param {object} options - Options object
|
|
229
|
+
* @param {function} options.log - Log function for output
|
|
230
|
+
* @returns {Promise<boolean>} - True if identity is valid, false otherwise
|
|
231
|
+
*/
|
|
232
|
+
export const validateGitIdentity = async ($, options = {}) => {
|
|
233
|
+
const { log = console.log } = options;
|
|
234
|
+
|
|
235
|
+
// Check user.name
|
|
236
|
+
let userName = null;
|
|
237
|
+
try {
|
|
238
|
+
const nameResult = await $`git config user.name 2>/dev/null || true`;
|
|
239
|
+
userName = nameResult.stdout.toString().trim() || null;
|
|
240
|
+
} catch {
|
|
241
|
+
userName = null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check user.email
|
|
245
|
+
let userEmail = null;
|
|
246
|
+
try {
|
|
247
|
+
const emailResult = await $`git config user.email 2>/dev/null || true`;
|
|
248
|
+
userEmail = emailResult.stdout.toString().trim() || null;
|
|
249
|
+
} catch {
|
|
250
|
+
userEmail = null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Both must be set and non-empty
|
|
254
|
+
const isValid = !!(userName && userName.length > 0 && userEmail && userEmail.length > 0);
|
|
255
|
+
|
|
256
|
+
if (!isValid) {
|
|
257
|
+
const missing = [];
|
|
258
|
+
if (!userName || userName.length === 0) missing.push('user.name');
|
|
259
|
+
if (!userEmail || userEmail.length === 0) missing.push('user.email');
|
|
260
|
+
|
|
261
|
+
await log('');
|
|
262
|
+
await log('❌ Git identity not configured', { level: 'error' });
|
|
263
|
+
await log('');
|
|
264
|
+
await log(' Git commits require both user.name and user.email to be set.');
|
|
265
|
+
await log(` Missing: ${missing.join(' and ')}`);
|
|
266
|
+
await log('');
|
|
267
|
+
await log(' Current configuration:');
|
|
268
|
+
await log(` user.name: ${userName || '(not set)'}`);
|
|
269
|
+
await log(` user.email: ${userEmail || '(not set)'}`);
|
|
270
|
+
await log('');
|
|
271
|
+
await log(' 🔧 How to fix:');
|
|
272
|
+
await log('');
|
|
273
|
+
await log(' Option 1: Use GitHub CLI to set identity from your account');
|
|
274
|
+
await log(' gh-setup-git-identity');
|
|
275
|
+
await log('');
|
|
276
|
+
await log(' Option 2: Set identity manually');
|
|
277
|
+
await log(' git config --global user.name "Your Name"');
|
|
278
|
+
await log(' git config --global user.email "you@example.com"');
|
|
279
|
+
await log('');
|
|
280
|
+
await log(' Related error: "fatal: empty ident name (for <>) not allowed"');
|
|
281
|
+
await log('');
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return true;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Attempts to repair git identity using gh-setup-git-identity --repair
|
|
290
|
+
* This function requires gh-setup-git-identity to be installed.
|
|
291
|
+
*
|
|
292
|
+
* @param {function} execFunc - The exec function to use (for testing)
|
|
293
|
+
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
294
|
+
*/
|
|
295
|
+
export const repairGitIdentity = async (execFunc = execAsync) => {
|
|
296
|
+
const result = {
|
|
297
|
+
success: false,
|
|
298
|
+
error: null,
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
// First check if gh-setup-git-identity is installed
|
|
303
|
+
try {
|
|
304
|
+
await execFunc('which gh-setup-git-identity', {
|
|
305
|
+
encoding: 'utf8',
|
|
306
|
+
});
|
|
307
|
+
} catch {
|
|
308
|
+
result.error = 'gh-setup-git-identity is not installed. Please install it first or fix git identity manually.';
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Run gh-setup-git-identity --repair
|
|
313
|
+
await execFunc('gh-setup-git-identity --repair', {
|
|
314
|
+
encoding: 'utf8',
|
|
315
|
+
env: process.env,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Check if the repair was successful by validating git identity
|
|
319
|
+
const identityCheck = await checkGitIdentity(execFunc);
|
|
320
|
+
if (identityCheck.isValid) {
|
|
321
|
+
result.success = true;
|
|
322
|
+
} else {
|
|
323
|
+
result.error = `Repair command completed but identity is still invalid: ${identityCheck.error}`;
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
result.error = `Failed to repair git identity: ${error.message}`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return result;
|
|
330
|
+
};
|
|
331
|
+
|
|
137
332
|
// Export all functions as default as well
|
|
138
333
|
export default {
|
|
139
334
|
isGitRepository,
|
|
@@ -142,4 +337,7 @@ export default {
|
|
|
142
337
|
getCommitSha,
|
|
143
338
|
getGitVersion,
|
|
144
339
|
getGitVersionAsync,
|
|
340
|
+
checkGitIdentity,
|
|
341
|
+
validateGitIdentity,
|
|
342
|
+
repairGitIdentity,
|
|
145
343
|
};
|
|
@@ -31,12 +31,20 @@ export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNum
|
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
33
|
// Determine which file to create based on CLI flags
|
|
34
|
-
|
|
35
|
-
const
|
|
34
|
+
let useClaudeFile = argv.claudeFile !== false;
|
|
35
|
+
const useAutoGitkeepFile = argv.autoGitkeepFile !== false;
|
|
36
|
+
|
|
37
|
+
// Pre-check: If CLAUDE.md would be ignored by .gitignore, automatically switch to .gitkeep mode
|
|
38
|
+
if (useClaudeFile && useAutoGitkeepFile) {
|
|
39
|
+
const checkResult = await $({ cwd: tempDir, silent: true })`git check-ignore CLAUDE.md 2>/dev/null`;
|
|
40
|
+
if (checkResult.code === 0) {
|
|
41
|
+
await log(formatAligned('ℹ️', 'Pre-check:', 'CLAUDE.md is in .gitignore, switching to .gitkeep mode\n'));
|
|
42
|
+
useClaudeFile = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
36
45
|
|
|
37
|
-
// Log which mode we're using
|
|
38
46
|
if (argv.verbose) {
|
|
39
|
-
await log(` Using ${useClaudeFile ? 'CLAUDE.md' : '.gitkeep'} mode (--claude-file=${
|
|
47
|
+
await log(` Using ${useClaudeFile ? 'CLAUDE.md' : '.gitkeep'} mode (--claude-file=${argv.claudeFile !== false}, --gitkeep-file=${argv.gitkeepFile === true}, --auto-gitkeep-file=${useAutoGitkeepFile})`, { verbose: true });
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
let filePath;
|
|
@@ -62,14 +70,13 @@ export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNum
|
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
} else {
|
|
65
|
-
//
|
|
66
|
-
|
|
73
|
+
// .gitkeep mode (via explicit --gitkeep-file or auto-gitkeep-file fallback)
|
|
74
|
+
const modeDesc = argv.gitkeepFile === true ? '.gitkeep (explicit --gitkeep-file)' : '.gitkeep (CLAUDE.md is ignored)';
|
|
75
|
+
await log(formatAligned('📝', 'Creating:', modeDesc));
|
|
67
76
|
|
|
68
77
|
filePath = path.join(tempDir, '.gitkeep');
|
|
69
78
|
fileName = '.gitkeep';
|
|
70
|
-
|
|
71
|
-
// .gitkeep files are typically small, no need to check for existing content
|
|
72
|
-
// But we'll check if it exists for proper handling
|
|
79
|
+
// Check if .gitkeep already exists for proper handling
|
|
73
80
|
try {
|
|
74
81
|
existingContent = await fs.readFile(filePath, 'utf8');
|
|
75
82
|
fileExisted = true;
|
|
@@ -125,12 +132,13 @@ Proceed.
|
|
|
125
132
|
finalContent = taskInfo;
|
|
126
133
|
}
|
|
127
134
|
} else {
|
|
128
|
-
// .gitkeep: Use minimal metadata format
|
|
135
|
+
// .gitkeep: Use minimal metadata format (explicit --gitkeep-file or auto-gitkeep-file fallback)
|
|
136
|
+
const creationReason = argv.gitkeepFile === true ? '# This file was created with --gitkeep-file flag' : '# This file was created because CLAUDE.md is in .gitignore (--auto-gitkeep-file=true)';
|
|
129
137
|
const gitkeepContent = `# Auto-generated file for PR creation
|
|
130
138
|
# Issue: ${issueUrl}
|
|
131
139
|
# Branch: ${branchName}
|
|
132
140
|
# Timestamp: ${timestamp}
|
|
133
|
-
|
|
141
|
+
${creationReason}
|
|
134
142
|
# It will be removed when the task is complete`;
|
|
135
143
|
|
|
136
144
|
if (fileExisted && existingContent) {
|
|
@@ -289,9 +297,8 @@ Proceed.
|
|
|
289
297
|
}
|
|
290
298
|
|
|
291
299
|
await log(formatAligned('📝', 'Creating commit:', `With ${commitFileName} file`));
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const fileDesc = commitFileName === 'CLAUDE.md' ? 'CLAUDE.md with task information for AI processing' : `.gitkeep for PR creation (${useGitkeepFile ? 'created with --gitkeep-file flag (experimental)' : 'CLAUDE.md is in .gitignore'})`;
|
|
300
|
+
// Commit message distinguishes between explicit --gitkeep-file and auto-gitkeep-file fallback
|
|
301
|
+
const fileDesc = commitFileName === 'CLAUDE.md' ? 'CLAUDE.md with task information for AI processing' : `.gitkeep for PR creation (${argv.gitkeepFile === true ? 'created with --gitkeep-file flag' : 'CLAUDE.md is in .gitignore'})`;
|
|
295
302
|
const commitMessage = `Initial commit with task details\n\nAdding ${fileDesc}.\nThis file will be removed when the task is complete.\n\nIssue: ${issueUrl}`;
|
|
296
303
|
|
|
297
304
|
// Use explicit cwd option for better reliability
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -130,9 +130,14 @@ export const createYargsConfig = yargsInstance => {
|
|
|
130
130
|
})
|
|
131
131
|
.option('gitkeep-file', {
|
|
132
132
|
type: 'boolean',
|
|
133
|
-
description: 'Create .gitkeep file instead of CLAUDE.md (
|
|
133
|
+
description: 'Create .gitkeep file instead of CLAUDE.md (mutually exclusive with --claude-file)',
|
|
134
134
|
default: false,
|
|
135
135
|
})
|
|
136
|
+
.option('auto-gitkeep-file', {
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
description: 'Automatically use .gitkeep if CLAUDE.md is in .gitignore (pre-checks before creating file)',
|
|
139
|
+
default: true,
|
|
140
|
+
})
|
|
136
141
|
.option('attach-logs', {
|
|
137
142
|
type: 'boolean',
|
|
138
143
|
description: 'Upload the solution draft log file to the Pull Request on completion (⚠️ WARNING: May expose sensitive data)',
|
|
@@ -316,6 +321,11 @@ export const createYargsConfig = yargsInstance => {
|
|
|
316
321
|
description: 'Automatically remove .playwright-mcp/ folder before checking for uncommitted changes. This prevents browser automation artifacts from triggering auto-restart. Use --no-playwright-mcp-auto-cleanup to keep the folder for debugging.',
|
|
317
322
|
default: true,
|
|
318
323
|
})
|
|
324
|
+
.option('auto-gh-configuration-repair', {
|
|
325
|
+
type: 'boolean',
|
|
326
|
+
description: 'Automatically repair git configuration using gh-setup-git-identity --repair when git identity is not configured. Requires gh-setup-git-identity to be installed.',
|
|
327
|
+
default: false,
|
|
328
|
+
})
|
|
319
329
|
.parserConfiguration({
|
|
320
330
|
'boolean-negation': true,
|
|
321
331
|
})
|
|
@@ -33,6 +33,10 @@ const {
|
|
|
33
33
|
// isGitHubUrlType - not currently used
|
|
34
34
|
} = githubLib;
|
|
35
35
|
|
|
36
|
+
// Import git-related functions for identity validation and repair
|
|
37
|
+
const gitLib = await import('./git.lib.mjs');
|
|
38
|
+
const { checkGitIdentity, repairGitIdentity } = gitLib;
|
|
39
|
+
|
|
36
40
|
// Import Claude-related functions
|
|
37
41
|
const claudeLib = await import('./claude.lib.mjs');
|
|
38
42
|
// Import Sentry integration
|
|
@@ -217,6 +221,77 @@ export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnectio
|
|
|
217
221
|
return false;
|
|
218
222
|
}
|
|
219
223
|
|
|
224
|
+
// Check git identity configuration before proceeding
|
|
225
|
+
// This prevents the "fatal: empty ident name" error during commits
|
|
226
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1131
|
|
227
|
+
let gitIdentity = await checkGitIdentity();
|
|
228
|
+
if (!gitIdentity.isValid) {
|
|
229
|
+
// Check if auto-repair is enabled
|
|
230
|
+
if (argv.autoGhConfigurationRepair) {
|
|
231
|
+
await log('');
|
|
232
|
+
await log('⚠️ Git identity not configured, attempting auto-repair...', { level: 'warning' });
|
|
233
|
+
await log(` ${gitIdentity.error || 'Configuration is incomplete'}`);
|
|
234
|
+
await log('');
|
|
235
|
+
|
|
236
|
+
const repairResult = await repairGitIdentity();
|
|
237
|
+
if (repairResult.success) {
|
|
238
|
+
await log('✅ Git identity successfully repaired using gh-setup-git-identity --repair');
|
|
239
|
+
// Re-check identity to display the configured values
|
|
240
|
+
gitIdentity = await checkGitIdentity();
|
|
241
|
+
await log(` user.name: ${gitIdentity.name}`);
|
|
242
|
+
await log(` user.email: ${gitIdentity.email}`);
|
|
243
|
+
await log('');
|
|
244
|
+
} else {
|
|
245
|
+
await log('');
|
|
246
|
+
await log('❌ Auto-repair failed', { level: 'error' });
|
|
247
|
+
await log(` ${repairResult.error}`);
|
|
248
|
+
await log('');
|
|
249
|
+
await log(' Current configuration:');
|
|
250
|
+
await log(` user.name: ${gitIdentity.name || '(not set)'}`);
|
|
251
|
+
await log(` user.email: ${gitIdentity.email || '(not set)'}`);
|
|
252
|
+
await log('');
|
|
253
|
+
await log(' 🔧 How to fix manually:');
|
|
254
|
+
await log('');
|
|
255
|
+
await log(' Option 1: Install gh-setup-git-identity and use --auto-gh-configuration-repair');
|
|
256
|
+
await log(' npm install -g @link-foundation/gh-setup-git-identity');
|
|
257
|
+
await log('');
|
|
258
|
+
await log(' Option 2: Set identity manually');
|
|
259
|
+
await log(' git config --global user.name "Your Name"');
|
|
260
|
+
await log(' git config --global user.email "you@example.com"');
|
|
261
|
+
await log('');
|
|
262
|
+
await log(' Related error: "fatal: empty ident name (for <>) not allowed"');
|
|
263
|
+
await log('');
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
await log('');
|
|
268
|
+
await log('❌ Git identity not configured', { level: 'error' });
|
|
269
|
+
await log('');
|
|
270
|
+
await log(' Git commits require both user.name and user.email to be set.');
|
|
271
|
+
await log(` ${gitIdentity.error || 'Configuration is incomplete'}`);
|
|
272
|
+
await log('');
|
|
273
|
+
await log(' Current configuration:');
|
|
274
|
+
await log(` user.name: ${gitIdentity.name || '(not set)'}`);
|
|
275
|
+
await log(` user.email: ${gitIdentity.email || '(not set)'}`);
|
|
276
|
+
await log('');
|
|
277
|
+
await log(' 🔧 How to fix:');
|
|
278
|
+
await log('');
|
|
279
|
+
await log(' Option 1: Use GitHub CLI to set identity from your account');
|
|
280
|
+
await log(' gh-setup-git-identity');
|
|
281
|
+
await log('');
|
|
282
|
+
await log(' Option 2: Set identity manually');
|
|
283
|
+
await log(' git config --global user.name "Your Name"');
|
|
284
|
+
await log(' git config --global user.email "you@example.com"');
|
|
285
|
+
await log('');
|
|
286
|
+
await log(' Option 3: Enable auto-repair (requires gh-setup-git-identity)');
|
|
287
|
+
await log(' solve <issue-url> --auto-gh-configuration-repair');
|
|
288
|
+
await log('');
|
|
289
|
+
await log(' Related error: "fatal: empty ident name (for <>) not allowed"');
|
|
290
|
+
await log('');
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
220
295
|
// Skip tool connection validation if in dry-run mode or explicitly requested
|
|
221
296
|
if (!skipToolConnection) {
|
|
222
297
|
let isToolConnected = false;
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const fs = (await use('fs')).promises;
|
|
|
21
21
|
|
|
22
22
|
// Import shared library functions
|
|
23
23
|
const lib = await import('./lib.mjs');
|
|
24
|
-
const { log, cleanErrorMessage, formatAligned } = lib;
|
|
24
|
+
const { log, cleanErrorMessage, formatAligned, getLogFile } = lib;
|
|
25
25
|
|
|
26
26
|
// Import feedback detection functions
|
|
27
27
|
const feedbackLib = await import('./solve.feedback.lib.mjs');
|
|
@@ -29,6 +29,10 @@ const feedbackLib = await import('./solve.feedback.lib.mjs');
|
|
|
29
29
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
30
30
|
const { reportError } = sentryLib;
|
|
31
31
|
|
|
32
|
+
// Import GitHub functions for log attachment
|
|
33
|
+
const githubLib = await import('./github.lib.mjs');
|
|
34
|
+
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
35
|
+
|
|
32
36
|
const { detectAndCountFeedback } = feedbackLib;
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -517,6 +521,55 @@ export const watchForFeedback = async params => {
|
|
|
517
521
|
}
|
|
518
522
|
}
|
|
519
523
|
|
|
524
|
+
// Issue #1107: Attach log after each auto-restart session with its own cost estimation
|
|
525
|
+
// This ensures each restart has its own log comment instead of one combined log at the end
|
|
526
|
+
const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
|
|
527
|
+
if (isTemporaryWatch && prNumber && shouldAttachLogs) {
|
|
528
|
+
await log('');
|
|
529
|
+
await log(formatAligned('📎', 'Uploading auto-restart session log...', ''));
|
|
530
|
+
try {
|
|
531
|
+
const logFile = getLogFile();
|
|
532
|
+
if (logFile) {
|
|
533
|
+
// Use "Auto-restart X/Y Log" format as requested in issue #1107
|
|
534
|
+
const customTitle = `🔄 Auto-restart ${autoRestartCount}/${maxAutoRestartIterations} Log`;
|
|
535
|
+
const logUploadSuccess = await attachLogToGitHub({
|
|
536
|
+
logFile,
|
|
537
|
+
targetType: 'pr',
|
|
538
|
+
targetNumber: prNumber,
|
|
539
|
+
owner,
|
|
540
|
+
repo,
|
|
541
|
+
$,
|
|
542
|
+
log,
|
|
543
|
+
sanitizeLogContent,
|
|
544
|
+
verbose: argv.verbose,
|
|
545
|
+
customTitle,
|
|
546
|
+
sessionId: latestSessionId,
|
|
547
|
+
tempDir,
|
|
548
|
+
anthropicTotalCostUSD: latestAnthropicCost,
|
|
549
|
+
// Pass agent tool pricing data when available
|
|
550
|
+
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
551
|
+
pricingInfo: toolResult.pricingInfo,
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
if (logUploadSuccess) {
|
|
555
|
+
await log(formatAligned('', '✅ Auto-restart session log uploaded to PR', '', 2));
|
|
556
|
+
} else {
|
|
557
|
+
await log(formatAligned('', '⚠️ Could not upload auto-restart session log', '', 2));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} catch (logUploadError) {
|
|
561
|
+
reportError(logUploadError, {
|
|
562
|
+
context: 'attach_auto_restart_log',
|
|
563
|
+
prNumber,
|
|
564
|
+
owner,
|
|
565
|
+
repo,
|
|
566
|
+
autoRestartCount,
|
|
567
|
+
operation: 'upload_session_log',
|
|
568
|
+
});
|
|
569
|
+
await log(formatAligned('', `⚠️ Log upload error: ${cleanErrorMessage(logUploadError)}`, '', 2));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
520
573
|
await log('');
|
|
521
574
|
if (isTemporaryWatch) {
|
|
522
575
|
await log(formatAligned('✅', `${argv.tool.toUpperCase()} execution completed:`, 'Checking for remaining changes...'));
|