This document outlines the code review standards for talkwise-warden based on patterns observed in past pull requests. Use this as a checklist when reviewing PRs or writing new code.
β Don't:
const response = await axios.get(
`https://api.github.com/repos/${owner}/${repo}/issues`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github+json'
}
}
);β Do:
import { createGitHubApiClient } from '@utils/github-api-client';
const http = createGitHubApiClient();
const response = await http.get(`/repos/${owner}/${repo}/issues`);Why: Centralized configuration, consistency, and maintainability. See Controllers CLAUDE.md for details.
β Don't:
const whereClause: any = {
repositoryId: repositoryIds,
};β Do:
interface WhereClause {
repositoryId: string[];
pullRequest?: {
state: string;
};
}
const whereClause: WhereClause = {
repositoryId: repositoryIds,
};Why: Type safety is one of TypeScript's core benefits. Using any defeats the purpose and makes refactoring dangerous.
β Don't:
throw new AppError('Commit message is required', 400);
res.status(200).json({ success: true });β Do:
import { StatusCodes } from 'http-status-codes';
throw new AppError('Commit message is required', StatusCodes.BAD_REQUEST);
res.status(StatusCodes.OK).json({ success: true });Why: Makes code self-documenting, prevents typos (e.g., 409 vs 499), and improves searchability.
β Don't:
const maxSequence = Math.min(expectedSequence, 1000000);
const maxPages = 10; // Limit: 1000 issues max
hasMore = response.data.length === 100;β Do:
const MAX_SEQUENCE_GAP_LIMIT = 1000000;
const MAX_PAGINATION_PAGES = 10;
const GITHUB_MAX_PER_PAGE = 100;
const maxSequence = Math.min(expectedSequence, MAX_SEQUENCE_GAP_LIMIT);
const maxPages = MAX_PAGINATION_PAGES;
hasMore = response.data.length === GITHUB_MAX_PER_PAGE;Why: Makes intent clear, prevents errors when values need to change, enables easy configuration.
β Don't:
whereClause.pullRequest = {
state: state,
};β Do:
whereClause.pullRequest = {
state
};Why: Cleaner, more concise, modern JavaScript standard.
β Don't:
const repo = repoParam.replace(/\.git$/i, '');
[projectId.match(/^[0-9a-f-]{36}$/i) ? 'id' : 'slug']: projectIdβ Do:
// Create helper functions
function removeGitSuffix(repo: string): string {
return repo.replace(/\.git$/i, '');
}
function isUuid(value: string): boolean {
return /^[0-9a-f-]{36}$/i.test(value);
}
function isSlug(value: string): boolean {
return !isUuid(value);
}
// Use them
const repo = removeGitSuffix(repoParam);
[isSlug(projectId) ? 'slug' : 'id']: projectIdWhy: Improves readability, makes logic testable, enables reuse.
β Don't:
const actualIssues = issues.filter((issue: Record<string, unknown>) => {
try {
return !issue.pull_request;
} catch (e) {
logger.warn({ error: e instanceof Error ? e.message : String(e) });
return false;
}
});β Do:
const actualIssues = issues.filter((issue: Record<string, unknown>) => {
return !issue?.pull_request;
});Why: Simpler, more idiomatic JavaScript. Property access doesn't throw errors in JavaScriptβit returns undefined.
β Don't:
if (!response || !response.data) {
hasMore = false; // Useless assignment
break;
}β Do:
if (!response || !response.data) {
break;
}Why: The assignment has no effect since the loop exits immediately.
β Don't:
const accessToken = process.env.GITHUB_ACCESS_TOKEN;
if (!accessToken) {
throw new AppError('GitHub API token not configured', 500);
}β
Do:
Ensure getGithubAccessToken() validates at app startup (already implemented in warden). Remove runtime checks in individual functions.
Why: Fail fast at startup rather than during user requests.
This is a judgment call. If there's a risk that req.params could be undefined or malformed:
Consider:
async reconcileRepository = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
const { repositoryId } = req.params;
try {
// implementation
} catch (error) {
next(error);
}
};Why: Simpler code. If repositoryId is undefined, TypeScript/validation middleware should catch it earlier.
β Don't:
// Inside middleware file
declare module 'express-serve-static-core' {
interface Request {
requestId?: string;
logger?: pino.Logger;
}
}β
Do:
Create src/types/global.d.ts or similar for type augmentations.
Why: Keeps type declarations organized and easily discoverable.
Example:
requestContextMiddlewareβrequestLoggerMiddlewareorrequestContextLoggerMiddleware
Why: "Context" is vague. More specific names clarify intent.
When dealing with GitHub API or other external APIs that may return unexpected data:
// Validate response structure
if (!response || !response.data) {
logger.warn({ page, repositoryKey }, 'Invalid response structure from GitHub API');
break;
}
const issues = Array.isArray(response.data) ? response.data : [];
if (!Array.isArray(issues)) {
logger.warn({ page, repositoryKey }, 'Issues response is not an array');
break;
}Why: External APIs can change or fail in unexpected ways. Defensive programming prevents crashes.
When reviewing a PR, check for:
- GitHub API calls use
createGitHubApiClient() - No usage of
anytype (use proper types/interfaces) - HTTP status codes use
StatusCodesenum - Magic numbers extracted to constants
- Object property shorthand used where applicable
- Complex logic extracted to helper functions
- Optional chaining used instead of unnecessary try-catch
- No useless assignments before
break/return - Type augmentations in proper type definition files
- Middleware and functions have clear, specific names
- External API responses validated
- Consistent formatting (handled by Prettier)
const GITHUB_MAX_PER_PAGE = 100;
const MAX_PAGINATION_PAGES = 10;
let page = 1;
let hasMore = true;
while (hasMore && page <= MAX_PAGINATION_PAGES) {
const http = createGitHubApiClient();
const response = await http.get(`/repos/${owner}/${repo}/issues`, {
params: {
state: 'all',
per_page: GITHUB_MAX_PER_PAGE,
page
}
});
const items = response.data;
hasMore = items.length === GITHUB_MAX_PER_PAGE;
page++;
}function parseRepoUrl(url: string): { owner: string; repo: string } | null {
const match = url.match(/github\.com\/([^/]+)\/([^/]+)/);
if (!match) {
return null;
}
const [, owner, repoWithGit] = match;
const repo = removeGitSuffix(repoWithGit);
return { owner, repo };
}catch (error: unknown) {
if (axios.isAxiosError(error)) {
const status = error.response?.status || StatusCodes.INTERNAL_SERVER_ERROR;
const message = error.response?.data?.message || error.message;
throw new AppError(message, status);
}
throw error;
}Code reviews are about:
- Correctness - Does it work and handle edge cases?
- Maintainability - Can future developers understand and modify it?
- Consistency - Does it follow established patterns?
- Type Safety - Does it leverage TypeScript properly?
Be thorough but constructive. The goal is to ship high-quality code while helping each other grow as engineers.