Skip to content

feat: manual pr review via x402 integration#88

Open
LennyMalcolm0 wants to merge 5 commits intomainfrom
dev
Open

feat: manual pr review via x402 integration#88
LennyMalcolm0 wants to merge 5 commits intomainfrom
dev

Conversation

@LennyMalcolm0
Copy link
Copy Markdown
Member

@LennyMalcolm0 LennyMalcolm0 commented Apr 7, 2026

Closes #86

Summary by CodeRabbit

  • New Features

    • Agent endpoint to submit a PR for manual analysis (returns a task ID).
    • Optional paywall for agent reviews via x402/Stellar.
  • Updates

    • CORS now exposes the "Payment-Required" response header.
    • Local development origins expanded for testing.
  • Chores

    • Project moved to ESM; tests migrated to Vitest.
    • Added a dedicated manual-pr-analysis background job.

@devasign-app
Copy link
Copy Markdown

devasign-app bot commented Apr 7, 2026

Merge Score: 65/100

🔴 █████████████░░░░░░░ 65%

The PR introduces a new manual PR review route integrated with x402 for payments. However, the change to NodeNext in tsconfig.json will break the build because static imports lack .js extensions. Additionally, missing environment variables will crash the app on startup instead of gracefully disabling the route.

Code Suggestions (3)

High Priority (1)

  1. tsconfig.json (Line 4)
    The change to NodeNext in tsconfig.json requires all relative imports in .ts files to include explicit .js extensions. While dynamic imports in test files were updated, static imports across the codebase were not, which will cause TypeScript compilation to fail (TS2835).

Reasoning: TypeScript's NodeNext module resolution strictly enforces explicit file extensions for relative imports. Unless @x402 packages strictly require ESM and NodeNext, it is safer to revert this change. If NodeNext is required, you must update all relative imports across the entire project to include .js.

Suggested Code:

{
    "compilerOptions": {
        "target": "ES2020",
        "module": "CommonJS",
        "lib": [
            "ES2020"
        ],
        "outDir": "./dist",
        "rootDir": "./",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "types": [
            "node",
            "jest"
        ]
    }
}

Medium Priority (1)

  1. api/routes/agent.route.ts (Line 15)
    Throwing an error at the module level for missing environment variables will crash the application on startup if they are not set. It is safer to log a warning and disable the routes.

Reasoning: In environments where this feature is not configured (e.g., local development, CI/CD, or specific deployments), the entire application will fail to start. Graceful degradation improves maintainability and deployment stability.

Suggested Code:

export const agentRoutes = Router();

if (!facilitatorUrl || !payTo || !facilitatorApiKey) {
    console.warn("Missing required x402 environment variables. Agent routes are disabled.");
} else {
    const facilitatorClient = new HTTPFacilitatorClient({
        url: facilitatorUrl,
        createAuthHeaders: async () => {
            const headers = { Authorization: `Bearer ${facilitatorApiKey}` };
            return { verify: headers, settle: headers, supported: headers };
        }
    });

    agentRoutes.use(
        paymentMiddlewareFromConfig(
            {
                [`POST ${ENDPOINTS.AGENT.REVIEW}`]: {
                    accepts: {
                        scheme: "exact",
                        price: "$0.50",
                        network,
                        payTo
                    },
                    description: "Pull request review",
                    mimeType: "application/json"
                }
            } as RoutesConfig,
            facilitatorClient,
            [{ network, server: new ExactStellarScheme() }]
        )
    );

    agentRoutes.post(
        ENDPOINTS.AGENT.REVIEW,
        (async (req: Request, res: Response, next: NextFunction) => {
            try {
                const { prUrl } = req.body;

                const githubPrRegex = /^https?:\/\/(www\.)?github\.com\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+\/pull\/\d+\/?.*$/;
                if (!prUrl || !githubPrRegex.test(prUrl)) {
                    throw new ValidationError("Please provide a valid GitHub Pull Request URL.");
                }

                const taskId = await cloudTasksService.addManualPRAnalysisJob(prUrl);

                responseWrapper({
                    res,
                    status: STATUS_CODES.SUCCESS,
                    data: { success: true, taskId },
                    message: "Review in progress. A comment will be posted by our bot."
                });
            } catch (error) {
                next(error);
            }
        }) as unknown as RequestHandler
    );
}

Low Priority (1)

  1. api/routes/agent.route.ts (Line 56)
    Improve the GitHub PR URL regex to extract the exact components and avoid passing unvalidated trailing characters to the background job.

Reasoning: The current regex allows any trailing characters (.*) after the PR number. Extracting the clean URL or components prevents potential parsing issues or SSRF vectors in the background worker.

Suggested Code:

                const match = prUrl.match(/^https?:\/\/(?:www\.)?github\.com\/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\/pull\/(\d+)/);
                if (!match) {
                    throw new ValidationError("Please provide a valid GitHub Pull Request URL.");
                }
                // Optionally reconstruct a clean URL to pass to the job
                const cleanPrUrl = match[0];
                const taskId = await cloudTasksService.addManualPRAnalysisJob(cleanPrUrl);
📊 Review Metadata
  • Processing Time: 67s
  • Analysis Date: 4/7/2026, 4:02:06 PM

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

💬 Questions about this review? Open an issue or contact support.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa6ba29b-4780-4402-a50e-838ea5f59cbc

📥 Commits

Reviewing files that changed from the base of the PR and between 637645c and 79111fe.

📒 Files selected for processing (1)
  • api/routes/agent.route.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/routes/agent.route.ts

📝 Walkthrough

Walkthrough

Adds a paid PR-review endpoint (/agent/review) with x402/Stellar payment middleware, enqueues a manual PR analysis Cloud Task, extends Cloud Tasks job types/endpoints, adjusts CORS exposure, migrates many local imports to explicit ESM .js specifiers, and replaces Jest with Vitest/test config updates.

Changes

Cohort / File(s) Summary
Agent route & mount
api/routes/agent.route.ts, api/routes/index.ts, api/index.ts
New agentRoutes router and mount at ENDPOINTS.AGENT.PREFIX; POST /agent/review validates PR URL, requires x402 exact $0.50 payment (middleware), enqueues manual PR analysis job, and returns taskId. Re-export added.
Cloud Tasks: manual PR analysis
api/services/cloud-tasks.service.ts, api/utilities/data.ts
Added manual-pr-analysis JobType, ManualPRAnalysisData payload, env/queue mapping, 600s dispatch deadline, addManualPRAnalysisJob(prUrl) method, and ENDPOINTS.INTERNAL.MANUAL_PR_ANALYSIS.
Payment middleware & config checks
api/routes/agent.route.ts, api/services/error-handler.service.ts, api/utilities/data.ts
Integrates x402 facilitator client when X402_* env vars present; registers paymentMiddlewareFromConfig for the review route; added env validation for X402 vars and CLOUD_TASKS_MANUAL_PR_ANALYSIS_QUEUE.
CORS tweak
api/index.ts
CORS config updated to include exposedHeaders: ["Payment-Required"].
ESM import path migration (code)
api/** (controllers, services, models, middlewares, routes, schemas, config) — e.g. api/controllers/..., api/services/..., api/middlewares/..., api/routes/...
Widespread change of local imports to explicit .js entrypoints and prisma_client/index.js for ESM/NodeNext resolution; mostly non-functional path updates.
Octokit graphql type
api/models/github.model.ts
Repointed InstallationOctokit.graphql type to import("@octokit/graphql/types").graphql.
ESM & test tooling migration
package.json, tsconfig.json, vitest.config.ts, jest.config.js (deleted), tests/**
Switched project to ESM ("type":"module"), replaced Jest with Vitest (new scripts and vitest.config.ts, removed jest.config.js), adjusted tsconfig module/resolution, and migrated many tests from Jest → Vitest and to .js import specifiers.
Firebase / test mocks refactor
tests/mocks/firebase.service.mock.ts, many tests/integration/** and tests/helpers/**
Reworked Firebase and other service mocks to use Vitest (vi.hoisted, vi.mock, vi.fn) and adjusted many tests to use Vitest globals and .js specifiers; several large test files updated.
Minor config/service imports
api/config/*.ts, api/services/*.ts, api/controllers/*.ts, api/middlewares/*.ts
Various files updated to use .js suffixes for local imports and small typings/usage adjustments (e.g., Stellar/statsig imports, axios typing changes).

Sequence Diagram

sequenceDiagram
    participant Client
    participant AgentRoute as Agent Route Handler
    participant X402MW as x402 Payment Middleware
    participant Facilitator as x402 Facilitator
    participant Stellar as Stellar Network
    participant CloudTasks as Cloud Tasks Service
    participant Queue as Cloud Tasks Queue

    Client->>AgentRoute: POST /agent/review { prUrl }
    AgentRoute->>X402MW: pass request through payment middleware
    X402MW->>Facilitator: initiate payment ($0.50, exact)
    Facilitator->>Stellar: submit Stellar transaction
    Stellar-->>Facilitator: confirmation
    Facilitator-->>X402MW: payment verified
    X402MW-->>AgentRoute: middleware clears
    AgentRoute->>AgentRoute: validate prUrl
    AgentRoute->>CloudTasks: addManualPRAnalysisJob(prUrl)
    CloudTasks->>Queue: enqueue "manual-pr-analysis" task
    Queue-->>CloudTasks: returns taskId
    CloudTasks-->>AgentRoute: taskId
    AgentRoute-->>Client: 200 { taskId, status: "success" }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hopped in with a tiny coin in paw,
I guarded PRs with a Stellar law,
Middleware checked the silver light,
I queued a job and watched it flight —
Hooray! A paid review made me thaw 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Extensive out-of-scope changes detected: systematic ESM migration (.js extensions, tsconfig/package.json modifications), Jest-to-Vitest migration (jest.config.js deletion, vitest.config.ts addition), and Firebase config import refactoring unrelated to x402 integration requirements. Separate the x402/manual PR review feature (agent.route.ts, utilities/data.ts, cloud-tasks.service.ts changes) into a dedicated PR from the ESM/test framework migrations to maintain focused, reviewable changesets.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: manual pr review via x402 integration' accurately describes the main change: adding manual pull request review functionality through x402 integration for payment-per-use reviews.
Linked Issues check ✅ Passed The PR implements the requirement from issue #86 by adding x402 integration [agent.route.ts, package.json], Stellar network support [agent.route.ts, utilities/data.ts], and a new pay-per-use PR review endpoint [agent.route.ts, routes/index.ts].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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

Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (1)
api/routes/agent.route.ts (1)

50-73: Consider extracting the async handler wrapper pattern.

The as unknown as RequestHandler cast is a common workaround for Express's lack of native async handler support. This pattern appears to be manually applied here. If this pattern is used elsewhere in the codebase, consider extracting it into a reusable wrapper.

♻️ Optional: Create a reusable async handler wrapper
// In a utilities file
type AsyncRequestHandler = (req: Request, res: Response, next: NextFunction) => Promise<void>;

export const asyncHandler = (fn: AsyncRequestHandler): RequestHandler =>
    (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

Then usage becomes cleaner:

agentRoutes.post(
    ENDPOINTS.AGENT.REVIEW,
    asyncHandler(async (req, res, next) => {
        // ... handler code
    })
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/routes/agent.route.ts` around lines 50 - 73, The route handler in
agentRoutes.post uses the repetitive "as unknown as RequestHandler" cast to
support async handlers; extract a reusable async wrapper (e.g., asyncHandler)
that accepts an AsyncRequestHandler ((req, res, next) => Promise<void>) and
returns a RequestHandler which calls the async fn and catches errors to call
next(err). Replace the cast in agentRoutes.post by passing the async handler
through asyncHandler (instead of the "as unknown as RequestHandler" pattern) so
the code is cleaner and reusable across other routes that use async handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/routes/agent.route.ts`:
- Around line 56-59: Replace the vulnerable githubPrRegex in
api/routes/agent.route.ts (the githubPrRegex constant) with a safer pattern that
enforces HTTPS, removes the unnecessary (www\.) and the trailing `.*$`
backtracking part, and allows an optional trailing slash; e.g. use a pattern
like ^https://github\.com/[\w.-]+/[\w.-]+/pull/\d+/?$ to validate PR URLs and
keep the existing ValidationError logic unchanged.

In `@api/services/cloud-tasks.service.ts`:
- Line 83: The new config key "manual-pr-analysis" uses
CLOUD_TASKS_MANUAL_PR_ANALYSIS_QUEUE but that env var isn't validated in the
requiredVars list; update the environment validation (the requiredVars array
used by the init/config function that currently checks other CLOUD_TASKS_* vars)
to include "CLOUD_TASKS_MANUAL_PR_ANALYSIS_QUEUE" so startup will error or warn
if it's missing and prevent empty-string runtime failures when enqueuing manual
PR analysis tasks.

In `@tests/integration/api/wallet/index.api.test.ts`:
- Line 68: The test's Jest mock and the dynamic import refer to different module
specifiers so the mock is bypassed; update them to match by either changing the
mock registration for the Stellar module to include the .js extension (i.e.,
mock "../../../../api/services/stellar.service.js") or change the dynamic import
to import "../../../../api/services/stellar.service" so the module specifier
matches; ensure the mocked export name stellarService is what the test expects
and that jest.mock targets the exact same module string used by the dynamic
import.

In `@tsconfig.json`:
- Line 4: tsconfig.json changed "module" to "NodeNext" but the repo is missing
the surrounding ESM config; add "type": "module" to package.json and update Jest
config so ESM test files are handled: set extensionsToTreatAsEsm to include
".ts" and configure ts-jest with useESM: true (and ensure jest.config.js uses
those options) so dynamic imports like
import("../../../api/config/firebase.config.js") resolve correctly under
NodeNext.

---

Nitpick comments:
In `@api/routes/agent.route.ts`:
- Around line 50-73: The route handler in agentRoutes.post uses the repetitive
"as unknown as RequestHandler" cast to support async handlers; extract a
reusable async wrapper (e.g., asyncHandler) that accepts an AsyncRequestHandler
((req, res, next) => Promise<void>) and returns a RequestHandler which calls the
async fn and catches errors to call next(err). Replace the cast in
agentRoutes.post by passing the async handler through asyncHandler (instead of
the "as unknown as RequestHandler" pattern) so the code is cleaner and reusable
across other routes that use async handlers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67907463-5219-4ae7-a819-695901be7b7f

📥 Commits

Reviewing files that changed from the base of the PR and between 8b9c05d and a93de6a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • api/index.ts
  • api/models/github.model.ts
  • api/routes/agent.route.ts
  • api/routes/index.ts
  • api/services/cloud-tasks.service.ts
  • api/utilities/data.ts
  • package.json
  • tests/integration/api/installation/github.api.test.ts
  • tests/integration/api/installation/index.api.test.ts
  • tests/integration/api/installation/team.api.test.ts
  • tests/integration/api/internal/index.api.test.ts
  • tests/integration/api/task/activities.api.test.ts
  • tests/integration/api/task/contributor.api.test.ts
  • tests/integration/api/task/index.api.test.ts
  • tests/integration/api/task/installation.api.test.ts
  • tests/integration/api/task/{taskId}.api.test.ts
  • tests/integration/api/user/index.api.test.ts
  • tests/integration/api/wallet/index.api.test.ts
  • tests/integration/api/wallet/transactions.api.test.ts
  • tests/integration/api/webhook/github.api.test.ts
  • tests/integration/middlewares/auth.middleware.test.ts
  • tsconfig.json

@devasign-app
Copy link
Copy Markdown

devasign-app bot commented Apr 8, 2026

Updated Merge Score: 85/100

🟢 █████████████████░░░ 85%

The author successfully addressed the NodeNext module resolution issue by adding .js extensions to all relative imports. The PR URL regex was also improved, and the testing framework was successfully migrated from Jest to Vitest. However, the graceful degradation concern remains partially unaddressed: while the explicit throw was removed from the route file, the x402 environment variables were added to the required variables list in the error handler, which will still crash the app on startup if missing. The x402 client is also instantiated unconditionally. Moving these to warning variables and conditionally mounting the route will fully resolve the issue.

Code Suggestions (2)

High Priority (1)

  1. api/services/error-handler.service.ts (Line 152)
    Move x402 environment variables from required to warning variables to prevent startup crashes.

Reasoning: Adding the x402 environment variables to the required variables list in ErrorHandlerService will cause the entire application to crash on startup if they are missing. Since manual PR reviews are an optional feature, missing these variables should only log a warning and gracefully disable the specific route.

Suggested Code:

        const warningVars = [
            { keys: ["X402_FACILITATOR_URL", "X402_PAYEE_ADDRESS", "X402_API_KEY"], msg: "x402 misconfiguration - manual PR review agent will be disabled" },
            // ... existing warningVars
        ];

Medium Priority (1)

  1. api/routes/agent.route.ts (Line 15)
    Conditionally initialize the x402 client and routes to avoid runtime errors when environment variables are missing.

Reasoning: Because agentRoutes is imported in api/index.ts, the module-level code in agent.route.ts executes unconditionally. If facilitatorUrl is undefined, passing it to HTTPFacilitatorClient may cause a runtime or TypeScript error. Wrapping the initialization and providing a 503 fallback ensures the app remains stable.

Suggested Code:

export const agentRoutes = Router();

if (facilitatorUrl && payTo && facilitatorApiKey) {
    const facilitatorClient = new HTTPFacilitatorClient({
        url: facilitatorUrl,
        createAuthHeaders: async () => {
            const headers = { Authorization: `Bearer ${facilitatorApiKey}` };
            return { verify: headers, settle: headers, supported: headers };
        }
    });

    agentRoutes.use(
        paymentMiddlewareFromConfig(
            {
                [`POST ${ENDPOINTS.AGENT.REVIEW}`]: {
                    accepts: {
                        scheme: "exact",
                        price: "$0.50",
                        network,
                        payTo
                    },
                    description: "Pull request review",
                    mimeType: "application/json"
                }
            } as RoutesConfig,
            facilitatorClient,
            [{ network, server: new ExactStellarScheme() }]
        )
    );

    agentRoutes.post(
        ENDPOINTS.AGENT.REVIEW,
        (async (req: Request, res: Response, next: NextFunction) => {
            try {
                const { prUrl } = req.body;
                const match = prUrl.match(/^https?:\/\/(?:www\.)?github\.com\/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\/pull\/(\d+)/);
                if (!match) {
                    throw new ValidationError("Please provide a valid GitHub Pull Request URL.");
                }
                const cleanPrUrl = match[0];
                const taskId = await cloudTasksService.addManualPRAnalysisJob(cleanPrUrl);

                responseWrapper({
                    res,
                    status: STATUS_CODES.SUCCESS,
                    data: { success: true, taskId },
                    message: "Review in progress. A comment will be posted by our bot."
                });
            } catch (error) {
                next(error);
            }
        }) as unknown as RequestHandler
    );
} else {
    agentRoutes.post(ENDPOINTS.AGENT.REVIEW, (req: Request, res: Response) => {
        responseWrapper({
            res,
            status: STATUS_CODES.SERVER_ERROR,
            data: {},
            message: "Agent features are not configured on this server."
        });
    });
}
📊 Review Metadata
  • Processing Time: 34s
  • Analysis Date: 4/8/2026, 6:04:21 PM

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

💬 Questions about this review? Open an issue or contact support.

@devasign-app
Copy link
Copy Markdown

devasign-app bot commented Apr 8, 2026

Updated Merge Score: 90/100

🟢 ██████████████████░░ 90%

The author successfully addressed the unconditional instantiation of the x402 client by wrapping the setup in agent.route.ts with a conditional block, providing a clean fallback response when the agent is not configured. The ESM and Vitest migrations also look solid. However, the graceful degradation is still blocked by the ErrorHandlerService, which continues to enforce the x402 environment variables as strictly required (requiredVars). Moving these to warningVars will fully resolve the issue and allow the application to boot without them.

Code Suggestions (1)

High Priority (1)

  1. api/services/error-handler.service.ts (Line 152)
    Move x402 environment variables from requiredVars to warningVars to prevent application crash on startup.

Reasoning: You successfully added graceful degradation in agent.route.ts by conditionally instantiating the x402 client and mounting the routes. However, because X402_FACILITATOR_URL, X402_PAYEE_ADDRESS, and X402_API_KEY are still listed in the requiredVars array in ErrorHandlerService, the application will throw a fatal error and crash on startup if they are missing. Moving them to warningVars will allow the app to start and correctly fall back to the disabled state.

Suggested Code:

        const warningVars = [
            { keys: ["X402_FACILITATOR_URL", "X402_PAYEE_ADDRESS", "X402_API_KEY"], msg: "x402 misconfiguration - agent features will be disabled" },
            // ... existing warning vars
        ];
📊 Review Metadata
  • Processing Time: 37s
  • Analysis Date: 4/8/2026, 6:15:36 PM

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

💬 Questions about this review? Open an issue or contact support.

Copy link
Copy Markdown

@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: 7

♻️ Duplicate comments (1)
api/routes/agent.route.ts (1)

53-60: ⚠️ Potential issue | 🔴 Critical

Replace the PR URL regex with a linear-time parser or anchored check.

This pattern is still vulnerable to backtracking on attacker-controlled input, and it also accepts arbitrary junk after the PR number because cleanPrUrl uses the partial match. Parse with URL + pathname segments, or switch to an anchored, non-backtracking validation approach before release.

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

In `@api/routes/agent.route.ts` around lines 53 - 60, The current regex-based
validation of prUrl (and the use of match[0] as cleanPrUrl) is vulnerable to
backtracking and may allow trailing junk; replace it with a linear parser using
the URL constructor: create a URL from prUrl, verify url.protocol is http/https
and url.hostname is "github.com" (or "www.github.com"), split url.pathname by
"/" and confirm segments match ["", owner, repo, "pull", number] with owner/repo
matching allowed characters and number being digits only, then rebuild a
canonical cleanPrUrl from those components and pass that to
cloudTasksService.addManualPRAnalysisJob(cleanPrUrl). Ensure you remove the
unsafe regex and thrown ValidationError remains when parsing/validation fails.
🧹 Nitpick comments (8)
api/config/stellar.config.ts (1)

11-15: Type safety reduced by casting to any.

The customClient as any cast suppresses type checking between axios's AxiosInstance and what ApplicationConfiguration expects. This likely indicates a type mismatch between the SDK's expected HTTP client interface and axios's types.

Consider investigating the root cause—this could be a version mismatch or an SDK type definition issue. If the cast is necessary, the inline comment explaining why would help future maintainers.

#!/bin/bash
# Check `@stellar/typescript-wallet-sdk` version and axios version
echo "Checking relevant package versions:"
cat package.json | jq '{stellar_sdk: .dependencies["@stellar/typescript-wallet-sdk"], axios: .dependencies["axios"]}'

# Check ApplicationConfiguration constructor signature if available in node_modules
echo -e "\nSearching for ApplicationConfiguration type definition:"
fd "ApplicationConfiguration" node_modules/@stellar --type f -x head -50 {} 2>/dev/null || echo "SDK not found in node_modules"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/config/stellar.config.ts` around lines 11 - 15, The cast to any on
customClient when constructing ApplicationConfiguration (new
ApplicationConfiguration(DefaultSigner, customClient as any)) hides a type
mismatch between axios's AxiosInstance and the SDK's expected HTTP client;
either update the code to pass a properly typed client that matches the SDK
interface (adapt/custom-wrapper or use the SDK's HttpClient type) or, if the
cast is unavoidable, add an explicit comment explaining why and include the SDK
and axios version check; inspect ApplicationConfiguration's constructor/type in
the SDK to determine the required methods/signature and implement or type-assert
to that specific interface instead of using any (referencing customClient,
ApplicationConfiguration, and DefaultSigner to locate the code).
tests/integration/api/task/installation.api.test.ts (1)

9-9: Variable shadowing: mockFirebaseAuth is both imported and redeclared.

Line 9 imports mockFirebaseAuth from the mock file, but line 50 declares a local variable with the same name that shadows the import. This could lead to confusion. Consider renaming the local variable (e.g., mockVerifyIdToken) to clarify its purpose and avoid shadowing.

♻️ Suggested rename to avoid shadowing
-import { mockFirebaseAuth } from "../../../mocks/firebase.service.mock.js";
+import { mockFirebaseAuth as firebaseAuthMock } from "../../../mocks/firebase.service.mock.js";

Or rename the local variable:

-    let mockFirebaseAuth: any;
+    let mockVerifyIdToken: any;
...
-        mockFirebaseAuth = firebaseAdmin.auth().verifyIdToken;
+        mockVerifyIdToken = firebaseAdmin.auth().verifyIdToken;
...
-        mockFirebaseAuth.mockResolvedValue({
+        mockVerifyIdToken.mockResolvedValue({

Also applies to: 50-50

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

In `@tests/integration/api/task/installation.api.test.ts` at line 9, The import
mockFirebaseAuth is being shadowed by a locally declared variable named
mockFirebaseAuth; rename the local variable (for example to mockVerifyIdToken or
mockFirebaseVerifyIdToken) wherever it’s declared and referenced in this test to
avoid shadowing and clarify intent—update references in the test setup/teardown
and any calls that use the local mock so they point to the new name, leaving the
imported mockFirebaseAuth untouched.
tests/integration/middlewares/webhook.middleware.test.ts (2)

10-26: Keep the mocked specifiers identical to the ESM imports.

These mocks still target extensionless module ids even though the migrated code imports .js specifiers. Vitest keys mocks by the provided module id, so leaving the strings different makes mock application depend on resolver normalization instead of being explicit. (vitest.dev)

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

In `@tests/integration/middlewares/webhook.middleware.test.ts` around lines 10 -
26, The mocks use extensionless module ids so Vitest won't reliably match the
ESM imports; update the vi.mock calls to use the exact ESM specifiers (include
the .js extension) that the code imports, e.g. change
"../../../api/services/octokit.service" to
"../../../api/services/octokit.service.js" and
"../../../api/config/logger.config" to "../../../api/config/logger.config.js"
while keeping the mocked objects (OctokitService and dataLogger with their fn()
stubs) identical.

424-532: Restore explicit coverage for the remaining webhook guards.

api/middlewares/webhook.middleware.ts:169-254 still short-circuits PRs that do not target the default branch and rejects issue_comment payloads with no issue, but this suite no longer exercises either branch. I’d keep unit tests for both here since they gate whether manual review processing is skipped or accepted.

Also applies to: 534-620

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

In `@tests/integration/middlewares/webhook.middleware.test.ts` around lines 424 -
532, Add tests that explicitly cover the remaining webhook guard branches in
validateGitHubWebhookEvent: (1) a pull_request payload whose
pull_request.base.ref is not the repository default branch (use
createValidPRPayload({ pull_request: { base: { ref: "non-default" }}})), set
X-GitHub-Event to "pull_request", call validateGitHubWebhookEvent and assert
mockNext was not called and mockResponse.status/json were called with the same
status/message the middleware returns for non-default-target PRs; and (2) an
issue_comment payload that omits the issue object (use a
createIssueCommentPayload or build payload with no issue), set X-GitHub-Event to
"issue_comment", call validateGitHubWebhookEvent and assert the middleware
rejects it by not calling mockNext and by calling mockResponse.status/json with
the same status/message the middleware returns for missing issue. Ensure you
reference validateGitHubWebhookEvent, STATUS_CODES, mockRequest.get,
mockResponse, mockNext, and the existing payload helpers so the tests exercise
those exact guard branches.
tests/mocks/firebase.service.mock.ts (1)

261-271: Keep setupFirebaseMocks() free of vi.mock() calls.

Vitest hoists vi.mock before imports regardless of where it appears in the file, so these calls are not scoped to setupFirebaseMocks() and don’t behave like runtime setup. That makes the helper misleading and duplicates the top-level mocks already declared above. I’d let this helper only return/reset the hoisted instances. (vitest.dev)

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

In `@tests/mocks/firebase.service.mock.ts` around lines 261 - 271, The
setupFirebaseMocks function currently contains vi.mock calls which are hoisted
by Vitest and therefore not scoped to runtime; remove the
vi.mock("firebase-admin/firestore") and
vi.mock("../../api/config/firebase.config.js") calls from setupFirebaseMocks,
leaving the top-level hoisted mocks in place, and change setupFirebaseMocks to
only return or reset the existing mock instances (mockTimestamp,
mockFirestoreDB, mockFirebaseAdmin) so it becomes a runtime helper that
resets/returns those mocks rather than declaring them.
tests/integration/api/webhook/github.api.test.ts (2)

15-94: Match these vi.mock(...) ids to the .js import specifiers.

Same concern as the other migrated suites: the app code now imports .js paths, but these mocks still use extensionless ids. Vitest mock resolution is module-id based, and mismatched ids are a known source of mocks silently not applying. (vitest.dev)

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

In `@tests/integration/api/webhook/github.api.test.ts` around lines 15 - 94, The
vi.mock call paths in github.api.test.ts use extensionless module ids that don't
match the app's imports (which use .js), so mocks silently fail; update each
vi.mock(...) identifier to the exact .js import specifier used by the app (for
the Firebase config mock, the FirebaseService mock, OctokitService mock,
ContractService mock, KMSService mock, cloudTasksService mock, and
stellarService mock) so the module ids match (e.g. change
"../../../../api/config/firebase.config" to
"../../../../api/config/firebase.config.js" and likewise for the other mocked
modules).

458-468: Keep asserting the queued-response timestamp.

api/services/github-webhook/pull_request-webhook.service.ts:65-80 still returns data.timestamp. Dropping it from the assertion weakens this API contract test and makes a response-shape regression easier to miss.

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

In `@tests/integration/api/webhook/github.api.test.ts` around lines 458 - 468, The
test removed asserting data.timestamp but the pull_request-webhook.service.ts
(function handling PR webhooks) still returns data.timestamp, so restore an
assertion for it in tests/integration/api/webhook/github.api.test.ts: update the
expect(response.body).toMatchObject (or add a separate assertion) to include
data.timestamp and validate it is present and a well-formed timestamp (e.g.,
non-empty string or ISO datetime); reference the pull_request-webhook.service.ts
timestamp field to ensure the test and implementation remain in sync.
tests/integration/api/task/index.api.test.ts (1)

16-76: Use the exact .js specifiers in these Vitest mocks.

The code under test now imports the ESM .js paths, but these vi.mock(...) calls still use extensionless ids. Vitest registers mocks against the provided module id, and id/path mismatches are a known way to accidentally load the real module instead of the mock. I’d make every mocked specifier match the runtime import string exactly here. (vitest.dev)

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

In `@tests/integration/api/task/index.api.test.ts` around lines 16 - 76, The
Vitest mocks use extensionless module ids so they don't match the runtime ESM
imports; update every vi.mock call so the specifier exactly matches the ESM
import string (include the ".js" extension) used by the code under test—e.g.
change the vi.mock invocations that register mockStellarService,
mockContractService, mockFirebaseService, mockOctokitService, and the KMSService
mock to use the exact ".js" module specifiers the application imports so the
mocks (getAccountInfo, transferAsset, createEscrow, refund, createTask,
updateTaskStatus, addBountyLabelAndCreateBountyComment, decryptWallet, etc.) are
correctly applied at test runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/config/firebase.config.ts`:
- Line 1: The import for ServiceAccount is wrong—it's not exported from the root
"firebase-admin"; update the import statements so ServiceAccount is imported
from "firebase-admin/app" (or switch to modular imports from
"firebase-admin/app" such as initializeApp and cert) and keep the default admin
import from "firebase-admin" if you still use admin.initializeApp; specifically
replace the current import of ServiceAccount from "firebase-admin" with an
import of type ServiceAccount from "firebase-admin/app" or refactor to modular
imports (initializeApp, cert, ServiceAccount) to resolve the incorrect export.

In `@api/routes/agent.route.ts`:
- Around line 10-21: The startup currently builds the HTTPFacilitatorClient with
potentially undefined envs (facilitatorUrl, payTo, facilitatorApiKey) and always
emits an Authorization header; update the initialization around
HTTPFacilitatorClient so you validate that facilitatorUrl and payTo are present
before creating the client (or skip/disable the paywall when missing), and
change createAuthHeaders to only return Authorization when facilitatorApiKey is
non-empty (otherwise return empty headers or omit Authorization). Reference the
constants network, facilitatorUrl, payTo, facilitatorApiKey and the
HTTPFacilitatorClient/createAuthHeaders function to locate and guard the logic.
- Around line 27-69: The payment middleware (paymentMiddlewareFromConfig) is
currently applied before request validation and can charge callers even if the
handler fails; move the cheap PR URL validation (the regex check for prUrl in
the POST handler for ENDPOINTS.AGENT.REVIEW) to run before attaching the payment
middleware so invalid requests are rejected without charging, and implement a
durable/compensating pattern around cloudTasksService.addManualPRAnalysisJob
(e.g., persist a pending-job record or enqueue to a reliable work-queue first
and only allow payment settlement after successful enqueue, or implement
refund/compensation logic when addManualPRAnalysisJob throws) so failures in
addManualPRAnalysisJob do not result in unrecoverable charges.
- Around line 27-43: The payment middleware configuration currently passes a
single object as the `accepts` value to paymentMiddlewareFromConfig (in
agentRoutes usage of paymentMiddlewareFromConfig with ENDPOINTS.AGENT.REVIEW and
ExactStellarScheme), but the installed x402 v2 API expects `accepts` to be an
array and a separately registered resource server parameter; update the call so
the route's config uses `accepts: [{ scheme: "exact", price: "$0.50", network,
payTo, mimeType: "application/json", description: "Pull request review" }]` and
pass the resource server instance according to the v2 signature (verify whether
paymentMiddlewareFromConfig expects `facilitatorClient, server` or a servers
list and adjust usage of facilitatorClient and ExactStellarScheme accordingly),
then run the TypeScript build to confirm the types (RoutesConfig,
paymentMiddlewareFromConfig, agentRoutes) match the package's v2 definitions.

In `@api/services/cloud-tasks.service.ts`:
- Line 92: Add a route handler for the internal manual PR analysis endpoint so
Cloud Tasks doesn't get 404s: in internal.route.ts register a POST handler for
ENDPOINTS.INTERNAL.MANUAL_PR_ANALYSIS (or the router method used by existing
internal routes, e.g., registerInternalRoutes/router.post) that validates the
incoming task payload, invokes the same processing logic used for manual PR
analysis (call the service function that performs the analysis), and returns the
appropriate 200/4xx response; ensure the handler name and exported router follow
the project's routing conventions so the endpoint matches the path configured in
cloud-tasks.service.ts.

In `@api/services/statsig.service.ts`:
- Around line 1-2: The import for statsig-node is wrong: replace the named
import of Statsig and StatsigUser with a default import for Statsig and a
type-only named import for StatsigUser (i.e., import Statsig, { type StatsigUser
} from "statsig-node") so the runtime uses the default export; update the
top-level import statement that currently reads "import { Statsig, StatsigUser }
from 'statsig-node'" to the corrected form and keep dataLogger usage unchanged.

In `@tests/mocks/firebase.service.mock.ts`:
- Around line 16-35: The messagesCol.get() mock currently uses mockResolvedValue
with a snapshot of mockMessages at construction, causing later createMessage
calls not to appear; change the messagesCol.get mock (the vi.fn assigned to get
on messagesCol) to use mockImplementation(async () => ({ docs:
mockMessages.map(msg => ({ id: msg.id, data: () => msg })) })) so each
invocation returns a live snapshot built from the current mockMessages array
(ensure it's async so callers still receive a Promise).

---

Duplicate comments:
In `@api/routes/agent.route.ts`:
- Around line 53-60: The current regex-based validation of prUrl (and the use of
match[0] as cleanPrUrl) is vulnerable to backtracking and may allow trailing
junk; replace it with a linear parser using the URL constructor: create a URL
from prUrl, verify url.protocol is http/https and url.hostname is "github.com"
(or "www.github.com"), split url.pathname by "/" and confirm segments match ["",
owner, repo, "pull", number] with owner/repo matching allowed characters and
number being digits only, then rebuild a canonical cleanPrUrl from those
components and pass that to
cloudTasksService.addManualPRAnalysisJob(cleanPrUrl). Ensure you remove the
unsafe regex and thrown ValidationError remains when parsing/validation fails.

---

Nitpick comments:
In `@api/config/stellar.config.ts`:
- Around line 11-15: The cast to any on customClient when constructing
ApplicationConfiguration (new ApplicationConfiguration(DefaultSigner,
customClient as any)) hides a type mismatch between axios's AxiosInstance and
the SDK's expected HTTP client; either update the code to pass a properly typed
client that matches the SDK interface (adapt/custom-wrapper or use the SDK's
HttpClient type) or, if the cast is unavoidable, add an explicit comment
explaining why and include the SDK and axios version check; inspect
ApplicationConfiguration's constructor/type in the SDK to determine the required
methods/signature and implement or type-assert to that specific interface
instead of using any (referencing customClient, ApplicationConfiguration, and
DefaultSigner to locate the code).

In `@tests/integration/api/task/index.api.test.ts`:
- Around line 16-76: The Vitest mocks use extensionless module ids so they don't
match the runtime ESM imports; update every vi.mock call so the specifier
exactly matches the ESM import string (include the ".js" extension) used by the
code under test—e.g. change the vi.mock invocations that register
mockStellarService, mockContractService, mockFirebaseService,
mockOctokitService, and the KMSService mock to use the exact ".js" module
specifiers the application imports so the mocks (getAccountInfo, transferAsset,
createEscrow, refund, createTask, updateTaskStatus,
addBountyLabelAndCreateBountyComment, decryptWallet, etc.) are correctly applied
at test runtime.

In `@tests/integration/api/task/installation.api.test.ts`:
- Line 9: The import mockFirebaseAuth is being shadowed by a locally declared
variable named mockFirebaseAuth; rename the local variable (for example to
mockVerifyIdToken or mockFirebaseVerifyIdToken) wherever it’s declared and
referenced in this test to avoid shadowing and clarify intent—update references
in the test setup/teardown and any calls that use the local mock so they point
to the new name, leaving the imported mockFirebaseAuth untouched.

In `@tests/integration/api/webhook/github.api.test.ts`:
- Around line 15-94: The vi.mock call paths in github.api.test.ts use
extensionless module ids that don't match the app's imports (which use .js), so
mocks silently fail; update each vi.mock(...) identifier to the exact .js import
specifier used by the app (for the Firebase config mock, the FirebaseService
mock, OctokitService mock, ContractService mock, KMSService mock,
cloudTasksService mock, and stellarService mock) so the module ids match (e.g.
change "../../../../api/config/firebase.config" to
"../../../../api/config/firebase.config.js" and likewise for the other mocked
modules).
- Around line 458-468: The test removed asserting data.timestamp but the
pull_request-webhook.service.ts (function handling PR webhooks) still returns
data.timestamp, so restore an assertion for it in
tests/integration/api/webhook/github.api.test.ts: update the
expect(response.body).toMatchObject (or add a separate assertion) to include
data.timestamp and validate it is present and a well-formed timestamp (e.g.,
non-empty string or ISO datetime); reference the pull_request-webhook.service.ts
timestamp field to ensure the test and implementation remain in sync.

In `@tests/integration/middlewares/webhook.middleware.test.ts`:
- Around line 10-26: The mocks use extensionless module ids so Vitest won't
reliably match the ESM imports; update the vi.mock calls to use the exact ESM
specifiers (include the .js extension) that the code imports, e.g. change
"../../../api/services/octokit.service" to
"../../../api/services/octokit.service.js" and
"../../../api/config/logger.config" to "../../../api/config/logger.config.js"
while keeping the mocked objects (OctokitService and dataLogger with their fn()
stubs) identical.
- Around line 424-532: Add tests that explicitly cover the remaining webhook
guard branches in validateGitHubWebhookEvent: (1) a pull_request payload whose
pull_request.base.ref is not the repository default branch (use
createValidPRPayload({ pull_request: { base: { ref: "non-default" }}})), set
X-GitHub-Event to "pull_request", call validateGitHubWebhookEvent and assert
mockNext was not called and mockResponse.status/json were called with the same
status/message the middleware returns for non-default-target PRs; and (2) an
issue_comment payload that omits the issue object (use a
createIssueCommentPayload or build payload with no issue), set X-GitHub-Event to
"issue_comment", call validateGitHubWebhookEvent and assert the middleware
rejects it by not calling mockNext and by calling mockResponse.status/json with
the same status/message the middleware returns for missing issue. Ensure you
reference validateGitHubWebhookEvent, STATUS_CODES, mockRequest.get,
mockResponse, mockNext, and the existing payload helpers so the tests exercise
those exact guard branches.

In `@tests/mocks/firebase.service.mock.ts`:
- Around line 261-271: The setupFirebaseMocks function currently contains
vi.mock calls which are hoisted by Vitest and therefore not scoped to runtime;
remove the vi.mock("firebase-admin/firestore") and
vi.mock("../../api/config/firebase.config.js") calls from setupFirebaseMocks,
leaving the top-level hoisted mocks in place, and change setupFirebaseMocks to
only return or reset the existing mock instances (mockTimestamp,
mockFirestoreDB, mockFirebaseAdmin) so it becomes a runtime helper that
resets/returns those mocks rather than declaring them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ad1ff24-6774-4e17-8c62-d46de2f42bd9

📥 Commits

Reviewing files that changed from the base of the PR and between a93de6a and 637645c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (98)
  • api/config/database.config.ts
  • api/config/firebase.config.ts
  • api/config/stellar.config.ts
  • api/controllers/installation/github.controller.ts
  • api/controllers/installation/index.controller.ts
  • api/controllers/installation/index.ts
  • api/controllers/installation/team.controller.ts
  • api/controllers/internal/index.controller.ts
  • api/controllers/internal/index.ts
  • api/controllers/task/activities.controller.ts
  • api/controllers/task/contributor.controller.ts
  • api/controllers/task/index.controller.ts
  • api/controllers/task/index.ts
  • api/controllers/task/installation.controller.ts
  • api/controllers/task/{taskId}.controller.ts
  • api/controllers/user/index.controller.ts
  • api/controllers/user/index.ts
  • api/controllers/user/sumsub.controller.ts
  • api/controllers/wallet/index.controller.ts
  • api/controllers/wallet/index.ts
  • api/controllers/wallet/transactions.controller.ts
  • api/controllers/webhook/github.controller.ts
  • api/controllers/webhook/index.ts
  • api/controllers/webhook/sumsub.controller.ts
  • api/index.ts
  • api/middlewares/auth.middleware.ts
  • api/middlewares/error.middleware.ts
  • api/middlewares/rate-limit.middleware.ts
  • api/middlewares/request.middleware.ts
  • api/middlewares/webhook.middleware.ts
  • api/models/ai-review.model.ts
  • api/models/error.model.ts
  • api/models/task.model.ts
  • api/routes/_test_/contract.test.route.ts
  • api/routes/_test_/general.test.route.ts
  • api/routes/_test_/octokit.test.route.ts
  • api/routes/_test_/stellar.test.route.ts
  • api/routes/agent.route.ts
  • api/routes/index.ts
  • api/routes/installation.route.ts
  • api/routes/internal.route.ts
  • api/routes/task.route.ts
  • api/routes/user.route.ts
  • api/routes/wallet.route.ts
  • api/routes/webhook.route.ts
  • api/schemas/installation.schema.ts
  • api/schemas/task.schema.ts
  • api/schemas/wallet.schema.ts
  • api/services/cloud-tasks.service.ts
  • api/services/contract.service.ts
  • api/services/error-handler.service.ts
  • api/services/firebase.service.ts
  • api/services/github-webhook/installation-webhook.service.ts
  • api/services/github-webhook/installation_repositories-webhook.service.ts
  • api/services/github-webhook/issue_comment-webhook.service.ts
  • api/services/github-webhook/pull_request-webhook.service.ts
  • api/services/github-webhook/push-webhook.service.ts
  • api/services/kms.service.ts
  • api/services/octokit.service.ts
  • api/services/socket.service.ts
  • api/services/statsig.service.ts
  • api/services/stellar.service.ts
  • jest.config.js
  • package.json
  • tests/helpers/__tests__/database-test-utilities.test.ts
  • tests/helpers/database-test-helper.ts
  • tests/helpers/database-test-utilities.ts
  • tests/helpers/test-data-factory.ts
  • tests/helpers/test-utils.ts
  • tests/integration/api/installation/github.api.test.ts
  • tests/integration/api/installation/index.api.test.ts
  • tests/integration/api/installation/team.api.test.ts
  • tests/integration/api/internal/index.api.test.ts
  • tests/integration/api/task/activities.api.test.ts
  • tests/integration/api/task/contributor.api.test.ts
  • tests/integration/api/task/index.api.test.ts
  • tests/integration/api/task/installation.api.test.ts
  • tests/integration/api/task/{taskId}.api.test.ts
  • tests/integration/api/user/index.api.test.ts
  • tests/integration/api/user/sumsub.api.test.ts
  • tests/integration/api/wallet/index.api.test.ts
  • tests/integration/api/wallet/transactions.api.test.ts
  • tests/integration/api/webhook/github.api.test.ts
  • tests/integration/api/webhook/sumsub.api.test.ts
  • tests/integration/middlewares/auth.middleware.test.ts
  • tests/integration/middlewares/error.middleware.test.ts
  • tests/integration/middlewares/rate-limit.middleware.test.ts
  • tests/integration/middlewares/request.middleware.test.ts
  • tests/integration/middlewares/webhook.middleware.test.ts
  • tests/mocks/cuid2.mock.ts
  • tests/mocks/firebase.service.mock.ts
  • tests/setup/global-setup.ts
  • tests/setup/setup-verification.test.ts
  • tests/setup/vitest.setup.ts
  • tests/tsconfig.json
  • tests/unit/helper.test.ts
  • tsconfig.json
  • vitest.config.ts
💤 Files with no reviewable changes (1)
  • jest.config.js
✅ Files skipped from review due to trivial changes (51)
  • api/schemas/wallet.schema.ts
  • api/models/task.model.ts
  • api/controllers/internal/index.ts
  • api/controllers/installation/github.controller.ts
  • api/services/github-webhook/push-webhook.service.ts
  • api/services/contract.service.ts
  • tests/helpers/database-test-utilities.ts
  • api/models/ai-review.model.ts
  • api/routes/test/general.test.route.ts
  • tests/helpers/database-test-helper.ts
  • api/controllers/installation/team.controller.ts
  • api/controllers/task/index.ts
  • api/controllers/user/sumsub.controller.ts
  • api/controllers/webhook/github.controller.ts
  • api/controllers/webhook/index.ts
  • api/services/octokit.service.ts
  • api/services/socket.service.ts
  • api/config/database.config.ts
  • api/services/stellar.service.ts
  • api/services/github-webhook/installation_repositories-webhook.service.ts
  • api/controllers/user/index.ts
  • api/routes/task.route.ts
  • api/routes/user.route.ts
  • api/middlewares/request.middleware.ts
  • api/controllers/task/installation.controller.ts
  • tests/setup/global-setup.ts
  • api/services/kms.service.ts
  • api/routes/webhook.route.ts
  • api/schemas/task.schema.ts
  • api/routes/wallet.route.ts
  • api/controllers/wallet/index.ts
  • api/controllers/installation/index.controller.ts
  • api/middlewares/auth.middleware.ts
  • api/controllers/internal/index.controller.ts
  • api/controllers/webhook/sumsub.controller.ts
  • api/services/github-webhook/pull_request-webhook.service.ts
  • api/controllers/task/activities.controller.ts
  • api/controllers/user/index.controller.ts
  • tests/mocks/cuid2.mock.ts
  • tests/integration/api/task/contributor.api.test.ts
  • api/controllers/wallet/index.controller.ts
  • api/routes/installation.route.ts
  • api/services/github-webhook/issue_comment-webhook.service.ts
  • tests/integration/api/task/{taskId}.api.test.ts
  • api/schemas/installation.schema.ts
  • api/services/firebase.service.ts
  • tests/integration/middlewares/rate-limit.middleware.test.ts
  • api/routes/test/octokit.test.route.ts
  • api/controllers/task/{taskId}.controller.ts
  • api/controllers/task/index.controller.ts
  • api/controllers/task/contributor.controller.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • api/index.ts
  • tests/integration/api/task/activities.api.test.ts
  • tests/integration/api/wallet/transactions.api.test.ts
  • package.json
  • tests/integration/api/internal/index.api.test.ts
  • tsconfig.json
  • tests/integration/middlewares/auth.middleware.test.ts
  • tests/integration/api/user/index.api.test.ts
  • tests/integration/api/wallet/index.api.test.ts
  • tests/integration/api/installation/index.api.test.ts
  • api/routes/index.ts
  • tests/integration/api/installation/team.api.test.ts
  • tests/integration/api/installation/github.api.test.ts

@@ -1,5 +1,4 @@
import * as admin from "firebase-admin";
import { ServiceAccount } from "firebase-admin";
import admin, { ServiceAccount } from "firebase-admin";
Copy link
Copy Markdown

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 firebase-admin version in package.json
echo "Checking firebase-admin version:"
cat package.json | jq '.dependencies["firebase-admin"] // .devDependencies["firebase-admin"]'

# Check if there are any other firebase-admin imports in the codebase for consistency
echo -e "\nOther firebase-admin imports in codebase:"
rg "from ['\"]firebase-admin['\"]" --type ts -n

Repository: devasignhq/devasign-api

Length of output: 240


🌐 Web query:

firebase-admin 13.6.1 ESM default import ServiceAccount export structure

💡 Result:

In firebase-admin v13.6.1, the ServiceAccount type is defined (and exported) like this:

export interface ServiceAccount {
  projectId?: string;
  clientEmail?: string;
  privateKey?: string;
}

[1]

Correct ESM import (recommended)

ServiceAccount is exported from the modular entrypoint firebase-admin/app:

import { initializeApp, cert, type ServiceAccount } from "firebase-admin/app";

const sa: ServiceAccount = {
  projectId: process.env.FIREBASE_PROJECT_ID,
  clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
  privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
};

initializeApp({ credential: cert(sa) });

[2]

About import admin from "firebase-admin" (default import)

The top-level firebase-admin entrypoint is still implemented as a CommonJS export = ... module (legacy “namespaced API”), not a true ESM export default module. So a default import only works via CJS↔ESM interop (Node/TS settings), not because the package defines an ESM default export. [3]

“camelCase vs snake_case” keys

Even though the TS ServiceAccount interface is camelCase, the runtime code accepts either camelCase (projectId, clientEmail, privateKey) or snake_case (project_id, client_email, private_key) and normalizes between them. [4]

Sources:
[1] https://github.com/firebase/firebase-admin-node/blob/v13.6.1/src/app/credential.ts
[2] https://github.com/firebase/firebase-admin-node/blob/v13.6.1/src/app/index.ts
[3] https://github.com/firebase/firebase-admin-node/blob/v13.6.1/src/index.ts
[4] https://github.com/firebase/firebase-admin-node/blob/v13.6.1/src/app/credential-internal.ts


🏁 Script executed:

#!/bin/bash
# Check if there are any errors related to this import in the codebase
# Look for how admin is being used
echo "Usage of 'admin' in firebase.config.ts:"
rg "admin\." api/config/firebase.config.ts -A 2 -B 2

# Check if ServiceAccount type is being used
echo -e "\nUsage of 'ServiceAccount' in codebase:"
rg "ServiceAccount" --type ts -n

Repository: devasignhq/devasign-api

Length of output: 724


🏁 Script executed:

#!/bin/bash
# Check tsconfig to see module resolution settings
echo "TypeScript Configuration:"
cat tsconfig.json | jq '.compilerOptions | {module, moduleResolution, esModuleInterop}'

# Try to find if ServiceAccount is re-exported from root firebase-admin
echo -e "\nChecking firebase-admin package types:"
npm list firebase-admin 2>/dev/null | head -5

# Check if there are type errors or if the code compiles
echo -e "\nLooking for any firebase-admin type definition files:"
fd "firebase-admin" node_modules/@types -type d 2>/dev/null | head -5

Repository: devasignhq/devasign-api

Length of output: 466


🏁 Script executed:

#!/bin/bash
# Fix the jq query syntax
echo "TypeScript Configuration:"
cat tsconfig.json 2>/dev/null | head -20

# Check if firebase-admin is actually installed
echo -e "\nChecking if firebase-admin is installed:"
test -d node_modules/firebase-admin && echo "firebase-admin is installed" || echo "firebase-admin NOT found in node_modules"

# Try to check the actual firebase-admin package exports
echo -e "\nChecking firebase-admin package.json for exports:"
if [ -f "node_modules/firebase-admin/package.json" ]; then
  cat node_modules/firebase-admin/package.json | grep -A 20 '"exports"'
else
  echo "firebase-admin package.json not found in node_modules"
fi

# Check the main index.ts in firebase-admin to see what it exports
echo -e "\nChecking main firebase-admin entry point:"
if [ -f "node_modules/firebase-admin/lib/index.d.ts" ]; then
  head -50 node_modules/firebase-admin/lib/index.d.ts | grep -E "export|ServiceAccount"
else
  echo "Type definition file not found, checking alternate locations..."
  find node_modules/firebase-admin -name "index.d.ts" 2>/dev/null | head -3
fi

Repository: devasignhq/devasign-api

Length of output: 935


Incorrect ServiceAccount import location.

ServiceAccount is not exported from the root firebase-admin module. It must be imported from firebase-admin/app instead. Update to:

import admin from "firebase-admin";
import { type ServiceAccount } from "firebase-admin/app";

Or use the modular imports directly:

import { initializeApp, cert, type ServiceAccount } from "firebase-admin/app";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/config/firebase.config.ts` at line 1, The import for ServiceAccount is
wrong—it's not exported from the root "firebase-admin"; update the import
statements so ServiceAccount is imported from "firebase-admin/app" (or switch to
modular imports from "firebase-admin/app" such as initializeApp and cert) and
keep the default admin import from "firebase-admin" if you still use
admin.initializeApp; specifically replace the current import of ServiceAccount
from "firebase-admin" with an import of type ServiceAccount from
"firebase-admin/app" or refactor to modular imports (initializeApp, cert,
ServiceAccount) to resolve the incorrect export.

Comment on lines +27 to +69
agentRoutes.use(
paymentMiddlewareFromConfig(
{
[`POST ${ENDPOINTS.AGENT.REVIEW}`]: {
accepts: {
scheme: "exact",
price: "$0.50",
network,
payTo
},
description: "Pull request review",
mimeType: "application/json"
}
} as RoutesConfig,
facilitatorClient,
[{ network, server: new ExactStellarScheme() }]
)
);

// Protected endpoint handler
agentRoutes.post(
ENDPOINTS.AGENT.REVIEW,
(async (req: Request, res: Response, next: NextFunction) => {
try {
const { prUrl } = req.body;

// Validate PR URL
const match = prUrl.match(/^https?:\/\/(?:www\.)?github\.com\/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\/pull\/(\d+)/);
if (!match) {
throw new ValidationError("Please provide a valid GitHub Pull Request URL.");
}
// Optionally reconstruct a clean URL to pass to the job
const cleanPrUrl = match[0];
const taskId = await cloudTasksService.addManualPRAnalysisJob(cleanPrUrl);

responseWrapper({
res,
status: STATUS_CODES.SUCCESS,
data: { success: true, taskId },
message: "Review in progress. A comment will be posted by our bot."
});
} catch (error) {
next(error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don’t settle payment before you know the request can be fulfilled.

paymentMiddlewareFromConfig(...) runs before the handler, and x402’s standard flow verifies/settles the payment before the server returns the successful resource response. With the current ordering, a missing/non-string prUrl or a Cloud Tasks enqueue failure can still charge the caller and then return an error. Move cheap body validation ahead of the paywall and add a durable/compensating path around addManualPRAnalysisJob. (github.com)

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

In `@api/routes/agent.route.ts` around lines 27 - 69, The payment middleware
(paymentMiddlewareFromConfig) is currently applied before request validation and
can charge callers even if the handler fails; move the cheap PR URL validation
(the regex check for prUrl in the POST handler for ENDPOINTS.AGENT.REVIEW) to
run before attaching the payment middleware so invalid requests are rejected
without charging, and implement a durable/compensating pattern around
cloudTasksService.addManualPRAnalysisJob (e.g., persist a pending-job record or
enqueue to a reliable work-queue first and only allow payment settlement after
successful enqueue, or implement refund/compensation logic when
addManualPRAnalysisJob throws) so failures in addManualPRAnalysisJob do not
result in unrecoverable charges.

Comment on lines +27 to +43
agentRoutes.use(
paymentMiddlewareFromConfig(
{
[`POST ${ENDPOINTS.AGENT.REVIEW}`]: {
accepts: {
scheme: "exact",
price: "$0.50",
network,
payTo
},
description: "Pull request review",
mimeType: "application/json"
}
} as RoutesConfig,
facilitatorClient,
[{ network, server: new ExactStellarScheme() }]
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify this matches the current x402 v2 server API.

The code is on the v2 @x402/* packages, but Coinbase’s current TypeScript seller docs show paymentMiddleware(..., server) with accepts: [{ ... }] and an explicitly registered resource server. This route still passes a scalar accepts object through paymentMiddlewareFromConfig, so please confirm it emits valid payment requirements for the installed package version before shipping the paywall. (docs.cdp.coinbase.com)

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

In `@api/routes/agent.route.ts` around lines 27 - 43, The payment middleware
configuration currently passes a single object as the `accepts` value to
paymentMiddlewareFromConfig (in agentRoutes usage of paymentMiddlewareFromConfig
with ENDPOINTS.AGENT.REVIEW and ExactStellarScheme), but the installed x402 v2
API expects `accepts` to be an array and a separately registered resource server
parameter; update the call so the route's config uses `accepts: [{ scheme:
"exact", price: "$0.50", network, payTo, mimeType: "application/json",
description: "Pull request review" }]` and pass the resource server instance
according to the v2 signature (verify whether paymentMiddlewareFromConfig
expects `facilitatorClient, server` or a servers list and adjust usage of
facilitatorClient and ExactStellarScheme accordingly), then run the TypeScript
build to confirm the types (RoutesConfig, paymentMiddlewareFromConfig,
agentRoutes) match the package's v2 definitions.

},
endpoints: {
"pr-analysis": ENDPOINTS.INTERNAL.PREFIX + ENDPOINTS.INTERNAL.PR_ANALYSIS,
"manual-pr-analysis": ENDPOINTS.INTERNAL.PREFIX + ENDPOINTS.INTERNAL.MANUAL_PR_ANALYSIS,
Copy link
Copy Markdown

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
# Description: Search for route handler registration for /internal/manual-pr-analysis

# Search for MANUAL_PR_ANALYSIS route registration in internal routes
rg -n "MANUAL_PR_ANALYSIS" --type=ts -C3

# Search for internal route definitions
ast-grep --pattern 'router.$_($_, $$$)'

# Check internal route file specifically
fd -t f "internal" --extension ts --exec cat {}

Repository: devasignhq/devasign-api

Length of output: 50379


🏁 Script executed:

# Search for internal route files
find api/routes -type f -name "*.route.ts" ! -path "*/_test_/*" | head -20

Repository: devasignhq/devasign-api

Length of output: 259


🏁 Script executed:

# Search for route registrations that handle manual-pr-analysis
rg -n "manual-pr-analysis|MANUAL_PR_ANALYSIS" api/routes --type=ts -l

Repository: devasignhq/devasign-api

Length of output: 49


🏁 Script executed:

# Look for internal or agent route files specifically
fd -t f "\.route\.ts$" api/routes

Repository: devasignhq/devasign-api

Length of output: 420


🏁 Script executed:

cat api/routes/internal.route.ts

Repository: devasignhq/devasign-api

Length of output: 409


🏁 Script executed:

# Also check agent.route.ts to see the manual-pr-analysis job dispatch
rg -n "addManualPRAnalysisJob" api/routes/agent.route.ts -A 10 -B 5

Repository: devasignhq/devasign-api

Length of output: 753


Add missing internal route handler for manual PR analysis.

The Cloud Tasks service is configured to dispatch tasks to /internal/manual-pr-analysis (cloud-tasks.service.ts:92), but the internal route file (api/routes/internal.route.ts) has no handler registered for this endpoint. Cloud Tasks will receive 404 errors when attempting to execute queued jobs. Add a route handler for ENDPOINTS.INTERNAL.MANUAL_PR_ANALYSIS to internal.route.ts that processes the manual PR analysis task.

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

In `@api/services/cloud-tasks.service.ts` at line 92, Add a route handler for the
internal manual PR analysis endpoint so Cloud Tasks doesn't get 404s: in
internal.route.ts register a POST handler for
ENDPOINTS.INTERNAL.MANUAL_PR_ANALYSIS (or the router method used by existing
internal routes, e.g., registerInternalRoutes/router.post) that validates the
incoming task payload, invokes the same processing logic used for manual PR
analysis (call the service function that performs the analysis), and returns the
appropriate 200/4xx response; ensure the handler name and exported router follow
the project's routing conventions so the endpoint matches the path configured in
cloud-tasks.service.ts.

Comment on lines +1 to +2
import { Statsig, StatsigUser } from "statsig-node";
import { dataLogger } from "../config/logger.config.js";
Copy link
Copy Markdown

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

🌐 Web query:

statsig-node named exports Statsig class API

💡 Result:

statsig-node (legacy Node.js Server SDK) does not expose a named Statsig class. The module’s primary export is the global Statsig singleton (shown in docs as const Statsig = require("statsig-node");).[1]

How to import

CommonJS (documented)

const Statsig = require("statsig-node");
await Statsig.initialize("server-secret-key", options);

[1]

ESM / TypeScript (typical interop)

import Statsig from "statsig-node";
await Statsig.initialize("server-secret-key", options);

(ESM default import works in many setups via CommonJS interop; if your tooling doesn’t, use createRequire or import * as Statsig from "statsig-node".)

“Class-based” API (the named export you likely want)

For multiple independent SDK instances, the SDK exposes an instance-based class StatsigServer (used as new StatsigServer(secretKey, options) then initializeAsync()).[1]

import { StatsigServer } from "statsig-node";

const sdk = new StatsigServer(secretKey, options);
await sdk.initializeAsync();

[1]

Main singleton API surface (what require("statsig-node") gives you)

The exported singleton (statsig/Statsig) provides methods like initialize, checkGate, getConfig, getExperiment, logEvent, shutdown, etc.[2]

Sources: Statsig Legacy Node.js Server SDK docs[1] and the SDK’s generated README/API doc in the package.[2]


🏁 Script executed:

cd api/services && head -120 statsig.service.ts | cat -n

Repository: devasignhq/devasign-api

Length of output: 4538


🌐 Web query:

statsig-node StatsigUser type named export TypeScript

💡 Result:

In statsig-node, StatsigUser is a TypeScript-only type export (export type StatsigUser = ...). [1] So you should import it as a type-only named import:

import Statsig from "statsig-node";
import type { StatsigUser } from "statsig-node";

const user: StatsigUser = { userID: "123" };

If you want a single import line (TS 3.8+), you can do:

import Statsig, { type StatsigUser } from "statsig-node";

Type-only imports are erased from JS output, which avoids runtime “named export” issues. [2]

Sources: [1] [2]


Fix statsig-node import statement.

The import uses named exports, but statsig-node exports Statsig as a default export and StatsigUser as a type-only named export. Update the import to:

import Statsig, { type StatsigUser } from "statsig-node";

The current import will cause a runtime error when trying to destructure Statsig as a named export.

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

In `@api/services/statsig.service.ts` around lines 1 - 2, The import for
statsig-node is wrong: replace the named import of Statsig and StatsigUser with
a default import for Statsig and a type-only named import for StatsigUser (i.e.,
import Statsig, { type StatsigUser } from "statsig-node") so the runtime uses
the default export; update the top-level import statement that currently reads
"import { Statsig, StatsigUser } from 'statsig-node'" to the corrected form and
keep dataLogger usage unchanged.

Comment on lines +16 to +35
const messagesCol = {
doc: vi.fn((id?: string) => ({
id: id || `mock_message_${Date.now()}`,
get: vi.fn().mockResolvedValue({
exists: true,
id: id || `mock_message_${Date.now()}`,
data: () => mockMessages.find(m => m.id === id) || {}
}),
set: vi.fn().mockResolvedValue(undefined),
update: vi.fn().mockResolvedValue(undefined)
})),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
get: vi.fn().mockResolvedValue({
docs: mockMessages.map(msg => ({
id: msg.id,
data: () => msg
}))
})
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Return a live snapshot from messagesCol.get().

mockResolvedValue({ docs: mockMessages.map(...) }) captures the collection contents once at mock construction time. Later createMessage calls will never appear in collection("messages").get(), so tests that read through the Firestore API will see stale state. Switch this to mockImplementation(async () => ({ docs: ... })) so each call reflects the current array.

Suggested fix
-        get: vi.fn().mockResolvedValue({
-            docs: mockMessages.map(msg => ({
-                id: msg.id,
-                data: () => msg
-            }))
-        })
+        get: vi.fn().mockImplementation(async () => ({
+            docs: mockMessages.map(msg => ({
+                id: msg.id,
+                data: () => msg
+            }))
+        }))
📝 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
const messagesCol = {
doc: vi.fn((id?: string) => ({
id: id || `mock_message_${Date.now()}`,
get: vi.fn().mockResolvedValue({
exists: true,
id: id || `mock_message_${Date.now()}`,
data: () => mockMessages.find(m => m.id === id) || {}
}),
set: vi.fn().mockResolvedValue(undefined),
update: vi.fn().mockResolvedValue(undefined)
})),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
get: vi.fn().mockResolvedValue({
docs: mockMessages.map(msg => ({
id: msg.id,
data: () => msg
}))
})
};
const messagesCol = {
doc: vi.fn((id?: string) => ({
id: id || `mock_message_${Date.now()}`,
get: vi.fn().mockResolvedValue({
exists: true,
id: id || `mock_message_${Date.now()}`,
data: () => mockMessages.find(m => m.id === id) || {}
}),
set: vi.fn().mockResolvedValue(undefined),
update: vi.fn().mockResolvedValue(undefined)
})),
where: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
get: vi.fn().mockImplementation(async () => ({
docs: mockMessages.map(msg => ({
id: msg.id,
data: () => msg
}))
}))
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mocks/firebase.service.mock.ts` around lines 16 - 35, The
messagesCol.get() mock currently uses mockResolvedValue with a snapshot of
mockMessages at construction, causing later createMessage calls not to appear;
change the messagesCol.get mock (the vi.fn assigned to get on messagesCol) to
use mockImplementation(async () => ({ docs: mockMessages.map(msg => ({ id:
msg.id, data: () => msg })) })) so each invocation returns a live snapshot built
from the current mockMessages array (ensure it's async so callers still receive
a Promise).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create new pay-per-use pull request review route

2 participants