Skip to content

Commit 580c8d0

Browse files
committed
Update claude.mjs with --green fixes and progress indicators
1 parent 9cae180 commit 580c8d0

File tree

1 file changed

+124
-22
lines changed

1 file changed

+124
-22
lines changed

scripts/claude.mjs

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,29 @@ async function checkClaude() {
132132
* Prepare Claude command arguments.
133133
* Adds --dangerously-skip-permissions by default (Let's get dangerous!)
134134
* unless --no-darkwing flag is used.
135+
* Adds model selection based on --pinky (default) or --the-brain (expensive).
135136
*/
136137
function prepareClaudeArgs(args = [], options = {}) {
137138
const opts = { __proto__: null, ...options }
139+
const claudeArgs = [...args]
140+
138141
// "Let's get dangerous!" - Darkwing Duck.
139142
if (!opts['no-darkwing']) {
140-
return ['--dangerously-skip-permissions', ...args]
143+
claudeArgs.unshift('--dangerously-skip-permissions')
144+
}
145+
146+
// "Gee, Brain, what do you want to do tonight?" - Pinky
147+
// "The same thing we do every night, Pinky - try to take over the world!" - The Brain
148+
if (opts['the-brain']) {
149+
// Use the expensive, powerful model (Claude 3 Opus)
150+
claudeArgs.push('--model', 'claude-3-opus-20240229')
151+
} else if (opts.pinky) {
152+
// Explicitly use the default model (Claude 3.5 Sonnet)
153+
claudeArgs.push('--model', 'claude-3-5-sonnet-20241022')
141154
}
142-
return args
155+
// If neither flag is set, let claude-console use its default
156+
157+
return claudeArgs
143158
}
144159

145160
/**
@@ -158,25 +173,64 @@ function shouldRunParallel(options = {}) {
158173

159174
/**
160175
* Run tasks in parallel with progress tracking.
176+
* NOTE: When running Claude agents in parallel, they must use stdio: 'pipe' to avoid
177+
* conflicting interactive prompts. If agents need user interaction, they would queue
178+
* and block each other. Use --seq flag for sequential execution with full interactivity.
161179
*/
162-
async function runParallel(tasks, description = 'tasks') {
180+
async function runParallel(tasks, description = 'tasks', taskNames = []) {
163181
log.info(`Running ${tasks.length} ${description} in parallel...`)
164182

165-
const results = await Promise.allSettled(tasks)
183+
const startTime = Date.now()
184+
let completed = 0
185+
186+
// Add progress tracking to each task
187+
const trackedTasks = tasks.map((task, index) => {
188+
const name = taskNames[index] || `Task ${index + 1}`
189+
const taskStartTime = Date.now()
190+
191+
return task.then(
192+
result => {
193+
completed++
194+
const elapsed = Math.round((Date.now() - taskStartTime) / 1000)
195+
log.done(`[${name}] Completed (${elapsed}s) - ${completed}/${tasks.length}`)
196+
return result
197+
},
198+
error => {
199+
completed++
200+
const elapsed = Math.round((Date.now() - taskStartTime) / 1000)
201+
log.failed(`[${name}] Failed (${elapsed}s) - ${completed}/${tasks.length}`)
202+
throw error
203+
}
204+
)
205+
})
206+
207+
// Progress indicator
208+
const progressInterval = setInterval(() => {
209+
const elapsed = Math.round((Date.now() - startTime) / 1000)
210+
const pending = tasks.length - completed
211+
if (pending > 0) {
212+
log.substep(`Progress: ${completed}/${tasks.length} complete, ${pending} running (${elapsed}s elapsed)`)
213+
}
214+
}, 15000) // Update every 15 seconds
215+
216+
const results = await Promise.allSettled(trackedTasks)
217+
clearInterval(progressInterval)
166218

219+
const totalElapsed = Math.round((Date.now() - startTime) / 1000)
167220
const succeeded = results.filter(r => r.status === 'fulfilled').length
168221
const failed = results.filter(r => r.status === 'rejected').length
169222

170223
if (failed > 0) {
171-
log.warn(`Completed: ${succeeded} succeeded, ${failed} failed`)
172-
// Log errors
224+
log.warn(`Completed in ${totalElapsed}s: ${succeeded} succeeded, ${failed} failed`)
225+
// Log errors with task names
173226
results.forEach((result, index) => {
174227
if (result.status === 'rejected') {
175-
log.error(`Task ${index + 1} failed: ${result.reason}`)
228+
const name = taskNames[index] || `Task ${index + 1}`
229+
log.error(`[${name}] failed: ${result.reason}`)
176230
}
177231
})
178232
} else {
179-
log.success(`All ${succeeded} ${description} completed successfully`)
233+
log.success(`All ${succeeded} ${description} completed successfully in ${totalElapsed}s`)
180234
}
181235

182236
return results
@@ -404,7 +458,8 @@ async function syncClaudeMd(claudeCmd, options = {}) {
404458
.catch(error => ({ project: project.name, success: false, error }))
405459
)
406460

407-
const results = await runParallel(tasks, 'CLAUDE.md updates')
461+
const taskNames = projects.map(p => path.basename(p))
462+
const results = await runParallel(tasks, 'CLAUDE.md updates', taskNames)
408463

409464
// Check for failures
410465
results.forEach(result => {
@@ -430,7 +485,8 @@ async function syncClaudeMd(claudeCmd, options = {}) {
430485
if (shouldRunParallel(opts) && projects.length > 1) {
431486
// Run commits in parallel
432487
const tasks = projects.map(project => commitChanges(project))
433-
await runParallel(tasks, 'commits')
488+
const taskNames = projects.map(p => path.basename(p))
489+
await runParallel(tasks, 'commits', taskNames)
434490
} else {
435491
// Run sequentially
436492
for (const project of projects) {
@@ -743,7 +799,8 @@ async function runSecurityScan(claudeCmd, options = {}) {
743799
.catch(error => ({ project: project.name, issues: null, error }))
744800
)
745801

746-
const results = await runParallel(tasks, 'security scans')
802+
const taskNames = projects.map(p => p.name)
803+
const results = await runParallel(tasks, 'security scans', taskNames)
747804

748805
// Collect results
749806
results.forEach(result => {
@@ -779,6 +836,9 @@ async function runSecurityScan(claudeCmd, options = {}) {
779836

780837
/**
781838
* Run Claude-assisted commits across Socket projects.
839+
* IMPORTANT: When running in parallel mode (default), Claude agents run silently (stdio: 'pipe').
840+
* Interactive prompts would conflict if multiple agents needed user input simultaneously.
841+
* Use --seq flag if you need interactive debugging across multiple repos.
782842
*/
783843
async function runClaudeCommit(claudeCmd, options = {}) {
784844
const opts = { __proto__: null, ...options }
@@ -1519,6 +1579,8 @@ Be specific and actionable.`
15191579

15201580
/**
15211581
* Run all checks, push, and monitor CI until green.
1582+
* NOTE: This operates on the current repo only. For multi-repo CI, run --green in each repo.
1583+
* Multi-repo parallel execution would conflict with interactive prompts if fixes fail.
15221584
*/
15231585
async function runGreen(claudeCmd, options = {}) {
15241586
const opts = { __proto__: null, ...options }
@@ -1529,19 +1591,20 @@ async function runGreen(claudeCmd, options = {}) {
15291591
printHeader('Green CI Pipeline')
15301592

15311593
// Step 1: Run local checks
1532-
log.step('Running local checks')
1594+
const repoName = path.basename(rootPath)
1595+
log.step(`Running local checks in ${colors.cyan(repoName)}`)
15331596
const localChecks = [
15341597
{ name: 'Install dependencies', cmd: 'pnpm', args: ['install'] },
15351598
{ name: 'Fix code style', cmd: 'pnpm', args: ['run', 'fix'] },
15361599
{ name: 'Run checks', cmd: 'pnpm', args: ['run', 'check'] },
1537-
{ name: 'Run coverage', cmd: 'pnpm', args: ['run', 'coverage'] },
1600+
{ name: 'Run coverage', cmd: 'pnpm', args: ['run', 'cover'] },
15381601
{ name: 'Run tests', cmd: 'pnpm', args: ['run', 'test', '--', '--update'] }
15391602
]
15401603

15411604
let autoFixAttempts = 0
15421605

15431606
for (const check of localChecks) {
1544-
log.progress(check.name)
1607+
log.progress(`[${repoName}] ${check.name}`)
15451608

15461609
if (isDryRun) {
15471610
log.done(`[DRY RUN] Would run: ${check.cmd} ${check.args.join(' ')}`)
@@ -1563,7 +1626,7 @@ async function runGreen(claudeCmd, options = {}) {
15631626

15641627
if (isAutoMode) {
15651628
// Attempt automatic fix
1566-
log.progress(`Attempting auto-fix with Claude (attempt ${autoFixAttempts}/${MAX_AUTO_FIX_ATTEMPTS})`)
1629+
log.progress(`[${repoName}] Auto-fix attempt ${autoFixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`)
15671630

15681631
const fixPrompt = `You are fixing a CI/build issue automatically. The command "${check.cmd} ${check.args.join(' ')}" failed in the ${path.basename(rootPath)} project.
15691632
@@ -1582,19 +1645,46 @@ IMPORTANT:
15821645
- If it's a type error, fix the code
15831646
- If it's a lint error, fix the formatting
15841647
- If tests are failing, update snapshots or fix the test
1648+
- If a script is missing, check if there's a similar script name (e.g., 'cover' vs 'coverage')
15851649
15861650
Fix this issue now by making the necessary changes.`
15871651

1588-
// Run Claude non-interactively to get the fix
1589-
log.substep('Analyzing error and applying fixes...')
1590-
await runCommand(claudeCmd, prepareClaudeArgs([], opts), {
1591-
input: fixPrompt,
1652+
// Run Claude non-interactively with timeout and progress
1653+
const startTime = Date.now()
1654+
const timeout = 120000 // 2 minute timeout
1655+
log.substep(`[${repoName}] Analyzing error...`)
1656+
1657+
const claudeProcess = spawn(claudeCmd, prepareClaudeArgs([], opts), {
15921658
cwd: rootPath,
1593-
stdio: 'pipe' // Don't inherit stdio - run silently
1659+
stdio: ['pipe', 'pipe', 'pipe']
1660+
})
1661+
1662+
claudeProcess.stdin.write(fixPrompt)
1663+
claudeProcess.stdin.end()
1664+
1665+
// Monitor progress with timeout
1666+
let progressInterval = setInterval(() => {
1667+
const elapsed = Date.now() - startTime
1668+
if (elapsed > timeout) {
1669+
log.warn(`[${repoName}] Claude fix timed out after ${Math.round(elapsed/1000)}s`)
1670+
claudeProcess.kill()
1671+
clearInterval(progressInterval)
1672+
} else {
1673+
log.substep(`[${repoName}] Claude working... (${Math.round(elapsed/1000)}s)`)
1674+
}
1675+
}, 10000) // Update every 10 seconds
1676+
1677+
await new Promise((resolve) => {
1678+
claudeProcess.on('close', () => {
1679+
clearInterval(progressInterval)
1680+
const elapsed = Date.now() - startTime
1681+
log.done(`[${repoName}] Claude fix completed in ${Math.round(elapsed/1000)}s`)
1682+
resolve()
1683+
})
15941684
})
15951685

1596-
// Give Claude's changes a moment to complete
1597-
await new Promise(resolve => setTimeout(resolve, 2000))
1686+
// Give file system a moment to sync
1687+
await new Promise(resolve => setTimeout(resolve, 1000))
15981688

15991689
// Retry the check
16001690
log.progress(`Retrying ${check.name}`)
@@ -2026,6 +2116,14 @@ async function main() {
20262116
type: 'string',
20272117
default: '10',
20282118
},
2119+
pinky: {
2120+
type: 'boolean',
2121+
default: false,
2122+
},
2123+
'the-brain': {
2124+
type: 'boolean',
2125+
default: false,
2126+
},
20292127
},
20302128
allowPositionals: true,
20312129
strict: false,
@@ -2053,13 +2151,17 @@ async function main() {
20532151
console.log(' --no-darkwing Disable "Let\'s get dangerous!" mode')
20542152
console.log(' --max-retries N Max CI fix attempts (--green, default: 3)')
20552153
console.log(' --max-auto-fixes N Max auto-fix attempts before interactive (--green, default: 10)')
2154+
console.log(' --pinky Use default model (Claude 3.5 Sonnet)')
2155+
console.log(' --the-brain Use expensive model (Claude 3 Opus) - "Try to take over the world!"')
20562156
console.log('\nExamples:')
20572157
console.log(' pnpm claude --review # Review staged changes')
20582158
console.log(' pnpm claude --fix # Scan for issues')
20592159
console.log(' pnpm claude --green # Ensure CI passes')
20602160
console.log(' pnpm claude --green --dry-run # Test green without real CI')
20612161
console.log(' pnpm claude --green --max-retries 5 # More CI retry attempts')
20622162
console.log(' pnpm claude --green --max-auto-fixes 3 # Fewer auto-fix attempts')
2163+
console.log(' pnpm claude --fix --the-brain # Deep analysis with powerful model')
2164+
console.log(' pnpm claude --refactor --pinky # Quick refactor with default model')
20632165
console.log(' pnpm claude --test lib/utils.js # Generate tests for a file')
20642166
console.log(' pnpm claude --explain path.join # Explain a concept')
20652167
console.log(' pnpm claude --refactor src/index.js # Suggest refactoring')

0 commit comments

Comments
 (0)