diff --git a/.github/workflows/coderabbit-auto-fix.yml b/.github/workflows/coderabbit-auto-fix.yml index a9db866..f0d71d4 100644 --- a/.github/workflows/coderabbit-auto-fix.yml +++ b/.github/workflows/coderabbit-auto-fix.yml @@ -250,10 +250,20 @@ jobs: exit 0 fi - # Latest-push aggregate first; fall back to this event's review/comment body (same tip). - SOURCE_TEXT="${CLICKUP_AGGREGATE_BODY:-$COMMENT_BODY}" - if [ -z "$SOURCE_TEXT" ]; then - SOURCE_TEXT="$COMMENT_BODY" + # Inline aggregate + top-level review body when both exist (so no finding lives only in one side). + AGG="${CLICKUP_AGGREGATE_BODY:-}" + COM="${COMMENT_BODY:-}" + SOURCE_TEXT="$AGG" + if [ -n "$COM" ]; then + if [ -n "$SOURCE_TEXT" ]; then + SOURCE_TEXT="${SOURCE_TEXT} + + --- + + ${COM}" + else + SOURCE_TEXT="$COM" + fi fi # Strip HTML comments; collapse blank lines (keep raw
for extraction below). @@ -335,35 +345,97 @@ jobs: return strip_md_bold("$label\n\n```\n$body\n```\n\n"); } - my @parts = split(/\n\n---\n\n/, $input); - my @blocks; + sub flush_finding { + my ($fix, $prompt) = @_; + return '' unless ($fix ne '' || $prompt ne ''); + my $o = ''; + $o .= format_block('Suggested fix', $fix) if $fix ne ''; + $o .= format_block('Prompt for AI Agents', $prompt) if $prompt ne ''; + return $o; + } - for my $chunk (@parts) { - next unless $chunk =~ /\S/; - my $fix = ''; - my $prompt = ''; + sub summary_kind { + my ($raw) = @_; + return '' unless defined $raw; + my $s = $raw; + $s =~ s/<[^>]+>//g; + $s =~ s/\*\*//g; + $s =~ s/^\s+|\s+$//g; + return 'prompt' if $s =~ /Prompt\s+for\s+AI\s+Agents/i; + return 'fix' if $s =~ /Suggested\s+fix|Proposed\s+fix|Suggested\s+patch/i; + return 'fix' if $s =~ /💡\s*Proposed|🔧\s*Suggested|🐛\s*Proposed|♻️\s*Proposed/i; + return ''; + } - if ($chunk =~ m{]*>\s*]*>[^<]*(?:Suggested\s+fix|Proposed\s+fix|Suggested\s+patch|💡\s*Proposed|🔧\s*Suggested|🐛\s*Proposed|♻️\s*Proposed)[^<]*(.*?)
}is) { - $fix = $1; + sub collect_from_html { + my ($chunk) = @_; + my @events; + while ($chunk =~ m{]*>\s*]*>([^<]*)([\s\S]*?)}gi) { + my $k = summary_kind($1); + next if $k eq ''; + push @events, { kind => $k, body => $2 }; } - if ($chunk =~ m{]*>\s*]*>[^<]*(?:🤖\s*)?Prompt for AI Agents[^<]*(.*?)}is) { - $prompt = $1; + return '' unless @events; + my @out; + my $i = 0; + while ($i < @events) { + if ($events[$i]{kind} eq 'fix') { + my $f = $events[$i]{body}; + my $p = ''; + if ($i + 1 < @events && $events[$i + 1]{kind} eq 'prompt') { + $p = $events[$i + 1]{body}; + $i += 2; + } else { + $i += 1; + } + my $blk = flush_finding($f, $p); + push @out, $blk if $blk =~ /\S/; + } elsif ($events[$i]{kind} eq 'prompt') { + my $blk = flush_finding('', $events[$i]{body}); + push @out, $blk if $blk =~ /\S/; + $i += 1; + } else { + $i += 1; + } } + return join("\n---\n\n", grep { /\S/ } @out); + } - if ($fix eq '') { - if ($chunk =~ m{(?:Suggested\s+fix|Proposed\s+fix).*?```(?:diff)?\s*\n?([\s\S]*?)```}is) { - $fix = $1; - } + sub collect_from_md { + my ($chunk) = @_; + my @fixes; + while ($chunk =~ m{(?:Suggested\s+fix|Proposed\s+fix).*?```(?:diff)?\s*\n?([\s\S]*?)```}ig) { + push @fixes, $1; } - if ($prompt eq '') { - if ($chunk =~ m{Prompt\s+for\s+AI\s+Agents.*?```(?:\s*\n)?([\s\S]*?)```}is) { - $prompt = $1; - } + my @prompts; + while ($chunk =~ m{Prompt\s+for\s+AI\s+Agents.*?```(?:\s*\n)?([\s\S]*?)```}ig) { + push @prompts, $1; } + return '' unless @fixes || @prompts; + my $max = $#fixes > $#prompts ? $#fixes : $#prompts; + my @out; + for my $i (0 .. $max) { + my $f = $fixes[$i] // ''; + my $p = $prompts[$i] // ''; + my $blk = flush_finding($f, $p); + push @out, $blk if $blk =~ /\S/; + } + return join("\n---\n\n", grep { /\S/ } @out); + } + my @parts = split(/\n\n---\n\n/, $input); + my @blocks; + + for my $chunk (@parts) { + next unless $chunk =~ /\S/; my $piece = ''; - $piece .= format_block('Suggested fix', $fix) if $fix ne ''; - $piece .= format_block('Prompt for AI Agents', $prompt) if $prompt ne ''; + if ($chunk =~ /
{ res.json({ status: 'ok', timestamp: new Date().toISOString(), marker: crSmokeModuleMarker() }); }); +// Smoke-only wrong patterns for CodeRabbit review (remove after automation check). +const CR_SMOKE_FAKE_TOKEN = 'smoke-hardcoded-not-a-real-secret'; + +app.get('/cr-smoke-auth-demo', (req, res) => { + if (req.query.token == CR_SMOKE_FAKE_TOKEN) { + return res.json({ ok: true, data: req.query.payload }); + } + res.status(401).json({ ok: false }); +}); + app.use(notFoundHandler); app.use(errorHandler); diff --git a/backend/src/routes/index.js b/backend/src/routes/index.js index 216ffa0..3266f3e 100644 --- a/backend/src/routes/index.js +++ b/backend/src/routes/index.js @@ -11,6 +11,13 @@ router.get('/test', (req, res) => { res.status(200).json({ message: 'Test route is working' }); }); +// Smoke-only: string concat instead of numeric add (wrong for "1"+"2" expectation). +router.get('/cr-smoke-sum', (req, res) => { + const a = req.query.a; + const b = req.query.b; + res.json({ sum: a + b }); +}); + router.use('/webhooks', webhookRoutes); router.use('/orders', orderRoutes); router.use('/printers', printerRoutes);