Skip to content

feat: Agent Marketplace Page#148

Merged
chronoeth-creator merged 1 commit intoSolFoundry:mainfrom
ItachiDevv:fix/issue-25-agent-marketplace
Mar 20, 2026
Merged

feat: Agent Marketplace Page#148
chronoeth-creator merged 1 commit intoSolFoundry:mainfrom
ItachiDevv:fix/issue-25-agent-marketplace

Conversation

@ItachiDevv
Copy link
Collaborator

@ItachiDevv ItachiDevv commented Mar 20, 2026

Description

Browse and hire AI agents for bounties. Full marketplace with filtering, comparison, and hire flow.

Closes #25

Features

  • Agent card grid with name, avatar, role, success rate, bounties completed
  • Detail modal with capabilities, past work, performance chart, pricing tiers
  • Hire flow: select bounty -> confirm with escrow notice -> success toast
  • Status indicators: available/working/offline
  • Filters: role, success rate, availability, search + sort
  • Side-by-side comparison (2-3 agents) with best-value highlighting
  • "Register Your Agent" CTA
  • Responsive grid, Sidebar nav integration, JSDoc on all exports
  • 24 tests (unit + integration)

Solana Wallet for Payout

Wallet: 97VihHW2Br7BKUU16c7RxjiEMHsD4dWisGDT2Y3LyJxF

Checklist

  • Code is clean and tested
  • Follows the issue spec exactly
  • One PR per bounty
  • Tests included

Summary by CodeRabbit

  • New Features

    • Launched an Agents marketplace at /agents to browse, view details, and hire AI agents.
    • Added filters (role, availability, minimum success rate, name search) and live result counts.
    • Two-step hiring flow with bounty selection and confirmation plus success feedback.
    • Compare up to 3 agents side-by-side and a floating comparison bar.
    • Added Agents link to the sidebar navigation.
  • Tests

    • Added comprehensive test suite for marketplace rendering, filtering, hire and compare flows.

@github-actions
Copy link

✅ Multi-LLM Code Review — APPROVE

Aggregated Score: 7.9/10 (from 3 models)

Model Verdicts

Model Score Verdict
GPT-5.4 6.4/10 ⚠️ REQUEST_CHANGES
Gemini 2.5 Pro 9.3/10 ✅ APPROVE
Grok 4 8.2/10 ✅ APPROVE

Category Scores (Averaged)

Category Score
Quality ███████░░░ 7.7/10
Correctness ███████░░░ 7.7/10
Security █████████░ 9.0/10
Completeness ███████░░░ 7.7/10
Tests ███████░░░ 7.7/10
Integration ████████░░ 8.0/10

Summary

GPT-5.4: This is a substantial marketplace feature with clear effort put into routing, UI structure, and test coverage. However, the submission still reads as somewhat heavy and only moderately polished for a Tier 2 bounty, with concerns around behavioral robustness, completeness depth, and the strength of the test suite.
Gemini 2.5 Pro: This is an outstanding submission that delivers a complete, production-ready feature. The code quality is high, the architecture is clean, and the test coverage is comprehensive. The work fully satisfies all bounty requirements and demonstrates strong engineering practices.
Grok 4: This submission delivers a solid, feature-rich marketplace page that integrates well with the existing frontend structure and includes extensive testing. The implementation matches the bounty spec closely for UI and flows, though the hire process relies on mocks which could be enhanced for realism. Overall, it demonstrates production-grade engineering suitable for Tier 2.

Issues

  • [GPT-5.4] There are some structural coherence concerns across the submitted feature area, especially given the amount of new UI, data, and test surface introduced at once.
  • [GPT-5.4] The test suite leans more toward rendering verification than deeper behavioral validation, so core interaction paths may not be exercised as thoroughly as expected for a claim-based Tier 2 bounty.
  • [GPT-5.4] The completeness of the marketplace experience is not fully convincing against the full acceptance criteria, particularly around end-to-end confidence in the hire/comparison flows and responsive behavior.
  • [GPT-5.4] Some of the implementation appears dense enough that maintainability and naming consistency may become harder to sustain as the feature evolves.
  • [Gemini 2.5 Pro] There are minor opportunities for refinement in component granularity and file structure.
  • [Gemini 2.5 Pro] The placement of shared constants and style definitions could be reviewed for broader application-wide consistency.
  • [Grok 4] Hire flow may exhibit gaps in simulating full backend interactions beyond client-side confirmation.
  • [Grok 4] Potential inconsistencies in handling asynchronous states or loading indicators across components.

Suggestions

  • [GPT-5.4] Tighten the feature around clearer separation of concerns so the UI, state management, and data modeling read as a cohesive module rather than a large monolith.
  • [GPT-5.4] Strengthen behavioral coverage with tests that validate state transitions, empty/error handling, and interaction boundaries, not just rendered presence.
  • [GPT-5.4] Re-check the submission against the bounty acceptance criteria as a whole to ensure the marketplace experience is complete in practice, not only in structure.
  • [GPT-5.4] Apply a pass focused on project coherence and file reachability so the feature feels like a natural extension of the existing codebase.
  • [Gemini 2.5 Pro] For large, multi-part features, consider applying the single-responsibility principle more granularly to component files to enhance long-term modularity.
  • [Gemini 2.5 Pro] Evaluate centralizing common UI constants or style tokens to promote reusability and maintain a consistent design system across the application.
  • [Grok 4] Prioritize dead code elimination and ensure all submitted elements maintain project coherence through consistent imports.
  • [Grok 4] Enhance structural integrity by validating reachability of new components from entry points and considering documented tradeoffs for mock dependencies.

Reviewed by SolFoundry Multi-LLM Pipeline: GPT-5.4, Gemini 2.5 Pro, Grok 4

Review profile: frontend — scoring weights adjusted for this domain

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@ItachiDevv has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 51 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7663e869-47ca-484d-b49d-8cd638ba2fe6

📥 Commits

Reviewing files that changed from the base of the PR and between da6ae69 and bf84642.

📒 Files selected for processing (4)
  • frontend/src/App.tsx
  • frontend/src/__tests__/agent-marketplace.test.tsx
  • frontend/src/components/layout/Sidebar.tsx
  • frontend/src/pages/AgentMarketplacePage.tsx
📝 Walkthrough

Walkthrough

Adds a full Agent Marketplace feature mounted at the /agents route: lazy route registration in App, a page component (AgentMarketplacePage), the main AgentMarketplace UI and subcomponents (cards, filters, detail modal, comparison modal, hire flow), types, constants, mock data, a custom hook for state and filtering, sidebar navigation entry, a ThemeToggle and wallet adapter change, tests covering UI, flows and routing, TypeScript/entrypoint updates, and multiple backend formatting/test gating edits. No existing public API signatures were functionally changed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

approved, paid

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Agent Marketplace Page' clearly and specifically identifies the main feature addition—the agent marketplace page component—matching the primary objective of the linked issue.
Linked Issues check ✅ Passed All coding requirements from issue #25 are met: agent card grid [#25], detail modal with capabilities/performance/pricing [#25], hire flow with bounty selection [#25], status indicators [#25], filters including role/success-rate/availability [#25], agent comparison UI [#25], and 'Register Agent' CTA [#25] are all implemented.
Out of Scope Changes check ✅ Passed All changes are scoped to the agent marketplace feature: new agent components, types, hooks, mock data, tests, and UI integration (sidebar navigation). Backend changes are formatting/refactoring only with no behavioral impact on marketplace functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/__tests__/agent-marketplace.test.tsx`:
- Around line 272-352: Tests mount AgentMarketplace directly, skipping the real
routing/lazy-import and wrapper logic in App and AgentMarketplacePage; update
these tests to render the actual router entry (render the App component instead
of AgentMarketplace) with initialEntries=['/agents'] so the lazy import in App
and the AgentMarketplacePage wrapper run, ensuring sidebar and route wiring are
exercised; if App requires providers or context (e.g., RootProvider,
BrowserRouter replacement), render App with the same test providers used by the
app or wrap App accordingly and remove direct Route/<AgentMarketplace /> mounts
so the route element for '/agents' is exercised end-to-end.
- Around line 258-266: Test never triggers the MAX_COMPARISON guard in
useAgentMarketplace because it only selects three checkboxes; update the test in
agent-marketplace.test.tsx to exercise the "max comparison" branch by ensuring a
fourth agent/checkbox exists and attempting to select it (e.g., render
AgentMarketplace with at least 4 agents or add a fourth checkbox in the test
DOM), then assert that the selection count does not increase past 3 and that any
UI feedback for the limit is shown; reference the MAX_COMPARISON constant and
the useAgentMarketplace selection handler to locate the relevant logic to
validate.

In `@frontend/src/components/agents/AgentMarketplace.tsx`:
- Around line 195-205: The past-work entries in the agent.pastWork map are
rendered as plain text; update the mapping inside AgentMarketplace (the map over
agent.pastWork) so that when a work object has a link (work.link) you render the
title/project line as an anchor <a> instead of plain <p>, preserving the
existing classes (truncate/text styles) and layout, opening in a new tab
(target="_blank") with rel="noopener noreferrer"; keep the same container
structure, key (work.id), and the reward span unchanged so items without
work.link continue to render as before.

In `@frontend/src/components/layout/Sidebar.tsx`:
- Around line 29-39: The navItems array contains a malformed object for the
'Agents' entry: there are two consecutive opening braces causing an unintended
nested object and leaving the following "label: 'Automations'" orphaned; fix it
by removing the extra "{" so the object that contains label: 'Agents', path:
'/agents', and its icon (the SVG) is a single flat object (look for the object
literal that includes label: 'Agents' in the navItems definition and adjust it
so the next entry for 'Automations' is a separate array element).

In `@frontend/src/hooks/useAgentMarketplace.ts`:
- Around line 25-33: The hook useAgentMarketplace currently aliases mockAgents
via the allAgents const and never allows updating agent state; change allAgents
to be state-backed (e.g., useState<Agent[]>(mockAgents)) within
useAgentMarketplace so agents can be updated on hire, add or expose an updater
function (e.g., updateAgentStatus or setAllAgents) that updates an agent by id
to transition its status from "available" to "working" when hiring, and ensure
the hook's returned object includes that updater so AgentMarketplace can call it
after a successful hire; update references to allAgents in the hook to use the
state variable and its setter.

In `@frontend/src/types/agent.ts`:
- Around line 41-46: Document that AgentPricing.hourlyRate must be a
non-negative number by adding a JSDoc comment above the hourlyRate property in
the AgentPricing interface; mention the intended constraint (e.g., "must be a
non-negative number, representing dollars per hour") and optionally note that
validation should be performed at runtime since TypeScript cannot enforce
numeric ranges. Update the JSDoc for the AgentPricing interface and specifically
the hourlyRate field to make this contract clear to implementers.
- Around line 34-39: Update the PerformanceDataPoint interface JSDoc to document
expected formats/ranges so consumers know how to populate month and successRate:
specify the expected month string format (e.g., "YYYY-MM" or "MMM YYYY") and
clarify the successRate scale (e.g., 0–100 percentage or 0–1 ratio); update the
comment above the PerformanceDataPoint interface and annotate the month and
successRate fields in the same JSDoc so code and charting logic using
PerformanceDataPoint (month, bountiesCompleted, successRate) will be consistent
and prevent runtime rendering errors.
- Around line 68-74: Update the AgentFilters interface documentation to clarify
that the minSuccessRate field is a percentage between 0 and 100; specifically,
add a JSDoc comment for the AgentFilters interface or for the minSuccessRate
property (referencing AgentFilters and minSuccessRate) stating "minSuccessRate:
number — percentage (0–100)" so UI code and validation logic know the expected
range.
- Around line 76-82: Update the BountyStub interface to document the constraint
on reward: in the interface comment or as a JSDoc on the reward property (export
interface BountyStub) clearly state that reward must be a non-negative number
(or positive if you prefer strictness) so consumers know invalid negative values
are not allowed; keep the type as number but add the explicit note near the
reward field and, if helpful, mention where runtime validation should occur
(e.g., input/hire flow validation) so the contract is clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a397ee5-6f5d-4df0-b0d1-b073d3987b54

📥 Commits

Reviewing files that changed from the base of the PR and between a176a1a and e6d3d4b.

📒 Files selected for processing (9)
  • frontend/src/App.tsx
  • frontend/src/__tests__/agent-marketplace.test.tsx
  • frontend/src/components/agents/AgentMarketplace.tsx
  • frontend/src/components/agents/agentConstants.ts
  • frontend/src/components/layout/Sidebar.tsx
  • frontend/src/data/mockAgents.ts
  • frontend/src/hooks/useAgentMarketplace.ts
  • frontend/src/pages/AgentMarketplacePage.tsx
  • frontend/src/types/agent.ts

Comment on lines +272 to +352
describe('Agent Marketplace route integration', () => {
it('renders the marketplace page at /agents route', async () => {
render(
<MemoryRouter initialEntries={['/agents']}>
<Routes>
<Route path="/agents" element={<AgentMarketplace />} />
<Route path="*" element={<Navigate to="/agents" replace />} />
</Routes>
</MemoryRouter>,
);

expect(screen.getByTestId('agent-marketplace')).toBeInTheDocument();
expect(
screen.getByRole('heading', { name: /agent marketplace/i }),
).toBeInTheDocument();

// Verify key sections render together
const page = screen.getByTestId('agent-marketplace');
expect(
within(page).getByRole('searchbox', { name: /search agents/i }),
).toBeInTheDocument();
expect(within(page).getByTestId('agent-filters')).toBeInTheDocument();
expect(within(page).getByTestId('register-agent-cta')).toBeInTheDocument();

// Verify all mock agents are rendered
for (const agent of mockAgents) {
expect(
within(page).getByTestId(`agent-card-${agent.id}`),
).toBeInTheDocument();
}
});

it('full page hire flow integration: render, open hire, select bounty, confirm', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });

render(
<MemoryRouter initialEntries={['/agents']}>
<Routes>
<Route path="/agents" element={<AgentMarketplace />} />
</Routes>
</MemoryRouter>,
);

expect(screen.getByTestId('agent-marketplace')).toBeInTheDocument();

// Click hire on first available agent
const hireButtons = screen.getAllByText('Hire Agent');
await userEvent.click(hireButtons[0]);
expect(screen.getByTestId('hire-agent-flow')).toBeInTheDocument();

// Select bounty and confirm
await userEvent.click(screen.getByTestId('bounty-option-bounty-002'));
await userEvent.click(screen.getByTestId('confirm-hire-button'));
expect(screen.getByTestId('hire-success-toast')).toBeInTheDocument();

vi.useRealTimers();
});

it('full page comparison flow integration: select agents, compare, view side-by-side', async () => {
render(
<MemoryRouter initialEntries={['/agents']}>
<Routes>
<Route path="/agents" element={<AgentMarketplace />} />
</Routes>
</MemoryRouter>,
);

const checkboxes = screen.getAllByRole('checkbox');
await userEvent.click(checkboxes[0]);
await userEvent.click(checkboxes[1]);
await userEvent.click(screen.getByTestId('open-comparison-button'));

const comparison = screen.getByTestId('agent-comparison');
expect(comparison).toBeInTheDocument();
// Verify comparison table has rows for metrics
expect(within(comparison).getByText('Success')).toBeInTheDocument();
expect(within(comparison).getByText('Bounties')).toBeInTheDocument();
expect(within(comparison).getByText('Rating')).toBeInTheDocument();
expect(within(comparison).getByText('Price')).toBeInTheDocument();
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

These tests do not validate the actual /agents routing integration.

Line 277, Line 310, and Line 334 all mount AgentMarketplace directly, so they never execute the lazy import at frontend/src/App.tsx Line 11, the route element at Line 19, or the wrapper in frontend/src/pages/AgentMarketplacePage.tsx Lines 5-6. A broken route/import path or missing sidebar integration would still go green here.

As per coding guidelines, frontend/**: React/TypeScript frontend. Check: Integration with existing components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/__tests__/agent-marketplace.test.tsx` around lines 272 - 352,
Tests mount AgentMarketplace directly, skipping the real routing/lazy-import and
wrapper logic in App and AgentMarketplacePage; update these tests to render the
actual router entry (render the App component instead of AgentMarketplace) with
initialEntries=['/agents'] so the lazy import in App and the
AgentMarketplacePage wrapper run, ensuring sidebar and route wiring are
exercised; if App requires providers or context (e.g., RootProvider,
BrowserRouter replacement), render App with the same test providers used by the
app or wrap App accordingly and remove direct Route/<AgentMarketplace /> mounts
so the route element for '/agents' is exercised end-to-end.

Comment on lines +195 to +205
<h3 className="text-sm font-semibold text-gray-300 uppercase mb-2">Past Work</h3>
<div className="space-y-2 mb-6" data-testid="agent-past-work">
{agent.pastWork.map((work) => (
<div key={work.id} className="flex items-center justify-between rounded-lg border border-gray-800 bg-gray-900/50 px-4 py-3">
<div className="min-w-0">
<p className="text-sm text-white truncate">{work.title}</p>
<p className="text-xs text-gray-500">{work.projectName} &middot; {work.completedAt}</p>
</div>
<span className="shrink-0 text-sm font-bold text-[#14F195]">${work.reward.toLocaleString()}</span>
</div>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Render past-work records as links when a URL exists.

Issue #25 calls for past work links, and frontend/src/data/mockAgents.ts Lines 41-48 already provide link on bw-001. Here, Line 200 renders every work item as plain text, so users cannot open the referenced work even when the data is present.

One way to preserve the current layout while surfacing the existing link data
-                  <p className="text-sm text-white truncate">{work.title}</p>
+                  {work.link ? (
+                    <a
+                      href={work.link}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      className="text-sm text-white truncate hover:text-[`#9945FF`]"
+                    >
+                      {work.title}
+                    </a>
+                  ) : (
+                    <p className="text-sm text-white truncate">{work.title}</p>
+                  )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h3 className="text-sm font-semibold text-gray-300 uppercase mb-2">Past Work</h3>
<div className="space-y-2 mb-6" data-testid="agent-past-work">
{agent.pastWork.map((work) => (
<div key={work.id} className="flex items-center justify-between rounded-lg border border-gray-800 bg-gray-900/50 px-4 py-3">
<div className="min-w-0">
<p className="text-sm text-white truncate">{work.title}</p>
<p className="text-xs text-gray-500">{work.projectName} &middot; {work.completedAt}</p>
</div>
<span className="shrink-0 text-sm font-bold text-[#14F195]">${work.reward.toLocaleString()}</span>
</div>
))}
<h3 className="text-sm font-semibold text-gray-300 uppercase mb-2">Past Work</h3>
<div className="space-y-2 mb-6" data-testid="agent-past-work">
{agent.pastWork.map((work) => (
<div key={work.id} className="flex items-center justify-between rounded-lg border border-gray-800 bg-gray-900/50 px-4 py-3">
<div className="min-w-0">
{work.link ? (
<a
href={work.link}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-white truncate hover:text-[`#9945FF`]"
>
{work.title}
</a>
) : (
<p className="text-sm text-white truncate">{work.title}</p>
)}
<p className="text-xs text-gray-500">{work.projectName} &middot; {work.completedAt}</p>
</div>
<span className="shrink-0 text-sm font-bold text-[`#14F195`]">${work.reward.toLocaleString()}</span>
</div>
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/agents/AgentMarketplace.tsx` around lines 195 - 205,
The past-work entries in the agent.pastWork map are rendered as plain text;
update the mapping inside AgentMarketplace (the map over agent.pastWork) so that
when a work object has a link (work.link) you render the title/project line as
an anchor <a> instead of plain <p>, preserving the existing classes
(truncate/text styles) and layout, opening in a new tab (target="_blank") with
rel="noopener noreferrer"; keep the same container structure, key (work.id), and
the reward span unchanged so items without work.link continue to render as
before.

Comment on lines +25 to +33
export function useAgentMarketplace() {
const [filters, setFilters] = useState<AgentFilters>(DEFAULT_FILTERS);
const [sortBy, setSortBy] = useState<AgentSortBy>('successRate');
const [comparisonIds, setComparisonIds] = useState<string[]>([]);
const [comparisonOpen, setComparisonOpen] = useState(false);
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [hiringAgent, setHiringAgent] = useState<Agent | null>(null);

const allAgents = mockAgents;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Back allAgents with writable state instead of a fixed alias.

Line 33 aliases mockAgents directly, and this hook never exposes any way to update an agent after a successful hire. Downstream, frontend/src/components/agents/AgentMarketplace.tsx Lines 405-409 can only show a toast, so the hired agent remains available and can be hired again instead of transitioning to working as required by issue #25.

As per coding guidelines, frontend/**: React/TypeScript frontend. Check: Component structure and state management.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/hooks/useAgentMarketplace.ts` around lines 25 - 33, The hook
useAgentMarketplace currently aliases mockAgents via the allAgents const and
never allows updating agent state; change allAgents to be state-backed (e.g.,
useState<Agent[]>(mockAgents)) within useAgentMarketplace so agents can be
updated on hire, add or expose an updater function (e.g., updateAgentStatus or
setAllAgents) that updates an agent by id to transition its status from
"available" to "working" when hiring, and ensure the hook's returned object
includes that updater so AgentMarketplace can call it after a successful hire;
update references to allAgents in the hook to use the state variable and its
setter.

Comment on lines +34 to +39
/** Monthly performance snapshot rendered as a bar chart in the detail modal. */
export interface PerformanceDataPoint {
month: string;
bountiesCompleted: number;
successRate: number;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document expected formats and ranges.

Two minor typing concerns:

  1. month: string – The chart library likely expects a specific format (e.g., "2024-03" or "Jan 2024"). Document the expected format in a JSDoc comment to prevent runtime rendering errors.

  2. successRate: number – Clarify whether this is a percentage (0–100) or a ratio (0–1). Consider adding a JSDoc comment to document the expected range.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/types/agent.ts` around lines 34 - 39, Update the
PerformanceDataPoint interface JSDoc to document expected formats/ranges so
consumers know how to populate month and successRate: specify the expected month
string format (e.g., "YYYY-MM" or "MMM YYYY") and clarify the successRate scale
(e.g., 0–100 percentage or 0–1 ratio); update the comment above the
PerformanceDataPoint interface and annotate the month and successRate fields in
the same JSDoc so code and charting logic using PerformanceDataPoint (month,
bountiesCompleted, successRate) will be consistent and prevent runtime rendering
errors.

Comment on lines +41 to +46
/** Pricing tier displayed in the agent detail modal. */
export interface AgentPricing {
tier: 'basic' | 'standard' | 'premium';
hourlyRate: number;
description: string;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document hourlyRate constraint.

The hourlyRate: number field should represent a positive value (or at least non-negative). While TypeScript cannot enforce numeric ranges natively, add a JSDoc comment documenting this constraint to guide implementers and avoid negative or zero rates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/types/agent.ts` around lines 41 - 46, Document that
AgentPricing.hourlyRate must be a non-negative number by adding a JSDoc comment
above the hourlyRate property in the AgentPricing interface; mention the
intended constraint (e.g., "must be a non-negative number, representing dollars
per hour") and optionally note that validation should be performed at runtime
since TypeScript cannot enforce numeric ranges. Update the JSDoc for the
AgentPricing interface and specifically the hourlyRate field to make this
contract clear to implementers.

Comment on lines +68 to +74
/** Filter state managed by useAgentMarketplace. */
export interface AgentFilters {
role: AgentRole | 'all';
minSuccessRate: number;
availability: AgentStatus | 'all';
searchQuery: string;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document minSuccessRate range.

The minSuccessRate: number field should be documented as a percentage (0–100) to guide filter UI implementation and validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/types/agent.ts` around lines 68 - 74, Update the AgentFilters
interface documentation to clarify that the minSuccessRate field is a percentage
between 0 and 100; specifically, add a JSDoc comment for the AgentFilters
interface or for the minSuccessRate property (referencing AgentFilters and
minSuccessRate) stating "minSuccessRate: number — percentage (0–100)" so UI code
and validation logic know the expected range.

Comment on lines +76 to +82
/** Bounty stub shown in the hire flow bounty picker. */
export interface BountyStub {
id: string;
title: string;
reward: number;
projectName: string;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document reward constraint.

The reward: number field should be documented as non-negative (likely positive) to prevent invalid bounty data from propagating through the hire flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/types/agent.ts` around lines 76 - 82, Update the BountyStub
interface to document the constraint on reward: in the interface comment or as a
JSDoc on the reward property (export interface BountyStub) clearly state that
reward must be a non-negative number (or positive if you prefer strictness) so
consumers know invalid negative values are not allowed; keep the type as number
but add the explicit note near the reward field and, if helpful, mention where
runtime validation should occur (e.g., input/hire flow validation) so the
contract is clear.

@github-actions
Copy link

✅ Multi-LLM Code Review — APPROVE

Aggregated Score: 6.1/10 (from 3 models)

Model Verdicts

Model Score Verdict
GPT-5.4 5.8/10 ⚠️ REQUEST_CHANGES
Gemini 2.5 Pro 5.8/10 ⚠️ REQUEST_CHANGES
Grok 4 7.3/10 ✅ APPROVE

Category Scores (Averaged)

Category Score
Quality ██████░░░░ 6.3/10
Correctness ██████░░░░ 6.0/10
Security ████████░░ 8.7/10
Completeness ██████░░░░ 6.3/10
Tests ████░░░░░░ 4.3/10
Integration █████░░░░░ 5.7/10

Summary

GPT-5.4: This PR shows a substantial effort toward the agent marketplace feature and does connect to the broader SolFoundry codebase. However, the submission still has structural coherence gaps, incomplete proof of integration, and test coverage that does not fully de-risk the new flows required by the bounty spec.
Gemini 2.5 Pro: This submission provides a functional baseline for the Agent Marketplace but falls short of the quality standard for this bounty. Critical issues in application integration and state management, along with low-quality tests, prevent approval. The core feature set is present but requires significant rework to be considered correct and robust.
Grok 4: This PR delivers a functional agent marketplace with solid integration into the existing codebase, addressing most spec requirements through new UI components and backend tweaks. While tests provide good baseline coverage, identified gaps in edge case validation slightly temper completeness. Overall, it represents production-ready work with minor areas for refinement to boost reliability.

Issues

  • [GPT-5.4] There are structural connectivity issues between some submitted frontend pieces and the application entry path, which weakens confidence that the full marketplace experience is actually reachable in the running product.
  • [GPT-5.4] The test suite does not fully demonstrate application-level routing and interaction coverage, so several important marketplace behaviors remain under-validated.
  • [GPT-5.4] Some acceptance-criteria details appear only partially evidenced, especially around the exact hire/comparison flow and the completeness of the marketplace experience.
  • [GPT-5.4] There are consistency and naming/documentation discipline issues across the broader patch, which makes the submission feel less polished than a production-ready feature drop.
  • [Gemini 2.5 Pro] The state handling for agents appears flawed, which could lead to incorrect UI displays after key user actions like hiring an agent.
  • [Gemini 2.5 Pro] The provided tests do not sufficiently cover application integration or logical edge cases.
  • [Gemini 2.5 Pro] The implementation is missing some details outlined in the bounty specification regarding data display.
  • [Grok 4] Test suites overlook certain conditional branches and integration points like routing.
  • [Grok 4] Some UI elements for dynamic content rendering, such as links in records, lack full implementation fidelity to spec.
  • [Grok 4] State management in hooks relies on static data without provisions for updates post-interaction.

Suggestions

  • [GPT-5.4] Tighten structural integrity by ensuring every submitted artifact is clearly reachable from the main application flow and that the feature is exercised through the same paths users will take in production.
  • [GPT-5.4] Strengthen test depth by covering integration-oriented scenarios and edge cases, not just isolated component behavior, so the core marketplace flows are better de-risked.
  • [GPT-5.4] Revisit the feature against the bounty acceptance criteria as a checklist, with attention to completeness and project coherence rather than just visual presence.
  • [GPT-5.4] Apply a consistency pass over naming, exports, and documentation so the patch reads as a unified extension of the existing codebase.
  • [Gemini 2.5 Pro] Focus on ensuring robust structural integrity and file reachability for all new components within the existing application.
  • [Gemini 2.5 Pro] Re-architect the client-side state management to ensure it is a reliable source of truth that correctly reflects changes.
  • [Gemini 2.5 Pro] Improve test quality by focusing on end-to-end user flows and explicit validation of all conditional logic branches.
  • [Gemini 2.5 Pro] Conduct a gap analysis against the original specification to ensure all requirements are fully met.

Reviewed by SolFoundry Multi-LLM Pipeline: GPT-5.4, Gemini 2.5 Pro, Grok 4

Review profile: frontend — scoring weights adjusted for this domain

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/PRStatusTracker.tsx (1)

333-347: ⚠️ Potential issue | 🟠 Major

Add an active/shouldReconnect flag to prevent state updates after unmount.

The onclose handler can fire after cleanup completes, calling setIsConnected(false) on an unmounted component. While clearTimeout clears the pending reconnect, the state update in onclose (line 334) will trigger a React memory leak warning.

Additionally, if onclose fires before clearTimeout completes, the new timeout from setTimeout(connect, 5000) may persist and trigger a reconnection attempt after the component has unmounted.

Guard the state update and timeout scheduling in onclose with an "active" flag that is set to false during cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/PRStatusTracker.tsx` around lines 333 - 347,
Introduce an "active" or "shouldReconnect" boolean scoped with the WebSocket
logic (set true before calling connect) and check it inside ws.onclose before
calling setIsConnected(false) and before scheduling reconnectTimer =
setTimeout(connect, 5000); set that flag to false in the cleanup returned by the
component so onclose will no-op after unmount, and ensure cleanup still closes
ws and clears reconnectTimer; ensure connect, ws.onclose, and the cleanup all
reference the same flag and that setConnectionError remains unchanged in the
catch block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/auth.py`:
- Around line 53-73: The current auth path accepts raw UUIDs from the bearer
token or X-User-ID header without verifying JWTs; update the authentication
logic in the block handling credentials and x_user_id to perform proper JWT
validation instead of trusting _is_valid_uuid alone: in the branch that reads
credentials.credentials use a JWT library to decode and verify the token
signature (with the project’s secret/public key and expected audience/issuer)
and extract user_id from claims (e.g., "sub" or "user_id"), and in the x_user_id
branch disallow direct acceptance in non-development environments (or require a
validated session/service token) so that only verified identities returned from
the JWT verification are accepted; keep raising HTTPException on failures (the
existing HTTPException and status handling should remain).

In `@backend/app/database.py`:
- Around line 22-23: The code currently uses os.getenv("DATABASE_URL",
"postgresql+asyncpg://postgres:postgres@localhost/solfoundry") which silently
falls back to weak default credentials; change this to require DATABASE_URL
explicitly by reading it without a default (e.g., os.environ["DATABASE_URL"] or
check if os.getenv("DATABASE_URL") is None) and raise a clear startup error
(RuntimeError/SystemExit) if missing so the app fails fast; update any variables
or functions that reference DATABASE_URL (the getenv call and the engine/asyncpg
setup code) to use the validated value.
- Around line 58-67: The docstrings for get_db() and get_db_session() claim
automatic commit/rollback but the code never frames an explicit transaction;
change both helpers to open a transaction with the AsyncSession (use async with
async_session_factory() as session: then async with session.begin(): yield
session) so that successful exits commit and exceptions rollback, referencing
the AsyncSession instance returned by async_session_factory(), get_db, and
get_db_session to locate the changes; ensure the session is still closed after
the transaction.

In `@backend/app/services/webhook_processor.py`:
- Around line 330-333: The queries in webhook_processor that currently reference
BountyDB.github_issue_number and BountyDB.github_repo should be changed to use
the existing BountyDB.github_issue_url (or add the missing model fields);
specifically, update the select(...) calls (the code building the query using
select(BountyDB).where(...)) to compare BountyDB.github_issue_url against the
canonical issue URL (e.g.
f"https://github.com/{github_repo}/issues/{github_issue_number}") or, if you
prefer altering the schema, add github_issue_number and github_repo attributes
to the BountyDB model and migrate accordingly; ensure all other query sites (the
other select(...) that used the same github_issue_number/github_repo pair) are
updated the same way.

In `@backend/tests/conftest.py`:
- Around line 12-19: The _pg_available() helper currently only checks for
asyncpg import and env vars, causing false positives; change it to attempt an
actual connection using asyncpg.connect() (with TEST_DATABASE_URL or
DATABASE_URL) inside an async-to-sync run or using asyncio.run, catch connection
errors (e.g., asyncpg.exceptions.* and OSError) and return True only on
successful connect/close, False on any exception; update the similar gating
logic referenced at lines 22-25 to use the same reachability check so tests
depending on PostgreSQL only run when a real connection succeeds.

In `@frontend/package.json`:
- Line 4: The dependency range for "@solana/web3.js" in package.json currently
allows vulnerable 1.95.6/1.95.7 releases; update the version specifier for the
"@solana/web3.js" entry (in frontend/package.json) to a safe constraint such as
"^1.95.8" or ">=1.95.8 <2" and then regenerate the lockfile (npm/yarn/pnpm
install) so the lockfile matches the tightened constraint.

In `@frontend/src/components/layout/ThemeToggle.tsx`:
- Around line 9-17: The ThemeToggle button is a toggle control but doesn't
expose its state to assistive tech; update the button in the ThemeToggle
component to include an aria-pressed attribute derived from theme (e.g.,
aria-pressed={theme === 'dark'}) so screen readers know whether dark mode is
active, keeping the existing aria-label and onToggle handler intact.

In `@frontend/src/hooks/useLeaderboard.ts`:
- Around line 34-36: The sort uses a generic Record<SortField, keyof
Contributor> and then casts values to number, which can hide future bugs; change
the mapped key type to only numeric Contributor fields (e.g. create a type alias
like NumericContributorKey = { [K in keyof Contributor]: Contributor[K] extends
number ? K : never }[keyof Contributor]) and declare fieldMap as
Record<SortField, NumericContributorKey> so `key` is typed as
NumericContributorKey and you can remove the unsafe `as number` cast in the
comparator (use the typed `key` directly in list.sort((a, b) => b[key] -
a[key])).

In `@frontend/tsconfig.json`:
- Line 1: The CI TypeScript check is skipping test files because
frontend/tsconfig.json uses "exclude" to omit tests; create a new
tsconfig.test.json that extends the existing compilerOptions but removes the
test exclusions (i.e., include "src" and omit the test-specific "exclude"
entries) so test .ts/.tsx files are type-checked, and update CI to run npx tsc
--noEmit --project tsconfig.test.json (or add the new tsconfig to the pipeline)
to ensure tests are validated by tsc.

---

Outside diff comments:
In `@frontend/src/components/PRStatusTracker.tsx`:
- Around line 333-347: Introduce an "active" or "shouldReconnect" boolean scoped
with the WebSocket logic (set true before calling connect) and check it inside
ws.onclose before calling setIsConnected(false) and before scheduling
reconnectTimer = setTimeout(connect, 5000); set that flag to false in the
cleanup returned by the component so onclose will no-op after unmount, and
ensure cleanup still closes ws and clears reconnectTimer; ensure connect,
ws.onclose, and the cleanup all reference the same flag and that
setConnectionError remains unchanged in the catch block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 059d9a28-55aa-4477-b241-5ccc041c719b

📥 Commits

Reviewing files that changed from the base of the PR and between e6d3d4b and da6ae69.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (50)
  • backend/app/api/auth.py
  • backend/app/api/bounties.py
  • backend/app/api/contributors.py
  • backend/app/api/leaderboard.py
  • backend/app/api/notifications.py
  • backend/app/api/payouts.py
  • backend/app/api/webhooks/github.py
  • backend/app/auth.py
  • backend/app/database.py
  • backend/app/main.py
  • backend/app/models/bounty.py
  • backend/app/models/contributor.py
  • backend/app/models/dispute.py
  • backend/app/models/leaderboard.py
  • backend/app/models/notification.py
  • backend/app/models/payout.py
  • backend/app/models/submission.py
  • backend/app/models/user.py
  • backend/app/models/webhook.py
  • backend/app/models/webhook_log.py
  • backend/app/services/auth_service.py
  • backend/app/services/bounty_service.py
  • backend/app/services/contributor_service.py
  • backend/app/services/leaderboard_service.py
  • backend/app/services/notification_service.py
  • backend/app/services/payout_service.py
  • backend/app/services/solana_client.py
  • backend/app/services/treasury_service.py
  • backend/app/services/webhook_processor.py
  • backend/app/services/webhook_service.py
  • backend/tests/conftest.py
  • backend/tests/test_auth.py
  • backend/tests/test_bounties.py
  • backend/tests/test_bounty_api.py
  • backend/tests/test_bounty_edge_cases.py
  • backend/tests/test_bounty_search.py
  • backend/tests/test_contributors.py
  • backend/tests/test_leaderboard.py
  • backend/tests/test_notification_api.py
  • backend/tests/test_payouts.py
  • backend/tests/test_webhook.py
  • frontend/index.html
  • frontend/package.json
  • frontend/src/components/PRStatusTracker.tsx
  • frontend/src/components/layout/Sidebar.tsx
  • frontend/src/components/layout/ThemeToggle.tsx
  • frontend/src/components/wallet/WalletProvider.tsx
  • frontend/src/hooks/useLeaderboard.ts
  • frontend/src/main.tsx
  • frontend/tsconfig.json

Comment on lines +53 to +73
if credentials:
# TODO: Implement JWT token validation
# For now, treat the token as the user ID (development only)
# In production, decode JWT and extract user_id from claims
token = credentials.credentials

# Validate token format (UUID)
if _is_valid_uuid(token):
return token

raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication token",
headers={"WWW-Authenticate": "Bearer"},
)

if x_user_id:
# Validate user ID format
if _is_valid_uuid(x_user_id):
return x_user_id

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Authentication bypass: identity is accepted without token verification.

Lines 53-61 and Lines 69-73 trust client-supplied UUIDs as authenticated identity (from bearer token or X-User-ID). This allows straightforward user impersonation and is a release-blocking auth flaw.

As per coding guidelines, "backend/**: Python FastAPI backend. Analyze thoroughly: Authentication/authorization gaps."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/auth.py` around lines 53 - 73, The current auth path accepts raw
UUIDs from the bearer token or X-User-ID header without verifying JWTs; update
the authentication logic in the block handling credentials and x_user_id to
perform proper JWT validation instead of trusting _is_valid_uuid alone: in the
branch that reads credentials.credentials use a JWT library to decode and verify
the token signature (with the project’s secret/public key and expected
audience/issuer) and extract user_id from claims (e.g., "sub" or "user_id"), and
in the x_user_id branch disallow direct acceptance in non-development
environments (or require a validated session/service token) so that only
verified identities returned from the JWT verification are accepted; keep
raising HTTPException on failures (the existing HTTPException and status
handling should remain).

Comment on lines 22 to 23
"DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost/solfoundry"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast instead of using weak default DB credentials.

Line 22 falls back to postgres:postgres@localhost/..., which can hide misconfiguration and weaken security posture in non-dev deployments. Prefer requiring DATABASE_URL explicitly and failing startup when absent.

As per coding guidelines, "backend/**: Python FastAPI backend. Analyze thoroughly: ... Authentication/authorization gaps ... Error handling and edge case coverage."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/database.py` around lines 22 - 23, The code currently uses
os.getenv("DATABASE_URL",
"postgresql+asyncpg://postgres:postgres@localhost/solfoundry") which silently
falls back to weak default credentials; change this to require DATABASE_URL
explicitly by reading it without a default (e.g., os.environ["DATABASE_URL"] or
check if os.getenv("DATABASE_URL") is None) and raise a clear startup error
(RuntimeError/SystemExit) if missing so the app fails fast; update any variables
or functions that reference DATABASE_URL (the getenv call and the engine/asyncpg
setup code) to use the validated value.

Comment on lines +58 to +67
async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""
FastAPI dependency that provides a database session.

Implements the Unit of Work pattern:
- A new session is created for each request
- The session automatically commits on successful completion
- Any exception triggers automatic rollback
- The session is always properly closed

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In SQLAlchemy 2.x AsyncSession, does async with AsyncSession() as session:automatically commit on successful exit, or only close/rollback uncommitted transactions unlesscommit()(orsession.begin()) is used?

💡 Result:

async with AsyncSession(...) as session: does not auto-commit. It only guarantees the session is closed on exit (equivalent to await session.close()). Any transaction that’s still in progress when the session is closed is ended (i.e., uncommitted work is not persisted). [1], [2]

To get “commit on success / rollback on error”, you must explicitly frame a transaction, e.g. async with session.begin(): ... (or use async_sessionmaker(...).begin() / Session.begin()), which commits on normal exit and rolls back on exception. [2]

Sources: SQLAlchemy asyncio docs for AsyncSession context manager behavior [1]; Session transaction framing (begin() context manager) [2]; Session close() semantics (ends transaction in progress) [3].


🏁 Script executed:

# First, let's find and read the backend/app/database.py file
fd -t f "database.py" backend/

# Get file info
wc -l backend/app/database.py

# Read the relevant sections
sed -n '55,75p' backend/app/database.py
sed -n '100,115p' backend/app/database.py

Repository: SolFoundry/solfoundry

Length of output: 1147


🏁 Script executed:

# Read the get_db_session function
sed -n '105,120p' backend/app/database.py

Repository: SolFoundry/solfoundry

Length of output: 517


🏁 Script executed:

# Search for get_db_session function definition
rg -n "def get_db_session" backend/app/database.py

# Also search for all function definitions to see what's around line 105
rg -n "^async def\|^def " backend/app/database.py

Repository: SolFoundry/solfoundry

Length of output: 95


🏁 Script executed:

# Read around get_db_session at line 90 and beyond
sed -n '85,125p' backend/app/database.py

Repository: SolFoundry/solfoundry

Length of output: 1278


Missing explicit commits in async session helpers despite documentation claiming automatic commit.

Both get_db() (lines 58–73) and get_db_session() (lines 90–107) include docstring and inline comments stating automatic commit behavior ("automatically commits on successful completion", "Auto-commits on exit"), but neither function executes await session.commit() on successful exit. The async with async_session_factory() as session: context manager only closes the session without committing; it does not persist uncommitted changes.

SQLAlchemy 2.x AsyncSession requires explicit transaction framing (e.g., async with session.begin():) to achieve auto-commit semantics on success and rollback on exception. Without this, callers relying on these helpers for Unit of Work semantics will experience dropped writes.

Add explicit transaction handling to both functions to match documented behavior, or update docstrings to clarify that callers must manage commits explicitly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/database.py` around lines 58 - 67, The docstrings for get_db()
and get_db_session() claim automatic commit/rollback but the code never frames
an explicit transaction; change both helpers to open a transaction with the
AsyncSession (use async with async_session_factory() as session: then async with
session.begin(): yield session) so that successful exits commit and exceptions
rollback, referencing the AsyncSession instance returned by
async_session_factory(), get_db, and get_db_session to locate the changes;
ensure the session is still closed after the transaction.

Comment on lines 330 to 333
query = select(BountyDB).where(
BountyDB.github_issue_number == github_issue_number,
BountyDB.github_repo == github_repo,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check BountyDB model definition for github_issue_number and github_repo fields
rg -n "github_issue_number|github_repo" backend/app/models/bounty.py

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

fd -e py -path "*/models*" backend | head -20

Repository: SolFoundry/solfoundry

Length of output: 233


🏁 Script executed:

cat -n backend/app/models/bounty.py | head -50

Repository: SolFoundry/solfoundry

Length of output: 1704


🏁 Script executed:

sed -n '325,340p' backend/app/services/webhook_processor.py

Repository: SolFoundry/solfoundry

Length of output: 602


🏁 Script executed:

rg -n "class BountyDB" backend/

Repository: SolFoundry/solfoundry

Length of output: 124


🏁 Script executed:

rg -n "github_issue_number" backend/

Repository: SolFoundry/solfoundry

Length of output: 1452


🏁 Script executed:

sed -n '167,200p' backend/app/models/bounty.py

Repository: SolFoundry/solfoundry

Length of output: 1313


Fix BountyDB field references in webhook processor queries.

The webhook processor queries BountyDB.github_issue_number and BountyDB.github_repo (lines 330-333, 368-371), but the BountyDB model only has github_issue_url. This will cause runtime errors when attempting to filter bounties by issue and repo. Update the query logic to use the correct field names or add the missing fields to the BountyDB model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/webhook_processor.py` around lines 330 - 333, The
queries in webhook_processor that currently reference
BountyDB.github_issue_number and BountyDB.github_repo should be changed to use
the existing BountyDB.github_issue_url (or add the missing model fields);
specifically, update the select(...) calls (the code building the query using
select(BountyDB).where(...)) to compare BountyDB.github_issue_url against the
canonical issue URL (e.g.
f"https://github.com/{github_repo}/issues/{github_issue_number}") or, if you
prefer altering the schema, add github_issue_number and github_repo attributes
to the BountyDB model and migrate accordingly; ensure all other query sites (the
other select(...) that used the same github_issue_number/github_repo pair) are
updated the same way.

Comment on lines +12 to +19
def _pg_available() -> bool:
"""Check if PostgreSQL is reachable (best-effort)."""
try:
import asyncpg # noqa: F401

return bool(os.getenv("TEST_DATABASE_URL") or os.getenv("DATABASE_URL"))
except ImportError:
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Postgres gating check can produce false positives and flaky test runs.

_pg_available() currently checks import + env presence, not actual connectivity, while the docstring claims reachability. This may run PG-dependent tests in environments where DB is configured but unavailable.

As per coding guidelines, "backend/**: Python FastAPI backend. Analyze thoroughly: Error handling and edge case coverage."

Also applies to: 22-25

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/conftest.py` around lines 12 - 19, The _pg_available() helper
currently only checks for asyncpg import and env vars, causing false positives;
change it to attempt an actual connection using asyncpg.connect() (with
TEST_DATABASE_URL or DATABASE_URL) inside an async-to-sync run or using
asyncio.run, catch connection errors (e.g., asyncpg.exceptions.* and OSError)
and return True only on successful connect/close, False on any exception; update
the similar gating logic referenced at lines 22-25 to use the same reachability
check so tests depending on PostgreSQL only run when a real connection succeeds.

"name": "solfoundry-frontend", "private": true, "type": "module",
"scripts": { "dev": "vite", "build": "tsc && vite build", "test": "vitest run" },
"dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.23.1" },
"dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.23.1", "@solana/wallet-adapter-react": "^0.15.35", "@solana/wallet-adapter-wallets": "^0.19.32", "@solana/wallet-adapter-base": "^0.9.23", "@solana/web3.js": "^1.95.0" },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Detected lockfiles:"
fd 'package-lock\.json|pnpm-lock\.yaml|yarn\.lock' -HI || true

echo
echo "Search for `@solana/web3.js` resolved entries and vulnerable patches (1.95.6 / 1.95.7):"
rg -n -C1 '@solana/web3\.js|1\.95\.(6|7)\b' -g 'package-lock.json' -g 'pnpm-lock.yaml' -g 'yarn.lock' || true

echo
echo "Expected result: no resolved `@solana/web3.js` version 1.95.6 or 1.95.7."

Repository: SolFoundry/solfoundry

Length of output: 10043


🌐 Web query:

@solana/web3.js 1.95.6 1.95.7 compromised vulnerable exploit

💡 Result:

Yes—@solana/web3.js versions 1.95.6 and 1.95.7 were compromised in a supply-chain attack (malicious npm publish) that exfiltrated private-key material, potentially enabling wallet draining for apps/bots/backends that handle private keys directly. The Solana protocol itself was not the issue, and the advisory notes this should not affect non-custodial wallets (which generally don’t expose private keys to the library). The malicious releases were unpublished and a patched version 1.95.8 was released. The Solana team’s advisory specifically calls out an affected update window of Dec 3, 2024 15:20–20:25 UTC. [1]

What to do

  • If you’re on 1.95.6 or 1.95.7: upgrade immediately to >= 1.95.8. [1]
  • If you may have installed/updated during the window: assume exposure and rotate any potentially loaded keys (server keypairs, bot hot wallets, program authorities, multisigs, etc.). [1]
  • Verify exposure: check package-lock.json / yarn.lock / pnpm-lock.yaml and your CI/build logs/artifacts to see whether 1.95.6/1.95.7 was actually resolved and deployed.

What the malware did (high level)

Reports describe obfuscated backdoor logic that captured key material and exfiltrated it to attacker-controlled infrastructure, targeting backend/bot use cases. [2]

Sources: GitHub security advisory (CVE-2024-54134 / GHSA-jcxm-7wvp-g6p5) [1], Wiz incident summary [2].


Tighten @solana/web3.js version constraint to explicitly exclude compromised releases.

The range ^1.95.0 in package.json line 4 permits versions 1.95.6 and 1.95.7, which were compromised in a supply-chain attack (CVE-2024-54134, unpublished). While the lockfile correctly resolves to 1.98.4, the permissive range in package.json is not best practice. Enforce an explicit minimum: "@solana/web3.js": "^1.95.8" or ">=1.95.8 <2" to prevent accidental installation of the vulnerable window if the lockfile is regenerated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/package.json` at line 4, The dependency range for "@solana/web3.js"
in package.json currently allows vulnerable 1.95.6/1.95.7 releases; update the
version specifier for the "@solana/web3.js" entry (in frontend/package.json) to
a safe constraint such as "^1.95.8" or ">=1.95.8 <2" and then regenerate the
lockfile (npm/yarn/pnpm install) so the lockfile matches the tightened
constraint.

Comment on lines +9 to +17
<button
type="button"
onClick={onToggle}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg
text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200
hover:bg-gray-100 dark:hover:bg-gray-800
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500"
aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Expose toggle state with aria-pressed on the button.

On Line 9-Line 17, this control behaves like a toggle but does not expose its current state to assistive technologies. Add aria-pressed derived from theme so screen readers can announce whether dark mode is currently active.

As per coding guidelines, frontend/** requires checking accessibility-impacting behavior, and this currently misses state semantics for a toggle control.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/layout/ThemeToggle.tsx` around lines 9 - 17, The
ThemeToggle button is a toggle control but doesn't expose its state to assistive
tech; update the button in the ThemeToggle component to include an aria-pressed
attribute derived from theme (e.g., aria-pressed={theme === 'dark'}) so screen
readers know whether dark mode is active, keeping the existing aria-label and
onToggle handler intact.

Comment on lines +34 to +36
const fieldMap: Record<SortField, keyof Contributor> = { points: 'points', bounties: 'bountiesCompleted', earnings: 'earningsFndry' };
const key = fieldMap[sortBy];
list.sort((a, b) => (b[key] as number) - (a[key] as number));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Strengthen sort-key typing to avoid silent comparator breakage.

Line 34 uses Record<SortField, keyof Contributor>, and Line 36 relies on as number casts. That combination permits accidental mapping to non-numeric Contributor fields in the future, which would degrade sorting into NaN comparisons at runtime. Please constrain the mapped key type to numeric contributor fields so this remains compile-time safe without numeric casts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/hooks/useLeaderboard.ts` around lines 34 - 36, The sort uses a
generic Record<SortField, keyof Contributor> and then casts values to number,
which can hide future bugs; change the mapped key type to only numeric
Contributor fields (e.g. create a type alias like NumericContributorKey = { [K
in keyof Contributor]: Contributor[K] extends number ? K : never }[keyof
Contributor]) and declare fieldMap as Record<SortField, NumericContributorKey>
so `key` is typed as NumericContributorKey and you can remove the unsafe `as
number` cast in the comparator (use the typed `key` directly in list.sort((a, b)
=> b[key] - a[key])).

@@ -1 +1 @@
{ "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "noEmit": true, "skipLibCheck": true, "isolatedModules": true }, "include": ["src"] }
{ "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "noEmit": true, "skipLibCheck": true, "isolatedModules": true }, "include": ["src"], "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/__tests__"] }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether test files are type-checked through an alternate TS config or CI command.
# Expected: either a dedicated tsconfig for tests OR CI/npm scripts running tsc against tests.

set -euo pipefail

echo "== tsconfig-like files =="
fd -HI 'tsconfig.*\.json$'

echo
echo "== package scripts mentioning tsc/vitest/test =="
fd -HI 'package\.json$' --exec sh -c '
  for f in "$@"; do
    echo "-- $f"
    jq -r ".scripts // {}" "$f" | jq -r "to_entries[] | \"\(.key): \(.value)\"" | rg -n "tsc|vitest|test" || true
  done
' sh {}

echo
echo "== CI/workflow commands mentioning tsc/vitest/test =="
fd -HI '\.ya?ml$' .github --exec rg -n "tsc|vitest|npm run test|pnpm test|yarn test" {} || true

Repository: SolFoundry/solfoundry

Length of output: 637


🏁 Script executed:

fd -HI '\.ya?ml$' .github --exec cat {} \;

Repository: SolFoundry/solfoundry

Length of output: 50379


🏁 Script executed:

cat frontend/package.json

Repository: SolFoundry/solfoundry

Length of output: 782


Test type-safety is not validated by the main TypeScript compiler (tsc) in CI.

The exclude rules in frontend/tsconfig.json prevent npx tsc --noEmit (line 140 in CI) from type-checking test files. While Vitest provides implicit TypeScript support during test execution, test files are not pre-validated by tsc, which can allow type errors in unused code paths or import issues to slip through until runtime.

Consider adding a separate tsconfig.test.json that extends the main config without excluding tests, or adding an explicit npx tsc --noEmit --project tsconfig.test.json step to the CI pipeline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/tsconfig.json` at line 1, The CI TypeScript check is skipping test
files because frontend/tsconfig.json uses "exclude" to omit tests; create a new
tsconfig.test.json that extends the existing compilerOptions but removes the
test exclusions (i.e., include "src" and omit the test-specific "exclude"
entries) so test .ts/.tsx files are type-checked, and update CI to run npx tsc
--noEmit --project tsconfig.test.json (or add the new tsconfig to the pipeline)
to ensure tests are validated by tsc.

…dry#25)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ItachiDevv ItachiDevv force-pushed the fix/issue-25-agent-marketplace branch from da6ae69 to bf84642 Compare March 20, 2026 11:59
@github-actions
Copy link

✅ Multi-LLM Code Review — APPROVE

Aggregated Score: 6.7/10 (from 3 models)

Model Verdicts

Model Score Verdict
GPT-5.4 6.4/10 ⚠️ REQUEST_CHANGES
Gemini 2.5 Pro 6.4/10 ⚠️ REQUEST_CHANGES
Grok 4 7.3/10 ✅ APPROVE

Category Scores (Averaged)

Category Score
Quality ██████░░░░ 6.3/10
Correctness ███████░░░ 7.0/10
Security █████████░ 9.0/10
Completeness ██████░░░░ 6.0/10
Tests █████░░░░░ 5.0/10
Integration ███████░░░ 7.0/10

Summary

GPT-5.4: This is a solid first pass at the marketplace feature and it does integrate into the existing frontend. However, the submission falls short of the bounty spec in a few important areas, especially around route-level validation, past-work presentation, and the depth of test coverage for core flows and edge cases.
Gemini 2.5 Pro: The submission provides a functional marketplace UI that covers many of the bounty requirements. However, it falls short of the quality bar due to significant architectural issues, incomplete features, and a lack of robust testing, preventing approval at this time.
Grok 4: This submission delivers a functional agent marketplace with strong frontend structure and good integration into the existing app. It covers key features effectively but falls short on a few spec details and test thoroughness. Overall, it's a solid Tier 2 contribution that meets the minimum review threshold.

Issues

  • [GPT-5.4] There are spec-completeness gaps in the agent detail experience, especially around how past work is presented and whether the marketplace content fully matches the requested browsing and hiring workflow.
  • [GPT-5.4] The test suite is useful but still leans toward component rendering rather than validating the actual application routing and integration paths, which weakens confidence in real-world behavior.
  • [GPT-5.4] Some interaction logic around comparison and hiring feels under-validated, so the submission does not yet demonstrate strong coverage of boundary conditions and state transitions.
  • [Gemini 2.5 Pro] The primary feature component is architecturally monolithic, which deviates from established project structure and best practices.
  • [Gemini 2.5 Pro] There are gaps in feature completeness when compared against the bounty specification, particularly regarding data presentation in the agent detail view.
  • [Gemini 2.5 Pro] The test suite does not adequately cover edge conditions or alternative user flows.
  • [Gemini 2.5 Pro] The submission shows a lack of project coherence by not leveraging existing or expected patterns for shared data structures and type definitions.
  • [Grok 4] Gaps in rendering dynamic content like past work elements to support interactivity.
  • [Grok 4] Incomplete test validation for boundary conditions in selection mechanisms.
  • [Grok 4] State management in hooks that limits post-action updates without external mutability.

Suggestions

  • [GPT-5.4] Strengthen structural integrity by validating the feature through the app’s real navigation and entry points, not only isolated component mounts.
  • [GPT-5.4] Improve spec traceability by reviewing each acceptance criterion against the implemented UI states and ensuring the marketplace experience reflects the requested browsing, comparison, and hire flow end to end.
  • [GPT-5.4] Expand test coverage toward edge cases and state transitions so the behavior is demonstrated under more than just the happy path.
  • [GPT-5.4] Tighten naming and presentation consistency across the marketplace UI so the feature reads as a cohesive extension of the existing product rather than a standalone demo.
  • [Gemini 2.5 Pro] Consider refactoring the implementation to better align with the principle of separation of concerns, potentially breaking down the feature into smaller, more focused components and hooks.
  • [Gemini 2.5 Pro] A thorough review of the bounty requirements is needed to ensure all specified features, such as data visualization and interactive elements, are fully implemented.
  • [Gemini 2.5 Pro] To improve code robustness, the test suite should be expanded to include boundary conditions and assertions for negative test cases.
  • [Gemini 2.5 Pro] Improve structural integrity by abstracting shared concerns like type definitions and mock data into modules that can be used across the project.

Reviewed by SolFoundry Multi-LLM Pipeline: GPT-5.4, Gemini 2.5 Pro, Grok 4

Review profile: frontend — scoring weights adjusted for this domain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant