Cleanup: extract constants, fix typecheck, add ESLint, organize store types
- Remove unused initCount state from useAuthGate hook - Replace magic number with MAX_SAVES_PER_USER constant in saves route - Extract duplicated EMAIL_REGEX and MIN_PASSWORD_LENGTH in auth routes - Fix game-simulation typecheck failure by adding DOM lib to tsconfig - Extract store UI types (ActivePage, InfraNav, etc.) to store/types.ts - Fix let→const for non-reassigned arrays in servingPipeline - Fix useless initial assignments in reputationSystem - Fix ambiguous multiline array access in sanityChecks - Add minimal ESLint config with typescript-eslint - Add .planning/ and *.log to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,3 +10,5 @@ balance-report*.json
|
||||
balance-metrics*.csv
|
||||
multirun-summary.csv
|
||||
multirun-timeseries.csv
|
||||
.planning/
|
||||
*.log
|
||||
|
||||
@@ -7,6 +7,9 @@ import { createToken } from '../lib/jwt';
|
||||
import { authMiddleware } from '../middleware/auth';
|
||||
import type { AppEnv } from '../types';
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
const auth = new Hono<AppEnv>();
|
||||
|
||||
auth.post('/anonymous', async (c) => {
|
||||
@@ -27,11 +30,11 @@ auth.post('/register', authMiddleware, async (c) => {
|
||||
inviteCode: string;
|
||||
}>();
|
||||
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
if (!email || !EMAIL_REGEX.test(email)) {
|
||||
return c.json({ error: 'Valid email required' }, 400);
|
||||
}
|
||||
if (!password || password.length < 8) {
|
||||
return c.json({ error: 'Password must be at least 8 characters' }, 400);
|
||||
if (!password || password.length < MIN_PASSWORD_LENGTH) {
|
||||
return c.json({ error: `Password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
|
||||
}
|
||||
|
||||
if (process.env.REQUIRE_INVITE !== 'false') {
|
||||
@@ -117,8 +120,8 @@ auth.post('/change-password', authMiddleware, async (c) => {
|
||||
newPassword: string;
|
||||
}>();
|
||||
|
||||
if (!newPassword || newPassword.length < 8) {
|
||||
return c.json({ error: 'New password must be at least 8 characters' }, 400);
|
||||
if (!newPassword || newPassword.length < MIN_PASSWORD_LENGTH) {
|
||||
return c.json({ error: `New password must be at least ${MIN_PASSWORD_LENGTH} characters` }, 400);
|
||||
}
|
||||
|
||||
if (!user.mustResetPassword) {
|
||||
@@ -193,7 +196,7 @@ auth.post('/change-email', authMiddleware, async (c) => {
|
||||
currentPassword: string;
|
||||
}>();
|
||||
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
if (!email || !EMAIL_REGEX.test(email)) {
|
||||
return c.json({ error: 'Valid email required' }, 400);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ savesRouter.get('/', async (c) => {
|
||||
.from(saves)
|
||||
.where(eq(saves.userId, userId))
|
||||
.orderBy(desc(saves.updatedAt))
|
||||
.limit(10);
|
||||
.limit(MAX_SAVES_PER_USER);
|
||||
|
||||
return c.json({ saves: userSaves });
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@ export function useAuthGate(): AuthGateState {
|
||||
const [admin, setAdmin] = useState(false);
|
||||
const [cloudSave, setCloudSave] = useState<CloudSaveInfo | null>(null);
|
||||
const [hasConflict, setHasConflict] = useState(false);
|
||||
const [initCount, setInitCount] = useState(0);
|
||||
|
||||
const init = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -98,7 +97,6 @@ export function useAuthGate(): AuthGateState {
|
||||
useState(() => { init(); });
|
||||
|
||||
const retry = useCallback(() => {
|
||||
setInitCount(c => c + 1);
|
||||
init();
|
||||
}, [init]);
|
||||
|
||||
|
||||
@@ -46,37 +46,9 @@ import {
|
||||
TECH_TREE, onModelDeployed,
|
||||
} from '@token-empire/game-engine';
|
||||
import { INITIAL_RIVALS } from '@token-empire/game-engine';
|
||||
import type { ActivePage, InfraNav, ModelsTab, UIState, GameNotification } from './types';
|
||||
|
||||
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
|
||||
| 'market' | 'serving' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'invitations' | 'settings';
|
||||
|
||||
export type InfraNavLevel = 'clusters' | 'cluster' | 'campus' | 'datacenter';
|
||||
|
||||
export interface InfraNav {
|
||||
level: InfraNavLevel;
|
||||
clusterId?: string;
|
||||
campusId?: string;
|
||||
datacenterId?: string;
|
||||
}
|
||||
|
||||
type ModelsTab = 'overview' | 'train' | 'models' | 'products';
|
||||
|
||||
interface UIState {
|
||||
activePage: ActivePage;
|
||||
notifications: GameNotification[];
|
||||
infraNav: InfraNav;
|
||||
modelsTab: ModelsTab;
|
||||
}
|
||||
|
||||
export interface GameNotification {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'info' | 'success' | 'warning' | 'danger';
|
||||
tick: number;
|
||||
read: boolean;
|
||||
action?: { label: string; page?: ActivePage; modelsTab?: ModelsTab };
|
||||
}
|
||||
export type { ActivePage, InfraNavLevel, InfraNav, ModelsTab, UIState, GameNotification } from './types';
|
||||
|
||||
function emptyDC(): Pick<DataCenter, 'networkSummary' | 'effectiveComputeRacks' | 'usedSlots' | 'usedPowerKW' | 'energyCostPerTick' | 'maintenanceCostPerTick' | 'currentUptime'> {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
|
||||
| 'market' | 'serving' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'invitations' | 'settings';
|
||||
|
||||
export type InfraNavLevel = 'clusters' | 'cluster' | 'campus' | 'datacenter';
|
||||
|
||||
export interface InfraNav {
|
||||
level: InfraNavLevel;
|
||||
clusterId?: string;
|
||||
campusId?: string;
|
||||
datacenterId?: string;
|
||||
}
|
||||
|
||||
export type ModelsTab = 'overview' | 'train' | 'models' | 'products';
|
||||
|
||||
export interface UIState {
|
||||
activePage: ActivePage;
|
||||
notifications: GameNotification[];
|
||||
infraNav: InfraNav;
|
||||
modelsTab: ModelsTab;
|
||||
}
|
||||
|
||||
export interface GameNotification {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'info' | 'success' | 'warning' | 'danger';
|
||||
tick: number;
|
||||
read: boolean;
|
||||
action?: { label: string; page?: ActivePage; modelsTab?: ModelsTab };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
ignores: ['**/dist/**', '**/node_modules/**', '**/*.js', '**/*.mjs', '**/drizzle/**'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
'preserve-caught-error': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
+4
-1
@@ -5,7 +5,7 @@
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build",
|
||||
"typecheck": "turbo typecheck",
|
||||
"lint": "turbo lint",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "turbo clean",
|
||||
@@ -13,8 +13,11 @@
|
||||
"simulate:ci": "pnpm --filter @token-empire/game-simulation simulate:ci"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"eslint": "^10.2.1",
|
||||
"turbo": "^2.5.0",
|
||||
"typescript": "^5.8.0",
|
||||
"typescript-eslint": "^8.59.1",
|
||||
"vitest": "^4.1.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
|
||||
@@ -75,7 +75,7 @@ interface CachedSlot {
|
||||
}
|
||||
|
||||
let cachedDeploymentVersion = -1;
|
||||
let cachedSlots: CachedSlot[] = [];
|
||||
const cachedSlots: CachedSlot[] = [];
|
||||
const fleetOutput: ModelServingSlot[] = [];
|
||||
|
||||
const mainRemaining = new Map<string, number>();
|
||||
@@ -83,7 +83,7 @@ const mainUsed = new Map<string, number>();
|
||||
const entRemaining = new Map<string, number>();
|
||||
const entUsed = new Map<string, number>();
|
||||
|
||||
let cachedUtilization: ModelUtilizationEntry[] = [];
|
||||
const cachedUtilization: ModelUtilizationEntry[] = [];
|
||||
|
||||
export function resetFleetCache(): void {
|
||||
cachedDeploymentVersion = -1;
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface ReputationTickResult {
|
||||
}
|
||||
|
||||
export function processReputation(state: GameState, researchBonuses?: ResearchBonuses): ReputationState & { _safetyIncident?: boolean } {
|
||||
let { safetyRecord, publicPerception, employeeSatisfaction, regulatoryStanding } = state.reputation;
|
||||
let { safetyRecord, publicPerception } = state.reputation;
|
||||
|
||||
let safetyIncident = false;
|
||||
if (state.models.bestDeployedSafetyScore > 0) {
|
||||
@@ -39,13 +39,13 @@ export function processReputation(state: GameState, researchBonuses?: ResearchBo
|
||||
const safetyResearchCount = state.research.completedResearch
|
||||
.filter(r => r.includes('alignment') || r.includes('interpretability') || r.includes('constitutional')).length;
|
||||
const complianceBonus = safetyResearchCount * 8;
|
||||
regulatoryStanding = Math.min(100, Math.max(0,
|
||||
const regulatoryStanding = Math.min(100, Math.max(0,
|
||||
50 + complianceBonus - regulatoryPressure,
|
||||
));
|
||||
|
||||
const talentMorale = Object.values(state.talent.departments)
|
||||
.reduce((sum, d) => sum + d.morale, 0) / 4;
|
||||
employeeSatisfaction = talentMorale * 100;
|
||||
const employeeSatisfaction = talentMorale * 100;
|
||||
|
||||
const reputationResearchBonus = researchBonuses?.reputationBonus ?? 0;
|
||||
publicPerception = Math.min(100, publicPerception + reputationResearchBonus * PUBLIC_PERCEPTION_GROWTH_RATE);
|
||||
|
||||
@@ -55,8 +55,8 @@ export function runSanityChecks(metrics: SimulationMetrics[]): SanityCheckResult
|
||||
const key = 'reputation-scale-consistency';
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
const lowName = ['safetyRecord', 'publicPerception', 'employeeSatisfaction', 'regulatoryStanding']
|
||||
[components.indexOf(belowThreshold[0])];
|
||||
const reputationFields = ['safetyRecord', 'publicPerception', 'employeeSatisfaction', 'regulatoryStanding'];
|
||||
const lowName = reputationFields[components.indexOf(belowThreshold[0])];
|
||||
violations.push({
|
||||
tick: m.tick, check: key,
|
||||
message: `${lowName} = ${belowThreshold[0].toFixed(2)} while others are 10+. Likely a scale mismatch (0-1 vs 0-100)`,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "@token-empire/tsconfig/node.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
|
||||
Generated
+706
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user