@@ -1511,6 +1511,265 @@ Be specific and actionable.`
15111511 return true
15121512}
15131513
1514+ /**
1515+ * Run all checks, push, and monitor CI until green.
1516+ */
1517+ async function runGreen ( claudeCmd , options = { } ) {
1518+ const opts = { __proto__ : null , ...options }
1519+ const maxRetries = parseInt ( opts [ 'max-retries' ] || '3' , 10 )
1520+ const isDryRun = opts [ 'dry-run' ]
1521+
1522+ printHeader ( 'Green CI Pipeline' )
1523+
1524+ // Step 1: Run local checks
1525+ log . step ( 'Running local checks' )
1526+ const localChecks = [
1527+ { name : 'Install dependencies' , cmd : 'pnpm' , args : [ 'install' ] } ,
1528+ { name : 'Fix code style' , cmd : 'pnpm' , args : [ 'run' , 'fix' ] } ,
1529+ { name : 'Run checks' , cmd : 'pnpm' , args : [ 'run' , 'check' ] } ,
1530+ { name : 'Run coverage' , cmd : 'pnpm' , args : [ 'run' , 'coverage' ] } ,
1531+ { name : 'Run tests' , cmd : 'pnpm' , args : [ 'run' , 'test' , '--' , '--update' ] }
1532+ ]
1533+
1534+ for ( const check of localChecks ) {
1535+ log . progress ( check . name )
1536+
1537+ if ( isDryRun ) {
1538+ log . done ( `[DRY RUN] Would run: ${ check . cmd } ${ check . args . join ( ' ' ) } ` )
1539+ continue
1540+ }
1541+
1542+ const result = await runCommandWithOutput ( check . cmd , check . args , {
1543+ cwd : rootPath ,
1544+ stdio : 'inherit'
1545+ } )
1546+
1547+ if ( result . exitCode !== 0 ) {
1548+ log . failed ( `${ check . name } failed` )
1549+
1550+ // Attempt to fix with Claude
1551+ log . progress ( 'Attempting auto-fix with Claude' )
1552+ const fixPrompt = `The command "${ check . cmd } ${ check . args . join ( ' ' ) } " failed in the ${ path . basename ( rootPath ) } project.
1553+
1554+ Please analyze the error and provide a fix. The error output was:
1555+ ${ result . stderr || result . stdout }
1556+
1557+ Provide specific file edits or commands to fix this issue.`
1558+
1559+ await runCommand ( claudeCmd , prepareClaudeArgs ( [ ] , opts ) , {
1560+ input : fixPrompt ,
1561+ stdio : 'inherit' ,
1562+ cwd : rootPath
1563+ } )
1564+
1565+ // Retry the check
1566+ log . progress ( `Retrying ${ check . name } ` )
1567+ const retryResult = await runCommandWithOutput ( check . cmd , check . args , {
1568+ cwd : rootPath
1569+ } )
1570+
1571+ if ( retryResult . exitCode !== 0 ) {
1572+ log . error ( `Failed to fix ${ check . name } automatically` )
1573+ return false
1574+ }
1575+ }
1576+
1577+ log . done ( `${ check . name } passed` )
1578+ }
1579+
1580+ // Step 2: Commit and push changes
1581+ log . step ( 'Committing and pushing changes' )
1582+
1583+ // Check for changes
1584+ const statusResult = await runCommandWithOutput ( 'git' , [ 'status' , '--porcelain' ] , {
1585+ cwd : rootPath
1586+ } )
1587+
1588+ if ( statusResult . stdout . trim ( ) ) {
1589+ log . progress ( 'Changes detected, committing' )
1590+
1591+ if ( isDryRun ) {
1592+ log . done ( '[DRY RUN] Would commit and push changes' )
1593+ } else {
1594+ // Stage all changes
1595+ await runCommand ( 'git' , [ 'add' , '.' ] , { cwd : rootPath } )
1596+
1597+ // Commit
1598+ const commitMessage = 'Fix CI issues and update tests'
1599+ await runCommand ( 'git' , [ 'commit' , '-m' , commitMessage , '--no-verify' ] , { cwd : rootPath } )
1600+
1601+ // Push
1602+ await runCommand ( 'git' , [ 'push' ] , { cwd : rootPath } )
1603+ log . done ( 'Changes pushed to remote' )
1604+ }
1605+ } else {
1606+ log . info ( 'No changes to commit' )
1607+ }
1608+
1609+ // Step 3: Monitor CI workflow
1610+ log . step ( 'Monitoring CI workflow' )
1611+
1612+ if ( isDryRun ) {
1613+ log . done ( '[DRY RUN] Would monitor CI workflow' )
1614+ printFooter ( 'Green CI Pipeline (dry run) complete!' )
1615+ return true
1616+ }
1617+
1618+ // Get current commit SHA
1619+ const shaResult = await runCommandWithOutput ( 'git' , [ 'rev-parse' , 'HEAD' ] , {
1620+ cwd : rootPath
1621+ } )
1622+ const currentSha = shaResult . stdout . trim ( )
1623+
1624+ // Get repo info
1625+ const remoteResult = await runCommandWithOutput ( 'git' , [ 'remote' , 'get-url' , 'origin' ] , {
1626+ cwd : rootPath
1627+ } )
1628+ const remoteUrl = remoteResult . stdout . trim ( )
1629+ const repoMatch = remoteUrl . match ( / g i t h u b \. c o m [: / ] ( .+ ?) \/ ( .+ ?) ( \. g i t ) ? $ / )
1630+
1631+ if ( ! repoMatch ) {
1632+ log . error ( 'Could not determine GitHub repository from remote URL' )
1633+ return false
1634+ }
1635+
1636+ const [ , owner , repoName ] = repoMatch
1637+ const repo = repoName . replace ( '.git' , '' )
1638+
1639+ // Monitor workflow with retries
1640+ let retryCount = 0
1641+ let lastRunId = null
1642+
1643+ while ( retryCount < maxRetries ) {
1644+ log . progress ( `Checking CI status (attempt ${ retryCount + 1 } /${ maxRetries } )` )
1645+
1646+ // Wait a bit for CI to start
1647+ if ( retryCount === 0 ) {
1648+ log . substep ( 'Waiting 10 seconds for CI to start...' )
1649+ await new Promise ( resolve => setTimeout ( resolve , 10000 ) )
1650+ }
1651+
1652+ // Check workflow runs using gh CLI
1653+ const runsResult = await runCommandWithOutput ( 'gh' , [
1654+ 'run' , 'list' ,
1655+ '--repo' , `${ owner } /${ repo } ` ,
1656+ '--commit' , currentSha ,
1657+ '--limit' , '1' ,
1658+ '--json' , 'databaseId,status,conclusion,name'
1659+ ] , {
1660+ cwd : rootPath
1661+ } )
1662+
1663+ if ( runsResult . exitCode !== 0 ) {
1664+ log . failed ( 'Failed to fetch workflow runs' )
1665+ return false
1666+ }
1667+
1668+ let runs
1669+ try {
1670+ runs = JSON . parse ( runsResult . stdout || '[]' )
1671+ } catch {
1672+ log . failed ( 'Failed to parse workflow runs' )
1673+ return false
1674+ }
1675+
1676+ if ( runs . length === 0 ) {
1677+ log . substep ( 'No workflow runs found yet, waiting...' )
1678+ await new Promise ( resolve => setTimeout ( resolve , 30000 ) )
1679+ continue
1680+ }
1681+
1682+ const run = runs [ 0 ]
1683+ lastRunId = run . databaseId
1684+
1685+ log . substep ( `Workflow "${ run . name } " status: ${ run . status } ` )
1686+
1687+ if ( run . status === 'completed' ) {
1688+ if ( run . conclusion === 'success' ) {
1689+ log . done ( 'CI workflow passed! 🎉' )
1690+ printFooter ( 'Green CI Pipeline complete!' )
1691+ return true
1692+ } else {
1693+ log . failed ( `CI workflow failed with conclusion: ${ run . conclusion } ` )
1694+
1695+ if ( retryCount < maxRetries - 1 ) {
1696+ // Fetch failure logs
1697+ log . progress ( 'Fetching failure logs' )
1698+
1699+ const logsResult = await runCommandWithOutput ( 'gh' , [
1700+ 'run' , 'view' , lastRunId . toString ( ) ,
1701+ '--repo' , `${ owner } /${ repo } ` ,
1702+ '--log-failed'
1703+ ] , {
1704+ cwd : rootPath
1705+ } )
1706+
1707+ // Analyze and fix with Claude
1708+ log . progress ( 'Analyzing CI failure with Claude' )
1709+ const fixPrompt = `The CI workflow failed for commit ${ currentSha } in ${ owner } /${ repo } .
1710+
1711+ Failure logs:
1712+ ${ logsResult . stdout || 'No logs available' }
1713+
1714+ Please analyze these CI logs and provide specific fixes for the failures. Focus on:
1715+ 1. Test failures
1716+ 2. Lint errors
1717+ 3. Type checking issues
1718+ 4. Build problems
1719+
1720+ Provide exact file changes needed to fix these issues.`
1721+
1722+ await runCommand ( claudeCmd , prepareClaudeArgs ( [ ] , opts ) , {
1723+ input : fixPrompt ,
1724+ stdio : 'inherit' ,
1725+ cwd : rootPath
1726+ } )
1727+
1728+ // Run local checks again
1729+ log . progress ( 'Running local checks after fixes' )
1730+ for ( const check of localChecks ) {
1731+ await runCommandWithOutput ( check . cmd , check . args , {
1732+ cwd : rootPath ,
1733+ stdio : 'inherit'
1734+ } )
1735+ }
1736+
1737+ // Commit and push fixes
1738+ const fixStatusResult = await runCommandWithOutput ( 'git' , [ 'status' , '--porcelain' ] , {
1739+ cwd : rootPath
1740+ } )
1741+
1742+ if ( fixStatusResult . stdout . trim ( ) ) {
1743+ log . progress ( 'Committing CI fixes' )
1744+ await runCommand ( 'git' , [ 'add' , '.' ] , { cwd : rootPath } )
1745+ await runCommand ( 'git' , [ 'commit' , '-m' , `Fix CI failures (attempt ${ retryCount + 1 } )` , '--no-verify' ] , { cwd : rootPath } )
1746+ await runCommand ( 'git' , [ 'push' ] , { cwd : rootPath } )
1747+
1748+ // Update SHA for next check
1749+ const newShaResult = await runCommandWithOutput ( 'git' , [ 'rev-parse' , 'HEAD' ] , {
1750+ cwd : rootPath
1751+ } )
1752+ currentSha = newShaResult . stdout . trim ( )
1753+ }
1754+
1755+ retryCount ++
1756+ } else {
1757+ log . error ( `CI still failing after ${ maxRetries } attempts` )
1758+ log . substep ( `View run at: https://github.com/${ owner } /${ repo } /actions/runs/${ lastRunId } ` )
1759+ return false
1760+ }
1761+ }
1762+ } else {
1763+ // Workflow still running, wait and check again
1764+ log . substep ( 'Workflow still running, waiting 30 seconds...' )
1765+ await new Promise ( resolve => setTimeout ( resolve , 30000 ) )
1766+ }
1767+ }
1768+
1769+ log . error ( `Exceeded maximum retries (${ maxRetries } )` )
1770+ return false
1771+ }
1772+
15141773/**
15151774 * Show available Claude operations.
15161775 */
@@ -1519,6 +1778,7 @@ function showOperations() {
15191778 console . log ( ' --sync Synchronize CLAUDE.md files across projects' )
15201779 console . log ( ' --commit Create commits with Claude assistance' )
15211780 console . log ( ' --push Create commits and push to remote' )
1781+ console . log ( ' --green Ensure all tests pass, push, monitor CI until green' )
15221782
15231783 console . log ( '\nCode quality:' )
15241784 console . log ( ' --review Review staged changes before committing' )
@@ -1562,6 +1822,10 @@ async function main() {
15621822 type : 'boolean' ,
15631823 default : false ,
15641824 } ,
1825+ green : {
1826+ type : 'boolean' ,
1827+ default : false ,
1828+ } ,
15651829 // Code quality.
15661830 review : {
15671831 type : 'boolean' ,
@@ -1645,6 +1909,10 @@ async function main() {
16451909 type : 'boolean' ,
16461910 default : false ,
16471911 } ,
1912+ 'max-retries' : {
1913+ type : 'string' ,
1914+ default : '3' ,
1915+ } ,
16481916 } ,
16491917 allowPositionals : true ,
16501918 strict : false ,
@@ -1654,7 +1922,7 @@ async function main() {
16541922 const hasOperation = values . sync || values . fix || values . commit || values . push ||
16551923 values . review || values . refactor || values . optimize || values . clean ||
16561924 values . audit || values . test || values . docs || values . explain ||
1657- values . debug || values . deps || values . migrate
1925+ values . debug || values . deps || values . migrate || values . green
16581926
16591927 // Show help if requested or no operation specified.
16601928 if ( values . help || ! hasOperation ) {
@@ -1670,9 +1938,13 @@ async function main() {
16701938 console . log ( ' --no-cross-repo Operate on current project only' )
16711939 console . log ( ' --seq Run sequentially (default: parallel)' )
16721940 console . log ( ' --no-darkwing Disable "Let\'s get dangerous!" mode' )
1941+ console . log ( ' --max-retries N Max CI fix attempts (--green, default: 3)' )
16731942 console . log ( '\nExamples:' )
16741943 console . log ( ' pnpm claude --review # Review staged changes' )
16751944 console . log ( ' pnpm claude --fix # Scan for issues' )
1945+ console . log ( ' pnpm claude --green # Ensure CI passes' )
1946+ console . log ( ' pnpm claude --green --dry-run # Test green without real CI' )
1947+ console . log ( ' pnpm claude --green --max-retries 5 # More fix attempts' )
16761948 console . log ( ' pnpm claude --test lib/utils.js # Generate tests for a file' )
16771949 console . log ( ' pnpm claude --explain path.join # Explain a concept' )
16781950 console . log ( ' pnpm claude --refactor src/index.js # Suggest refactoring' )
@@ -1710,6 +1982,8 @@ async function main() {
17101982 } else if ( values . push ) {
17111983 // --push combines commit and push.
17121984 success = await runClaudeCommit ( claudeCmd , { ...options , push : true } )
1985+ } else if ( values . green ) {
1986+ success = await runGreen ( claudeCmd , options )
17131987 }
17141988 // Code quality operations.
17151989 else if ( values . review ) {
0 commit comments