Unverified 提交 2c3fade9 authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Fix presubmit failures in eval suite fixtures and helpers (#3286)

<!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3286" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review"> </picture> </a> <!-- devin-review-badge-end -->
上级 a5f19192
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"ignorePatterns": ["e2e-tests/fixtures/**/*"],
"ignorePatterns": [
"e2e-tests/fixtures/**/*",
"src/__tests__/evals/fixtures/**/*"
],
"categories": {
"correctness": "warn"
},
......
......@@ -5,7 +5,7 @@ the same three models (Claude Sonnet 4.6, GPT 5.4, Gemini 3 Flash) but with
different tool sets and system prompts:
| Suite name | Tools available | System prompt |
| ------------------------- | ------------------------------------------- | --------------------------------------------- |
| ------------------------ | ------------------------------------------- | -------------------------------------------- |
| `search_replace` | `search_replace` only | Minimal custom "precise code editor" prompt |
| `search_replace_few` | `search_replace` only | Variant prompt encouraging fewer tool calls |
| `edit_file` | `edit_file` only | Minimal custom `edit_file` prompt |
......@@ -213,8 +213,8 @@ one folder. Folder names sort chronologically under `ls`.
- `judge` — the judge's verdict: `label`, `modelName`, `durationMs`,
`usage`, `pass` (boolean), and `explanation` (the judge's written
reasoning, with the trailing `PASS`/`FAIL` verdict line stripped).
- `passed` — the overall test outcome. Requires the judge to say `PASS` *and*
all structural checks to pass *and* no exceptions to be thrown.
- `passed` — the overall test outcome. Requires the judge to say `PASS` _and_
all structural checks to pass _and_ no exceptions to be thrown.
- `errorMessage` — set when the test threw (tool-call failure, structural
check failure, judge FAIL, etc.); `null` otherwise.
......
// UserProfile.tsx — class-based user profile component
import React from "react";
import { fetchUser, updateUser, fetchUserActivity } from "../services/userService";
import {
fetchUser,
updateUser,
fetchUserActivity,
} from "../services/userService";
import type { User, ActivityEntry } from "../types";
interface Props {
......@@ -89,14 +93,19 @@ export class UserProfile extends React.Component<Props, State> {
this.setState({ activity, activityLoading: false });
} catch (err) {
this.setState({
activityError: err instanceof Error ? err.message : "Failed to load activity",
activityError:
err instanceof Error ? err.message : "Failed to load activity",
activityLoading: false,
});
}
}
handleEdit = () => {
this.setState({ editing: true, draft: { ...this.state.user }, saveError: null });
this.setState({
editing: true,
draft: { ...this.state.user },
saveError: null,
});
};
handleCancel = () => {
......@@ -111,11 +120,17 @@ export class UserProfile extends React.Component<Props, State> {
this.setState({ saving: true, saveError: null });
try {
const updated = await updateUser(this.props.userId, this.state.draft);
this.setState({ user: updated, editing: false, draft: {}, saving: false });
this.setState({
user: updated,
editing: false,
draft: {},
saving: false,
});
this.props.onProfileUpdated?.(updated);
} catch (err) {
this.setState({
saveError: err instanceof Error ? err.message : "Failed to save changes",
saveError:
err instanceof Error ? err.message : "Failed to save changes",
saving: false,
});
}
......@@ -147,11 +162,14 @@ export class UserProfile extends React.Component<Props, State> {
// Upload stub — real impl would POST to /api/avatars
await new Promise((r) => setTimeout(r, 500));
const fakeUrl = URL.createObjectURL(file);
const updated = await updateUser(this.props.userId, { avatarUrl: fakeUrl });
const updated = await updateUser(this.props.userId, {
avatarUrl: fakeUrl,
});
this.setState({ user: updated, uploadingAvatar: false });
} catch (err) {
this.setState({
avatarError: err instanceof Error ? err.message : "Failed to upload avatar",
avatarError:
err instanceof Error ? err.message : "Failed to upload avatar",
uploadingAvatar: false,
});
}
......@@ -216,14 +234,25 @@ export class UserProfile extends React.Component<Props, State> {
return (
<div className="user-profile">
<div className="profile-header">
<div className="avatar-wrapper" onClick={!readOnly ? this.handleAvatarClick : undefined}>
<div
className="avatar-wrapper"
onClick={!readOnly ? this.handleAvatarClick : undefined}
>
{user.avatarUrl ? (
<img src={user.avatarUrl} alt={`${user.name}'s avatar`} className="avatar" />
<img
src={user.avatarUrl}
alt={`${user.name}'s avatar`}
className="avatar"
/>
) : (
<div className="avatar-placeholder">{user.name.charAt(0).toUpperCase()}</div>
<div className="avatar-placeholder">
{user.name.charAt(0).toUpperCase()}
</div>
)}
{!readOnly && (
<div className="avatar-overlay">{uploadingAvatar ? "Uploading…" : "Change"}</div>
<div className="avatar-overlay">
{uploadingAvatar ? "Uploading…" : "Change"}
</div>
)}
</div>
{!readOnly && (
......@@ -237,7 +266,9 @@ export class UserProfile extends React.Component<Props, State> {
)}
{avatarError && <p className="avatar-error">{avatarError}</p>}
<h1>{user.name}</h1>
<span className={`role-badge role-badge--${user.role}`}>{user.role}</span>
<span className={`role-badge role-badge--${user.role}`}>
{user.role}
</span>
</div>
{editing ? (
......@@ -276,7 +307,11 @@ export class UserProfile extends React.Component<Props, State> {
<button type="submit" disabled={saving}>
{saving ? "Saving…" : "Save"}
</button>
<button type="button" onClick={this.handleCancel} disabled={saving}>
<button
type="button"
onClick={this.handleCancel}
disabled={saving}
>
Cancel
</button>
</div>
......@@ -305,7 +340,10 @@ export class UserProfile extends React.Component<Props, State> {
)}
<div className="activity-section">
<button className="toggle-activity" onClick={this.handleToggleActivity}>
<button
className="toggle-activity"
onClick={this.handleToggleActivity}
>
{showActivity ? "Hide activity" : "Show recent activity"}
</button>
{showActivity && this.renderActivityFeed()}
......
// UserProfileFull.tsx — full-featured user profile page component
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
import React, {
useState,
useEffect,
useCallback,
useRef,
useMemo,
} from "react";
import { useNavigate, useParams } from "react-router-dom";
import { fetchUser, updateUser, uploadAvatar, fetchUserActivity } from "../services/userService";
import {
fetchUser,
updateUser,
uploadAvatar,
fetchUserActivity,
} from "../services/userService";
import type { User, ActivityItem } from "../types";
interface UserProfileFullProps {
......@@ -46,7 +57,8 @@ interface ActivityState {
function formatStatValue(value: number | string, unit?: string): string {
if (typeof value === "number") {
const formatted = value >= 1000 ? `${(value / 1000).toFixed(1)}k` : String(value);
const formatted =
value >= 1000 ? `${(value / 1000).toFixed(1)}k` : String(value);
return unit ? `${formatted} ${unit}` : formatted;
}
return value;
......@@ -133,7 +145,10 @@ export function UserProfile({
if (!file) return;
if (!file.type.startsWith("image/")) {
setAvatar((prev) => ({ ...prev, error: "Please select an image file" }));
setAvatar((prev) => ({
...prev,
error: "Please select an image file",
}));
return;
}
......@@ -143,7 +158,12 @@ export function UserProfile({
}
const previewUrl = URL.createObjectURL(file);
setAvatar((prev) => ({ ...prev, previewUrl, uploading: true, error: null }));
setAvatar((prev) => ({
...prev,
previewUrl,
uploading: true,
error: null,
}));
try {
const result = await uploadAvatar(resolvedUserId, file);
......@@ -170,7 +190,8 @@ export function UserProfile({
setAvatar({ url: null, uploading: false, error: null, previewUrl: null });
}, []);
const avatarDisplayUrl = avatar.previewUrl ?? avatar.url ?? "/default-avatar.png";
const avatarDisplayUrl =
avatar.previewUrl ?? avatar.url ?? "/default-avatar.png";
const renderAvatarBadge = useCallback(() => {
if (avatar.uploading) {
......@@ -247,18 +268,38 @@ export function UserProfile({
period: INITIAL_STATS_PERIOD,
});
const loadStats = useCallback(
async (period: "week" | "month" | "year") => {
const loadStats = useCallback(async (period: "week" | "month" | "year") => {
setStats((prev) => ({ ...prev, loading: true, error: null, period }));
try {
// Simulate loading stats — in production this hits the analytics API
await new Promise((r) => setTimeout(r, 100));
const mockCards: StatCard[] = [
{ label: "Commits", value: period === "week" ? 23 : period === "month" ? 87 : 1042, change: 12.5 },
{ label: "PRs Merged", value: period === "week" ? 5 : period === "month" ? 18 : 203, change: -3.2 },
{ label: "Reviews", value: period === "week" ? 11 : period === "month" ? 42 : 498, change: 8.1 },
{ label: "Lines Changed", value: period === "week" ? 1250 : period === "month" ? 4800 : 58000, unit: "lines", change: 15.7 },
{ label: "Issues Closed", value: period === "week" ? 7 : period === "month" ? 25 : 312, change: 0 },
{
label: "Commits",
value: period === "week" ? 23 : period === "month" ? 87 : 1042,
change: 12.5,
},
{
label: "PRs Merged",
value: period === "week" ? 5 : period === "month" ? 18 : 203,
change: -3.2,
},
{
label: "Reviews",
value: period === "week" ? 11 : period === "month" ? 42 : 498,
change: 8.1,
},
{
label: "Lines Changed",
value: period === "week" ? 1250 : period === "month" ? 4800 : 58000,
unit: "lines",
change: 15.7,
},
{
label: "Issues Closed",
value: period === "week" ? 7 : period === "month" ? 25 : 312,
change: 0,
},
{ label: "Build Success", value: "98.2%", change: 1.1 },
];
setStats({ cards: mockCards, loading: false, error: null, period });
......@@ -269,9 +310,7 @@ export function UserProfile({
error: err instanceof Error ? err.message : "Failed to load stats",
}));
}
},
[],
);
}, []);
useEffect(() => {
if (showStats && resolvedUserId) {
......@@ -295,20 +334,19 @@ export function UserProfile({
return typeof commitCard?.value === "number" ? commitCard.value : 0;
}, [stats.cards]);
const renderStatCard = useCallback(
(card: StatCard, index: number) => {
const renderStatCard = useCallback((card: StatCard, index: number) => {
return (
<div key={index} className="stat-card">
<div className="stat-card__label">{card.label}</div>
<div className="stat-card__value">{formatStatValue(card.value, card.unit)}</div>
<div className="stat-card__value">
{formatStatValue(card.value, card.unit)}
</div>
<div className={`stat-card__change ${getChangeClass(card.change)}`}>
{formatChangePercent(card.change)}
</div>
</div>
);
},
[],
);
}, []);
const renderStatsHeader = useCallback(() => {
const periods: Array<"week" | "month" | "year"> = ["week", "month", "year"];
......@@ -335,7 +373,8 @@ export function UserProfile({
return (
<div className="stats-summary">
<p>
Total activity: <strong>{totalCommits}</strong> commits this {stats.period}.
Total activity: <strong>{totalCommits}</strong> commits this{" "}
{stats.period}.
</p>
</div>
);
......@@ -393,13 +432,18 @@ export function UserProfile({
return groups;
}, [activityState.items]);
const renderActivityItem = useCallback((item: ActivityItem) => {
const renderActivityItem = useCallback(
(item: ActivityItem) => {
return (
<div key={item.id} className="activity-item">
<span className="activity-item__icon">{getActivityIcon(item.type)}</span>
<span className="activity-item__icon">
{getActivityIcon(item.type)}
</span>
<div className="activity-item__content">
<p className="activity-item__description">{item.description}</p>
<span className="activity-item__time">{formatActivityDate(item.timestamp)}</span>
<span className="activity-item__time">
{formatActivityDate(item.timestamp)}
</span>
{item.metadata?.pr && (
<a
className="activity-item__link"
......@@ -415,7 +459,9 @@ export function UserProfile({
</div>
</div>
);
}, [navigate]);
},
[navigate],
);
const renderActivityGroup = useCallback(
(dateKey: string, items: ActivityItem[]) => {
......@@ -556,10 +602,19 @@ export function UserProfile({
</div>
{saveError && <p className="form-error">{saveError}</p>}
<div className="form-actions">
<button type="submit" disabled={saving} className="btn btn--primary">
<button
type="submit"
disabled={saving}
className="btn btn--primary"
>
{saving ? "Saving…" : "Save Changes"}
</button>
<button type="button" onClick={handleCancel} disabled={saving} className="btn btn--secondary">
<button
type="button"
onClick={handleCancel}
disabled={saving}
className="btn btn--secondary"
>
Cancel
</button>
</div>
......@@ -648,7 +703,10 @@ export function UserProfile({
{/* ── Footer ────────────────────────────────────────── */}
<footer className="user-profile__footer">
<p>Profile last updated: {user.updatedAt ? new Date(user.updatedAt).toLocaleString() : "Never"}</p>
<p>
Profile last updated:{" "}
{user.updatedAt ? new Date(user.updatedAt).toLocaleString() : "Never"}
</p>
</footer>
</div>
);
......
......@@ -56,7 +56,9 @@ export function endSession(): void {
return;
}
const durationMs = Date.now() - currentSession.startedAt;
console.log(`analytics session ended: ${currentSession.id} (${durationMs}ms)`);
console.log(
`analytics session ended: ${currentSession.id} (${durationMs}ms)`,
);
currentSession = null;
}
......@@ -90,7 +92,9 @@ export function track(name: string, props: Record<string, unknown> = {}): void {
queue.push(event);
console.log(`tracked event: ${name}`);
if (queue.length >= MAX_BATCH_SIZE) {
console.warn(`analytics queue full (${queue.length}), flushing immediately`);
console.warn(
`analytics queue full (${queue.length}), flushing immediately`,
);
flush();
}
}
......@@ -100,7 +104,10 @@ export function trackPageView(path: string, title: string): void {
track("page_view", { path, title });
}
export function trackError(err: Error, context: Record<string, unknown> = {}): void {
export function trackError(
err: Error,
context: Record<string, unknown> = {},
): void {
console.error(`analytics error event: ${err.message}`, err);
track("error", {
message: err.message,
......@@ -130,9 +137,15 @@ export function trackSearch(query: string, resultCount: number): void {
track("search", { query, resultCount });
}
export function trackTiming(category: string, variable: string, durationMs: number): void {
export function trackTiming(
category: string,
variable: string,
durationMs: number,
): void {
if (durationMs < 0) {
console.error(`trackTiming: negative duration ${durationMs}ms for ${category}/${variable}`);
console.error(
`trackTiming: negative duration ${durationMs}ms for ${category}/${variable}`,
);
return;
}
track("timing", { category, variable, durationMs });
......@@ -203,7 +216,10 @@ function sendToBackend(events: Event[]): void {
let _identifiedUserId: string | null = null;
export function identify(userId: string, traits: Record<string, unknown> = {}): void {
export function identify(
userId: string,
traits: Record<string, unknown> = {},
): void {
if (!userId) {
console.warn("identify called with empty userId");
return;
......@@ -243,18 +259,25 @@ export function trackAddToCart(
price: number,
): void {
if (quantity <= 0) {
console.error(`trackAddToCart: invalid quantity ${quantity} for product ${productId}`);
console.error(
`trackAddToCart: invalid quantity ${quantity} for product ${productId}`,
);
return;
}
track("add_to_cart", { productId, quantity, price });
}
export function trackCheckoutStarted(cartValue: number, itemCount: number): void {
export function trackCheckoutStarted(
cartValue: number,
itemCount: number,
): void {
if (cartValue < 0) {
console.error(`trackCheckoutStarted: negative cart value ${cartValue}`);
return;
}
console.log(`checkout started — ${itemCount} items, $${cartValue.toFixed(2)}`);
console.log(
`checkout started — ${itemCount} items, $${cartValue.toFixed(2)}`,
);
track("checkout_started", { cartValue, itemCount });
}
......@@ -263,7 +286,9 @@ export function trackOrderCompleted(
revenue: number,
currency: string,
): void {
console.log(`order completed: ${orderId}${currency} ${revenue.toFixed(2)}`);
console.log(
`order completed: ${orderId}${currency} ${revenue.toFixed(2)}`,
);
track("order_completed", { orderId, revenue, currency });
}
......
......@@ -260,7 +260,12 @@ export function createNamespace(prefix: string) {
set<T>(key: string, value: T, sizeBytes: number): void {
set(ns(key), value, sizeBytes);
},
setWithTtl<T>(key: string, value: T, sizeBytes: number, ttlMs: number): void {
setWithTtl<T>(
key: string,
value: T,
sizeBytes: number,
ttlMs: number,
): void {
setWithTtl(ns(key), value, sizeBytes, ttlMs);
},
get<T>(key: string): T | null {
......
......@@ -33,78 +33,120 @@ export interface AppEvent {
// ── Individual handlers ────────────────────────────────────────────────────
async function notifyUserCreated(payload: Record<string, unknown>): Promise<void> {
async function notifyUserCreated(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Sending welcome email to ${payload.email}`);
// implementation elided
}
async function syncUserToSearchIndex(payload: Record<string, unknown>): Promise<void> {
async function syncUserToSearchIndex(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Syncing user ${payload.id} to search index`);
// implementation elided
}
async function revokeUserSessions(payload: Record<string, unknown>): Promise<void> {
async function revokeUserSessions(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Revoking all sessions for user ${payload.id}`);
// implementation elided
}
async function notifyUserRoleChanged(payload: Record<string, unknown>): Promise<void> {
logger.info(`Notifying user ${payload.id} of role change: ${payload.oldRole}${payload.newRole}`);
async function notifyUserRoleChanged(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Notifying user ${payload.id} of role change: ${payload.oldRole}${payload.newRole}`,
);
// implementation elided
}
async function notifyProjectCreated(payload: Record<string, unknown>): Promise<void> {
async function notifyProjectCreated(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Notifying team about new project ${payload.id}`);
// implementation elided
}
async function archiveProjectAssets(payload: Record<string, unknown>): Promise<void> {
async function archiveProjectAssets(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Archiving assets for project ${payload.id}`);
// implementation elided
}
async function cleanupProjectResources(payload: Record<string, unknown>): Promise<void> {
async function cleanupProjectResources(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Cleaning up resources for deleted project ${payload.id}`);
// implementation elided
}
async function notifyProjectMemberAdded(payload: Record<string, unknown>): Promise<void> {
logger.info(`Notifying user ${payload.memberId} they were added to project ${payload.projectId}`);
async function notifyProjectMemberAdded(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Notifying user ${payload.memberId} they were added to project ${payload.projectId}`,
);
// implementation elided
}
async function notifyProjectMemberRemoved(payload: Record<string, unknown>): Promise<void> {
logger.info(`Notifying user ${payload.memberId} they were removed from project ${payload.projectId}`);
async function notifyProjectMemberRemoved(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Notifying user ${payload.memberId} they were removed from project ${payload.projectId}`,
);
// implementation elided
}
async function recordPaymentSuccess(payload: Record<string, unknown>): Promise<void> {
async function recordPaymentSuccess(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(`Recording payment ${payload.transactionId}`);
// implementation elided
}
async function handlePaymentFailure(payload: Record<string, unknown>): Promise<void> {
async function handlePaymentFailure(
payload: Record<string, unknown>,
): Promise<void> {
logger.warn(`Payment failed for order ${payload.orderId}`);
// implementation elided
}
async function processRefund(payload: Record<string, unknown>): Promise<void> {
logger.info(`Processing refund ${payload.refundId} for transaction ${payload.transactionId}`);
logger.info(
`Processing refund ${payload.refundId} for transaction ${payload.transactionId}`,
);
// implementation elided
}
async function provisionSubscriptionFeatures(payload: Record<string, unknown>): Promise<void> {
logger.info(`Provisioning features for subscription ${payload.subscriptionId}`);
async function provisionSubscriptionFeatures(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Provisioning features for subscription ${payload.subscriptionId}`,
);
// implementation elided
}
async function deprovisionSubscriptionFeatures(payload: Record<string, unknown>): Promise<void> {
logger.info(`Deprovisioning features for cancelled subscription ${payload.subscriptionId}`);
async function deprovisionSubscriptionFeatures(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Deprovisioning features for cancelled subscription ${payload.subscriptionId}`,
);
// implementation elided
}
async function extendSubscriptionAccess(payload: Record<string, unknown>): Promise<void> {
logger.info(`Extending access for renewed subscription ${payload.subscriptionId}`);
async function extendSubscriptionAccess(
payload: Record<string, unknown>,
): Promise<void> {
logger.info(
`Extending access for renewed subscription ${payload.subscriptionId}`,
);
// implementation elided
}
......@@ -113,7 +155,9 @@ async function extendSubscriptionAccess(payload: Record<string, unknown>): Promi
* TODO: Refactor this switch into a Record<EventType, handler> map + dispatch function.
*/
export async function handleEvent(event: AppEvent): Promise<void> {
logger.info(`Handling event ${event.type} (correlation: ${event.correlationId}, source: ${event.sourceService})`);
logger.info(
`Handling event ${event.type} (correlation: ${event.correlationId}, source: ${event.sourceService})`,
);
switch (event.type) {
case "user.created":
......@@ -214,7 +258,9 @@ export interface BatchResult {
* Processes a batch of events sequentially. Failures are recorded but
* do not abort the remaining events in the batch.
*/
export async function handleEventBatch(batch: EventBatch): Promise<BatchResult> {
export async function handleEventBatch(
batch: EventBatch,
): Promise<BatchResult> {
logger.info(
`Processing batch ${batch.batchId} with ${batch.events.length} event(s)`,
);
......@@ -277,7 +323,9 @@ export async function retryDeadLetter(
try {
await handleEvent(entry.event);
retried++;
logger.info(`Retried dead-letter event ${entry.event.correlationId} successfully`);
logger.info(
`Retried dead-letter event ${entry.event.correlationId} successfully`,
);
} catch (err) {
logger.error(
`Retry failed for dead-letter event ${entry.event.correlationId}`,
......
......@@ -5,7 +5,8 @@ import { getAuthToken } from "./auth";
const logger = createLogger("fetch-client");
const BASE_URL = process.env.SERVICE_BASE_URL ?? "https://api.internal.example.com";
const BASE_URL =
process.env.SERVICE_BASE_URL ?? "https://api.internal.example.com";
const DEFAULT_TIMEOUT_MS = 8_000;
export interface FetchClientOptions {
......@@ -68,7 +69,12 @@ export async function serviceRequest<T>(
path: string,
options: FetchClientOptions = {},
): Promise<T> {
const { method = "GET", body, headers = {}, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
const {
method = "GET",
body,
headers = {},
timeoutMs = DEFAULT_TIMEOUT_MS,
} = options;
const token = await getAuthToken();
const controller = new AbortController();
......@@ -100,7 +106,10 @@ export async function serviceRequest<T>(
// ── Verb wrappers ──────────────────────────────────────────────────────────
export async function getResource<T>(path: string, headers?: Record<string, string>): Promise<T> {
export async function getResource<T>(
path: string,
headers?: Record<string, string>,
): Promise<T> {
const data = await serviceRequest<T>(path, { method: "GET", headers });
return data;
}
......@@ -115,7 +124,10 @@ export async function putResource<T>(path: string, body: unknown): Promise<T> {
return data;
}
export async function patchResource<T>(path: string, body: unknown): Promise<T> {
export async function patchResource<T>(
path: string,
body: unknown,
): Promise<T> {
const data = await serviceRequest<T>(path, { method: "PATCH", body });
return data;
}
......@@ -151,7 +163,9 @@ export async function listUsers(
);
}
export async function getUserSubscription(userId: string): Promise<Subscription | null> {
export async function getUserSubscription(
userId: string,
): Promise<Subscription | null> {
return getResource<Subscription | null>(`/users/${userId}/subscription`);
}
......@@ -189,7 +203,9 @@ export async function deleteProject(projectId: string): Promise<void> {
// ── Project membership ─────────────────────────────────────────────────────
export async function getProjectMembers(projectId: string): Promise<ProjectMember[]> {
export async function getProjectMembers(
projectId: string,
): Promise<ProjectMember[]> {
return getResource<ProjectMember[]>(`/projects/${projectId}/members`);
}
......@@ -213,12 +229,17 @@ export async function updateProjectMemberRole(
userId: string,
role: string,
): Promise<ProjectMember> {
return patchResource<ProjectMember>(`/projects/${projectId}/members/${userId}`, { role });
return patchResource<ProjectMember>(
`/projects/${projectId}/members/${userId}`,
{ role },
);
}
// ── Workspace resources ────────────────────────────────────────────────────
export async function getWorkspace(workspaceId: string): Promise<{ id: string; name: string; plan: string }> {
export async function getWorkspace(
workspaceId: string,
): Promise<{ id: string; name: string; plan: string }> {
return getResource(`/workspaces/${workspaceId}`);
}
......@@ -262,34 +283,50 @@ export async function downloadInvoicePdf(invoiceId: string): Promise<Blob> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
const response = await fetch(`${BASE_URL}/billing/invoices/${invoiceId}/pdf`, {
const response = await fetch(
`${BASE_URL}/billing/invoices/${invoiceId}/pdf`,
{
headers: { Authorization: `Bearer ${token}` },
signal: controller.signal,
});
},
);
clearTimeout(timer);
if (!response.ok) {
throw { code: "DOWNLOAD_FAILED", message: "Failed to download invoice PDF", status: response.status } satisfies ServiceError;
throw {
code: "DOWNLOAD_FAILED",
message: "Failed to download invoice PDF",
status: response.status,
} satisfies ServiceError;
}
return response.blob();
}
export async function listPaymentMethods(workspaceId: string): Promise<PaymentMethod[]> {
return getResource<PaymentMethod[]>(`/workspaces/${workspaceId}/billing/payment-methods`);
export async function listPaymentMethods(
workspaceId: string,
): Promise<PaymentMethod[]> {
return getResource<PaymentMethod[]>(
`/workspaces/${workspaceId}/billing/payment-methods`,
);
}
export async function addPaymentMethod(
workspaceId: string,
token: string,
): Promise<PaymentMethod> {
return postResource<PaymentMethod>(`/workspaces/${workspaceId}/billing/payment-methods`, { token });
return postResource<PaymentMethod>(
`/workspaces/${workspaceId}/billing/payment-methods`,
{ token },
);
}
export async function removePaymentMethod(
workspaceId: string,
paymentMethodId: string,
): Promise<void> {
return deleteResource(`/workspaces/${workspaceId}/billing/payment-methods/${paymentMethodId}`);
return deleteResource(
`/workspaces/${workspaceId}/billing/payment-methods/${paymentMethodId}`,
);
}
export async function setDefaultPaymentMethod(
......
......@@ -110,7 +110,8 @@ export function couponAppliestoShipping(order: Order): boolean {
export function effectiveShippingCost(order: Order): number {
const base = order.shippingCost;
if (!order.coupon?.appliesToShipping) return base;
if (order.coupon.type === "fixed") return Math.max(0, base - order.coupon.value);
if (order.coupon.type === "fixed")
return Math.max(0, base - order.coupon.value);
return base * (1 - order.coupon.value / 100);
}
......@@ -173,7 +174,11 @@ export function formatOrderLine(item: LineItem): string {
return `${item.sku} × ${item.quantity} @ $${item.unitPrice.toFixed(2)}`;
}
export function applyBulkDiscount(items: LineItem[], threshold: number, pct: number): number {
export function applyBulkDiscount(
items: LineItem[],
threshold: number,
pct: number,
): number {
const sub = subtotalOf(items);
if (sub < threshold) return sub;
return sub * (1 - pct);
......@@ -269,9 +274,7 @@ export function isFullRefund(order: Order, returnedSkus: string[]): boolean {
// ── Reporting helpers ──────────────────────────────────────────────────────
export function totalsByCurrency(
orders: Order[],
): Record<string, number> {
export function totalsByCurrency(orders: Order[]): Record<string, number> {
const totals: Record<string, number> = {};
for (const order of orders) {
const key = order.currency;
......@@ -287,17 +290,25 @@ export function averageOrderValue(orders: Order[]): number {
}
export function topOrdersByValue(orders: Order[], n: number): Order[] {
return [...orders].sort((a, b) => calculateTotal(b) - calculateTotal(a)).slice(0, n);
return [...orders]
.sort((a, b) => calculateTotal(b) - calculateTotal(a))
.slice(0, n);
}
export function totalRevenue(orders: Order[]): number {
return orders.reduce((sum, o) => sum + calculateTotal(o), 0);
}
export function ordersAboveThreshold(orders: Order[], threshold: number): Order[] {
export function ordersAboveThreshold(
orders: Order[],
threshold: number,
): Order[] {
return orders.filter((o) => calculateTotal(o) >= threshold);
}
export function ordersBelowThreshold(orders: Order[], threshold: number): Order[] {
export function ordersBelowThreshold(
orders: Order[],
threshold: number,
): Order[] {
return orders.filter((o) => calculateTotal(o) < threshold);
}
// order_processor.ts — core order processing logic
import { createLogger } from "./logger";
import type { Order, InventoryItem, PaymentMethod, ShippingAddress } from "./types";
import type {
Order,
InventoryItem,
PaymentMethod,
ShippingAddress,
} from "./types";
const logger = createLogger("order-processor");
......@@ -66,7 +71,12 @@ async function createShipment(
items: string[],
): Promise<{ trackingNumber: string; estimatedDelivery: string }> {
// Implementation elided — hits the shipping API
return { trackingNumber: "track_placeholder", estimatedDelivery: new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString() };
return {
trackingNumber: "track_placeholder",
estimatedDelivery: new Date(
Date.now() + 5 * 24 * 3600 * 1000,
).toISOString(),
};
}
async function cancelShipment(trackingNumber: string): Promise<void> {
......@@ -120,7 +130,10 @@ export async function processOrder(order: Order): Promise<ProcessResult> {
}
// Validate payment method
if (!order.payment.method || !["card", "paypal", "bank"].includes(order.payment.method)) {
if (
!order.payment.method ||
!["card", "paypal", "bank"].includes(order.payment.method)
) {
return { success: false, error: "Invalid payment method" };
}
if (!order.payment.amount || order.payment.amount <= 0) {
......@@ -145,7 +158,10 @@ export async function processOrder(order: Order): Promise<ProcessResult> {
for (const r of reservations) {
await releaseInventory(r.productId, r.quantity);
}
return { success: false, error: `Could not reserve stock for ${item.productId}` };
return {
success: false,
error: `Could not reserve stock for ${item.productId}`,
};
}
reservations.push({ productId: item.productId, quantity: item.quantity });
}
......@@ -153,7 +169,10 @@ export async function processOrder(order: Order): Promise<ProcessResult> {
// Charge the customer
let transaction: { transactionId: string };
try {
transaction = await chargePayment(order.payment.method, order.payment.amount);
transaction = await chargePayment(
order.payment.method,
order.payment.amount,
);
} catch (err) {
logger.error("Payment failed", err);
for (const r of reservations) {
......@@ -186,7 +205,9 @@ export async function processOrder(order: Order): Promise<ProcessResult> {
};
const orderId = await saveOrder(enrichedOrder);
logger.info(`Order ${orderId} confirmed (tracking: ${shipment.trackingNumber})`);
logger.info(
`Order ${orderId} confirmed (tracking: ${shipment.trackingNumber})`,
);
// Send confirmation email (best-effort — don't fail the order)
try {
......@@ -212,7 +233,10 @@ export async function cancelOrder(
try {
await cancelShipment(trackingNumber);
} catch (err) {
logger.warn(`Could not cancel shipment ${trackingNumber} for order ${orderId}`, err);
logger.warn(
`Could not cancel shipment ${trackingNumber} for order ${orderId}`,
err,
);
}
let refundOk = false;
......@@ -227,7 +251,10 @@ export async function cancelOrder(
await releaseInventory(r.productId, r.quantity);
}
await updateOrderStatus(orderId, refundOk ? "cancelled" : "cancellation_pending");
await updateOrderStatus(
orderId,
refundOk ? "cancelled" : "cancellation_pending",
);
logger.info(`Order ${orderId} cancelled (refund ok: ${refundOk})`);
return { success: true, orderId };
}
......@@ -252,7 +279,9 @@ export async function listOrdersForCustomer(
// ── Order enrichment ───────────────────────────────────────────────────────
export async function enrichOrderWithTracking(order: Order): Promise<Order & { trackingUrl: string }> {
export async function enrichOrderWithTracking(
order: Order,
): Promise<Order & { trackingUrl: string }> {
if (!order.trackingNumber) {
throw new Error("Order has no tracking number");
}
......@@ -268,7 +297,9 @@ export async function estimateDeliveryDate(
): Promise<string> {
const baseDays = expedited ? 2 : 5;
const regionBuffer = address.country !== "US" ? 7 : 0;
const estimate = new Date(Date.now() + (baseDays + regionBuffer) * 24 * 3600 * 1000);
const estimate = new Date(
Date.now() + (baseDays + regionBuffer) * 24 * 3600 * 1000,
);
return estimate.toISOString();
}
......@@ -301,7 +332,9 @@ export async function processOrderIdempotent(
): Promise<ProcessResult> {
const existing = checkIdempotency(idempotencyKey);
if (existing) {
logger.info(`Idempotency hit: returning existing order ${existing} for key ${idempotencyKey}`);
logger.info(
`Idempotency hit: returning existing order ${existing} for key ${idempotencyKey}`,
);
return { success: true, orderId: existing };
}
const result = await processOrder(order);
......
......@@ -110,16 +110,27 @@ export function createPolicy(role: string): Policy {
export function hasPermission(policy: Policy, action: string): boolean {
switch (action) {
case "view_content": return policy.canViewContent();
case "create_content": return policy.canCreateContent();
case "edit_content": return policy.canEditContent();
case "delete_content": return policy.canDeleteContent();
case "view_users": return policy.canViewUsers();
case "manage_users": return policy.canManageUsers();
case "view_roles": return policy.canViewRoles();
case "manage_roles": return policy.canManageRoles();
case "view_audit_log": return policy.canViewAuditLog();
case "export_data": return policy.canExportData();
default: return false;
case "view_content":
return policy.canViewContent();
case "create_content":
return policy.canCreateContent();
case "edit_content":
return policy.canEditContent();
case "delete_content":
return policy.canDeleteContent();
case "view_users":
return policy.canViewUsers();
case "manage_users":
return policy.canManageUsers();
case "view_roles":
return policy.canViewRoles();
case "manage_roles":
return policy.canManageRoles();
case "view_audit_log":
return policy.canViewAuditLog();
case "export_data":
return policy.canExportData();
default:
return false;
}
}
......@@ -37,8 +37,18 @@ interface ReportRange {
}
const MONTH_NAMES = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
// ── Sales reports ──────────────────────────────────────────────────────────
......@@ -117,7 +127,10 @@ export function salesByRegion(
s.region,
(revenueByRegion.get(s.region) ?? 0) + s.unitPrice * s.quantity,
);
unitsByRegion.set(s.region, (unitsByRegion.get(s.region) ?? 0) + s.quantity);
unitsByRegion.set(
s.region,
(unitsByRegion.get(s.region) ?? 0) + s.quantity,
);
}
const rows: Array<{ region: string; revenue: string; units: number }> = [];
......@@ -208,7 +221,10 @@ export function refundsByReason(
const amountByReason = new Map<string, number>();
const countByReason = new Map<string, number>();
for (const r of inRange) {
amountByReason.set(r.reason, (amountByReason.get(r.reason) ?? 0) + r.amount);
amountByReason.set(
r.reason,
(amountByReason.get(r.reason) ?? 0) + r.amount,
);
countByReason.set(r.reason, (countByReason.get(r.reason) ?? 0) + 1);
}
......@@ -307,10 +323,7 @@ export function mrrByPlan(
return rows;
}
export function churnRate(
subs: Subscription[],
range: ReportRange,
): string {
export function churnRate(subs: Subscription[], range: ReportRange): string {
const fromTs = Date.parse(range.from);
const toTs = Date.parse(range.to);
......@@ -369,10 +382,7 @@ export function topCustomersByRevenue(
}));
}
export function averageSaleValue(
sales: Sale[],
range: ReportRange,
): string {
export function averageSaleValue(sales: Sale[], range: ReportRange): string {
const fromTs = Date.parse(range.from);
const toTs = Date.parse(range.to);
const inRange = sales.filter((s) => {
......
......@@ -91,7 +91,9 @@ export async function archiveProject(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`archiveProject called with invalid id from user ${req.userId}`);
logger.warn(
`archiveProject called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -116,7 +118,9 @@ export async function getProjectMembers(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`getProjectMembers called with invalid id from user ${req.userId}`);
logger.warn(
`getProjectMembers called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -141,7 +145,9 @@ export async function addProjectMember(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`addProjectMember called with invalid id from user ${req.userId}`);
logger.warn(
`addProjectMember called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -151,7 +157,9 @@ export async function addProjectMember(
"INSERT INTO project_members (project_id, user_id, role, added_at) VALUES (?, ?, ?, ?)",
[id, memberId, role, new Date().toISOString()],
);
logger.info(`addProjectMember(${id}, member=${memberId}) took ${Date.now() - start}ms`);
logger.info(
`addProjectMember(${id}, member=${memberId}) took ${Date.now() - start}ms`,
);
res.status(201).json({ ok: true });
}
......@@ -167,7 +175,9 @@ export async function removeProjectMember(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`removeProjectMember called with invalid id from user ${req.userId}`);
logger.warn(
`removeProjectMember called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -177,7 +187,9 @@ export async function removeProjectMember(
"DELETE FROM project_members WHERE project_id = ? AND user_id = ?",
[id, memberId],
);
logger.info(`removeProjectMember(${id}, member=${memberId}) took ${Date.now() - start}ms`);
logger.info(
`removeProjectMember(${id}, member=${memberId}) took ${Date.now() - start}ms`,
);
res.json({ ok: true });
}
......@@ -187,13 +199,17 @@ export async function transferProjectOwnership(
): Promise<void> {
const start = Date.now();
if (!req.userId) {
logger.warn(`transferProjectOwnership called without userId from ${req.ip}`);
logger.warn(
`transferProjectOwnership called without userId from ${req.ip}`,
);
res.status(401).json({ error: "unauthorized" });
return;
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`transferProjectOwnership called with invalid id from user ${req.userId}`);
logger.warn(
`transferProjectOwnership called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -203,7 +219,9 @@ export async function transferProjectOwnership(
"UPDATE projects SET owner_id = ?, updated_at = ? WHERE id = ?",
[newOwnerId, new Date().toISOString(), id],
);
logger.info(`transferProjectOwnership(${id}, newOwner=${newOwnerId}) took ${Date.now() - start}ms`);
logger.info(
`transferProjectOwnership(${id}, newOwner=${newOwnerId}) took ${Date.now() - start}ms`,
);
res.json({ ok: true });
}
......@@ -219,7 +237,9 @@ export async function listProjectVersions(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`listProjectVersions called with invalid id from user ${req.userId}`);
logger.warn(
`listProjectVersions called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -244,7 +264,9 @@ export async function restoreProjectVersion(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`restoreProjectVersion called with invalid id from user ${req.userId}`);
logger.warn(
`restoreProjectVersion called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -259,11 +281,14 @@ export async function restoreProjectVersion(
return;
}
await db.query(
"UPDATE projects SET data = ?, updated_at = ? WHERE id = ?",
[(versionRows[0] as { data: unknown }).data, new Date().toISOString(), id],
await db.query("UPDATE projects SET data = ?, updated_at = ? WHERE id = ?", [
(versionRows[0] as { data: unknown }).data,
new Date().toISOString(),
id,
]);
logger.info(
`restoreProjectVersion(${id}, version=${versionId}) took ${Date.now() - start}ms`,
);
logger.info(`restoreProjectVersion(${id}, version=${versionId}) took ${Date.now() - start}ms`);
res.json({ ok: true });
}
......@@ -279,7 +304,9 @@ export async function duplicateProject(
}
const id = req.params.id;
if (!id || typeof id !== "string") {
logger.warn(`duplicateProject called with invalid id from user ${req.userId}`);
logger.warn(
`duplicateProject called with invalid id from user ${req.userId}`,
);
res.status(400).json({ error: "invalid id" });
return;
}
......@@ -295,6 +322,8 @@ export async function duplicateProject(
"INSERT INTO projects (name, owner_id, status, data, created_at) SELECT ?, owner_id, 'active', data, ? FROM projects WHERE id = ? RETURNING id",
[name, new Date().toISOString(), id],
);
logger.info(`duplicateProject(${id}${(newRows[0] as { id: string }).id}) took ${Date.now() - start}ms`);
logger.info(
`duplicateProject(${id}${(newRows[0] as { id: string }).id}) took ${Date.now() - start}ms`,
);
res.status(201).json(newRows[0]);
}
......@@ -193,7 +193,10 @@ export async function updateUserHandler(
return;
}
if (body.age !== undefined && (typeof body.age !== "number" || body.age < 0)) {
if (
body.age !== undefined &&
(typeof body.age !== "number" || body.age < 0)
) {
res.status(400).json({ error: "age must be a non-negative number" });
return;
}
......@@ -255,17 +258,20 @@ export async function changeRoleHandler(
return;
}
const existing = await db.query("SELECT id, role FROM users WHERE id = ?", [id]);
const existing = await db.query("SELECT id, role FROM users WHERE id = ?", [
id,
]);
if (existing.length === 0) {
res.status(404).json({ error: "user not found" });
return;
}
const previousRole = (existing[0] as { role: string }).role;
await db.query(
"UPDATE users SET role = ?, updated_at = ? WHERE id = ?",
[role, new Date().toISOString(), id],
);
await db.query("UPDATE users SET role = ?, updated_at = ? WHERE id = ?", [
role,
new Date().toISOString(),
id,
]);
logger.info(`changed role for user ${id}: ${previousRole}${role}`);
res.json({ id, role });
......
......@@ -161,13 +161,16 @@ export function reactivateUser(id: string): Promise<User> {
// ── Listing ────────────────────────────────────────────────────────────────
export function listUsers(page: number, limit: number): Promise<PaginatedUsers> {
export function listUsers(
page: number,
limit: number,
): Promise<PaginatedUsers> {
const offset = (page - 1) * limit;
return db
.query(
"SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?",
[limit, offset],
)
.query("SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?", [
limit,
offset,
])
.then((rows) => {
return db
.query("SELECT COUNT(*) AS total FROM users", [])
......@@ -213,10 +216,18 @@ export function logAuditEntry(entry: UserAuditEntry): Promise<void> {
return db
.query(
"INSERT INTO user_audit (user_id, action, performed_by, timestamp, metadata) VALUES (?, ?, ?, ?, ?)",
[entry.userId, entry.action, entry.performedBy, entry.timestamp, JSON.stringify(entry.metadata)],
[
entry.userId,
entry.action,
entry.performedBy,
entry.timestamp,
JSON.stringify(entry.metadata),
],
)
.then(() => {
logger.info(`audit: ${entry.action} on user ${entry.userId} by ${entry.performedBy}`);
logger.info(
`audit: ${entry.action} on user ${entry.userId} by ${entry.performedBy}`,
);
})
.catch((err) => {
logger.error(`logAuditEntry failed for userId=${entry.userId}`, err);
......@@ -245,7 +256,11 @@ export function requestEmailVerification(id: string): Promise<void> {
if (!user) throw new Error(`user ${id} not found`);
return db.query(
"INSERT INTO email_verifications (user_id, token, expires_at) VALUES (?, ?, ?)",
[id, Math.random().toString(36).slice(2), new Date(Date.now() + 24 * 3600 * 1000).toISOString()],
[
id,
Math.random().toString(36).slice(2),
new Date(Date.now() + 24 * 3600 * 1000).toISOString(),
],
);
})
.then(() => {
......@@ -264,8 +279,12 @@ export function verifyEmail(id: string, token: string): Promise<User> {
[id, token, new Date().toISOString()],
)
.then((rows) => {
if (rows.length === 0) throw new Error("invalid or expired verification token");
return db.query("UPDATE users SET email_verified = 1 WHERE id = ? RETURNING *", [id]);
if (rows.length === 0)
throw new Error("invalid or expired verification token");
return db.query(
"UPDATE users SET email_verified = 1 WHERE id = ? RETURNING *",
[id],
);
})
.then((rows) => {
logger.info(`verified email for user ${id}`);
......@@ -292,9 +311,15 @@ export function requestPasswordReset(email: string): Promise<void> {
return db
.query(
"INSERT INTO password_resets (user_id, token, expires_at) VALUES (?, ?, ?)",
[userId, Math.random().toString(36).slice(2), new Date(Date.now() + 3600 * 1000).toISOString()],
[
userId,
Math.random().toString(36).slice(2),
new Date(Date.now() + 3600 * 1000).toISOString(),
],
)
.then(() => { logger.info(`password reset token created for user ${userId}`); });
.then(() => {
logger.info(`password reset token created for user ${userId}`);
});
})
.catch((err) => {
logger.error(`requestPasswordReset failed for email`, err);
......@@ -302,7 +327,10 @@ export function requestPasswordReset(email: string): Promise<void> {
});
}
export function resetPassword(token: string, newPasswordHash: string): Promise<void> {
export function resetPassword(
token: string,
newPasswordHash: string,
): Promise<void> {
return db
.query(
"SELECT user_id FROM password_resets WHERE token = ? AND expires_at > ? AND used = 0",
......@@ -312,13 +340,18 @@ export function resetPassword(token: string, newPasswordHash: string): Promise<v
if (rows.length === 0) throw new Error("invalid or expired reset token");
const userId = (rows[0] as { user_id: string }).user_id;
return db
.query("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?", [
newPasswordHash,
new Date().toISOString(),
userId,
])
.then(() => db.query("UPDATE password_resets SET used = 1 WHERE token = ?", [token]))
.then(() => { logger.info(`password reset completed for user ${userId}`); });
.query(
"UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?",
[newPasswordHash, new Date().toISOString(), userId],
)
.then(() =>
db.query("UPDATE password_resets SET used = 1 WHERE token = ?", [
token,
]),
)
.then(() => {
logger.info(`password reset completed for user ${userId}`);
});
})
.catch((err) => {
logger.error(`resetPassword failed`, err);
......
......@@ -271,14 +271,8 @@ export function recordDirFor(
caseName: string,
modelLabel: string,
): string {
const runDirName =
`${fsTimestamp(RUN_START_TIMESTAMP)}__${sanitize(modelLabel)}`;
return resolve(
RESULTS_ROOT,
sanitize(suite),
runDirName,
sanitize(caseName),
);
const runDirName = `${fsTimestamp(RUN_START_TIMESTAMP)}__${sanitize(modelLabel)}`;
return resolve(RESULTS_ROOT, sanitize(suite), runDirName, sanitize(caseName));
}
export async function recordEvalRun(record: EvalRunRecord): Promise<void> {
......
......@@ -201,7 +201,7 @@ const evalFetch: typeof fetch = async (input, init) => {
// we can surface token counts in the reassembled non-streaming
// response instead of hard-coding zeros.
parsed.stream_options = {
...(parsed.stream_options ?? {}),
...parsed.stream_options,
include_usage: true,
};
const modifiedInit = { ...init, body: JSON.stringify(parsed) };
......
......@@ -39,7 +39,7 @@ function computeLcsTable(
const m = oldLines.length;
const n = newLines.length;
const dp: number[][] = Array.from({ length: m + 1 }, () =>
new Array<number>(n + 1).fill(0),
Array.from<number>({ length: n + 1 }).fill(0),
);
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
......@@ -99,7 +99,9 @@ function buildHunks(
positioned: readonly PositionedOp[],
context: number,
): Hunk[] {
const include = new Array<boolean>(positioned.length).fill(false);
const include = Array.from<boolean>({ length: positioned.length }).fill(
false,
);
for (let i = 0; i < positioned.length; i++) {
if (positioned[i].op.type !== "keep") {
const lo = Math.max(0, i - context);
......
......@@ -752,8 +752,7 @@ const SUITES: SuiteConfig[] = [
// (see helpers/prompts.ts) so prompt variations can be recorded
// without modifying the production prompt.
name: "pro_agent_experimental",
displayName:
"pro_agent_experimental (pro_agent with editable prompt copy)",
displayName: "pro_agent_experimental (pro_agent with editable prompt copy)",
systemPrompt: PRO_AGENT_EXPERIMENTAL_SYSTEM_PROMPT,
buildTools: (state, c, label) => ({
search_replace: searchReplaceHarnessTool(state, c, label),
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论