Wave 1: add deps, crypto random utility, i18n helper, privacy fix, CI gates
- fix(deps): add zod and typescript to package.json - feat(lib): add cryptographically secure random utility with TDD - feat(lib): add client i18n helper with TDD - fix(i18n): update privacy policy for crypto accuracy - ci(deploy): add test and lint gates, fix SSH security
This commit is contained in:
@@ -16,6 +16,8 @@ jobs:
|
||||
cache: npm
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run test
|
||||
- run: npm run build
|
||||
|
||||
- name: Setup SSH key
|
||||
@@ -28,6 +30,6 @@ jobs:
|
||||
- name: Deploy via rsync
|
||||
run: |
|
||||
rsync -avz --delete \
|
||||
-e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \
|
||||
-e "ssh -i ~/.ssh/deploy_key" \
|
||||
dist/ \
|
||||
${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:~/www/randify.pro/
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"active_work_id": "critical-refactor-f063daf0",
|
||||
"works": {
|
||||
"critical-refactor-f063daf0": {
|
||||
"work_id": "critical-refactor-f063daf0",
|
||||
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
|
||||
"plan_name": "critical-refactor",
|
||||
"status": "active",
|
||||
"started_at": "2026-05-13T16:25:19.266Z",
|
||||
"updated_at": "2026-05-13T16:33:52.668Z",
|
||||
"session_ids": [
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||
],
|
||||
"session_origins": {
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR": "direct"
|
||||
},
|
||||
"agent": "atlas",
|
||||
"task_sessions": {
|
||||
"todo:1": {
|
||||
"task_key": "todo:1",
|
||||
"task_label": "1",
|
||||
"task_title": "Add missing dependencies to package.json",
|
||||
"session_id": "ses_1ddd57501ffeO4NlJDKb2pwLz2",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "writing",
|
||||
"started_at": "2026-05-13T16:27:48.876Z",
|
||||
"status": "completed",
|
||||
"updated_at": "2026-05-13T16:33:52.668Z",
|
||||
"ended_at": "2026-05-13T16:33:52.668Z",
|
||||
"elapsed_ms": 363792
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
|
||||
"started_at": "2026-05-13T16:25:19.266Z",
|
||||
"status": "active",
|
||||
"updated_at": "2026-05-13T16:33:52.668Z",
|
||||
"session_ids": [
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||
],
|
||||
"session_origins": {
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR": "direct"
|
||||
},
|
||||
"plan_name": "critical-refactor",
|
||||
"task_sessions": {
|
||||
"todo:1": {
|
||||
"task_key": "todo:1",
|
||||
"task_label": "1",
|
||||
"task_title": "Add missing dependencies to package.json",
|
||||
"session_id": "ses_1ddd57501ffeO4NlJDKb2pwLz2",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "writing",
|
||||
"started_at": "2026-05-13T16:27:48.876Z",
|
||||
"status": "completed",
|
||||
"updated_at": "2026-05-13T16:33:52.668Z",
|
||||
"ended_at": "2026-05-13T16:33:52.668Z",
|
||||
"elapsed_ms": 363792
|
||||
}
|
||||
},
|
||||
"agent": "atlas"
|
||||
}
|
||||
@@ -0,0 +1,942 @@
|
||||
# Critical Fixes + Quality Refactoring
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Replace all `Math.random()` calls with a shared cryptographically secure `crypto.getRandomValues()` wrapper, deduplicate the dice engine by importing `src/lib/dice-engine.ts`, extract reusable Astro components for result/copy UI, and refactor all 28 generators to consume the new shared patterns. Fix CI to gate deployment on passing tests and lint.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - `src/lib/client/random.ts` + test (crypto wrapper)
|
||||
> - `src/lib/client/i18n.ts` + test (client locale helper)
|
||||
> - `src/components/ResultBox.astro` (reusable result container)
|
||||
> - `src/components/CopyButton.astro` (reusable copy button using existing `CopyFeedback`)
|
||||
> - 28 refactored generators using new utilities
|
||||
> - `DiceGenerator.astro` importing from `dice-engine.ts`
|
||||
> - Updated `dice-engine.ts` using crypto wrapper
|
||||
> - Updated Privacy Policy translations
|
||||
> - Fixed `.github/workflows/deploy.yml`
|
||||
>
|
||||
> **Estimated Effort**: Large
|
||||
> **Parallel Execution**: YES - 5-7 tasks per wave
|
||||
> **Critical Path**: Wave 1 (utilities) → Wave 2 (components) → Waves 3-6 (generator batches) → Wave 7 (dice + policy) → Wave 8 (CI) → F1-F4
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
User wants to fix critical issues and quality-refactor all generators into an easily extensible structure. No push to main without permission.
|
||||
|
||||
### Interview Summary
|
||||
**Key Decisions**:
|
||||
- Critical scope: Dice engine + honesty (Web Crypto API migration)
|
||||
- Tests: YES - TDD for new utilities, tests-after + agent QA for generators
|
||||
- Refactor scope: ALL 28 generators
|
||||
- CI/CD: Fix deploy.yml to run tests and lint
|
||||
- Quality over speed
|
||||
|
||||
### Research Findings
|
||||
- **28 generators** exist (not 10 as stated in outdated CLAUDE.md)
|
||||
- **29 files use `Math.random()`** despite aboutHowItWorks claiming Web Crypto API
|
||||
- **Dice engine duplicated**: `DiceGenerator.astro:274-958` inlines `src/lib/dice-engine.ts`
|
||||
- **Copy-paste UI**: Every generator recreates result + copy button DOM
|
||||
- **Hardcoded i18n**: Strings in generator frontmatter/scripts instead of `translations.ts`
|
||||
- **Missing `zod`**: Not in `package.json` despite direct import
|
||||
- **CI gap**: Tests and lint exist but deploy.yml skips them
|
||||
|
||||
### Metis Review
|
||||
**Identified Gaps** (addressed):
|
||||
- Core objective tightened to single sentence
|
||||
- Scope IN/OUT explicitly defined
|
||||
- Test strategy recorded (TDD for utilities)
|
||||
- Existing utilities inventoried (`clipboard.ts`, `validation.ts`, `animations.ts`)
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Replace every `Math.random()` call across all 28 generators with a shared cryptographically secure wrapper, deduplicate the dice engine by importing `src/lib/dice-engine.ts`, and refactor all generators to consume the new shared utilities.
|
||||
|
||||
### Concrete Deliverables
|
||||
- `src/lib/client/random.ts` + `random.test.ts`
|
||||
- `src/lib/client/i18n.ts` + `i18n.test.ts`
|
||||
- `src/components/ResultBox.astro`
|
||||
- `src/components/CopyButton.astro`
|
||||
- All 28 `*Generator.astro` files refactored
|
||||
- `DiceGenerator.astro` importing from `dice-engine.ts`
|
||||
- `src/lib/dice-engine.ts` updated to use `random.ts`
|
||||
- Updated Privacy Policy in `src/i18n/translations.ts`
|
||||
- Fixed `.github/workflows/deploy.yml`
|
||||
|
||||
### Definition of Done
|
||||
- [ ] `npm run test` passes (new + existing tests)
|
||||
- [ ] `npm run lint` passes
|
||||
- [ ] `npm run build` succeeds
|
||||
- [ ] No `Math.random()` calls remain in generator scripts
|
||||
- [ ] Dice engine is imported, not inlined
|
||||
- [ ] CI runs tests and lint before deploy
|
||||
|
||||
### Must Have
|
||||
- Cryptographically secure random for all generators
|
||||
- Deduplicated dice engine
|
||||
- Shared utilities for random + i18n
|
||||
- Shared Astro components for result/copy UI
|
||||
- All 28 generators refactored
|
||||
- CI quality gates
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- **NO shared dynamic `[slug].astro`** for Russian pages (breaks script bundling)
|
||||
- **NO changes to generator JSON schema** (strict Zod — unknown fields fail build)
|
||||
- **NO client-side router introduced**
|
||||
- **NO push to main** without explicit user approval
|
||||
- **NO removal of existing EN/RU page files** — keep separate files
|
||||
- **NO over-abstraction** — keep utilities simple and focused
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed.
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES (vitest v4.1.5 configured)
|
||||
- **Automated tests**: TDD for utilities, tests-after for generators
|
||||
- **Framework**: vitest (node environment)
|
||||
- **TDD workflow**: RED (failing test) → GREEN (minimal impl) → REFACTOR
|
||||
|
||||
### QA Policy
|
||||
Every task MUST include agent-executed QA scenarios.
|
||||
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`.
|
||||
|
||||
- **Frontend/UI**: Use Playwright — Navigate, interact, assert DOM, screenshot
|
||||
- **Library/Module**: Use Bash (bun/node REPL) — Import, call functions, compare output
|
||||
- **API/Config**: Use Bash (grep) — Search patterns, verify absence
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Parallel Execution Waves
|
||||
|
||||
```
|
||||
Wave 1 (Foundation - utilities + deps + CI draft, 5 tasks):
|
||||
├── Task 1: Add zod + typescript to package.json
|
||||
├── Task 2: Create crypto random utility (TDD)
|
||||
├── Task 3: Create client i18n helper (TDD)
|
||||
├── Task 35: Update Privacy Policy translations
|
||||
└── Task 36: Fix deploy.yml quality gates
|
||||
|
||||
Wave 2 (Shared components + dice crypto + CI verify, 4 tasks):
|
||||
├── Task 4: Create ResultBox.astro
|
||||
├── Task 5: Create CopyButton.astro
|
||||
├── Task 34: Update dice-engine.ts to use crypto random
|
||||
└── Task 37: Verify CI blocks deploy on failure
|
||||
|
||||
Wave 3 (Generators batch 1 + dice refactor, 8 tasks):
|
||||
├── Task 6: Refactor CardGenerator
|
||||
├── Task 7: Refactor CoinGenerator
|
||||
├── Task 8: Refactor ColorGenerator
|
||||
├── Task 9: Refactor CountryGenerator
|
||||
├── Task 10: Refactor DateGenerator
|
||||
├── Task 11: Refactor EmojiGenerator
|
||||
├── Task 12: Refactor FontPairGenerator
|
||||
└── Task 33: Refactor DiceGenerator to import dice-engine
|
||||
|
||||
Wave 4 (Generators batch 2, 7 tasks):
|
||||
├── Task 13: Refactor GradientGenerator
|
||||
├── Task 14: Refactor HashGenerator
|
||||
├── Task 15: Refactor LetterGenerator
|
||||
├── Task 16: Refactor ListGenerator
|
||||
├── Task 17: Refactor LoremGenerator
|
||||
├── Task 18: Refactor LotteryGenerator
|
||||
└── Task 19: Refactor Magic8BallGenerator
|
||||
|
||||
Wave 5 (Generators batch 3, 7 tasks):
|
||||
├── Task 20: Refactor MealGenerator
|
||||
├── Task 21: Refactor NamesGenerator
|
||||
├── Task 22: Refactor NumberGenerator
|
||||
├── Task 23: Refactor PaletteGenerator
|
||||
├── Task 24: Refactor PasswordGenerator
|
||||
├── Task 25: Refactor RpsGenerator
|
||||
└── Task 26: Refactor ShufflerGenerator
|
||||
|
||||
Wave 6 (Generators batch 4, 6 tasks):
|
||||
├── Task 27: Refactor TeamsGenerator
|
||||
├── Task 28: Refactor TimeGenerator
|
||||
├── Task 29: Refactor UuidGenerator
|
||||
├── Task 30: Refactor WeightedGenerator
|
||||
├── Task 31: Refactor WheelSpinner
|
||||
└── Task 32: Refactor YesNoGenerator
|
||||
|
||||
Wave FINAL (After ALL tasks - 4 parallel reviews):
|
||||
├── Task F1: Plan compliance audit (oracle)
|
||||
├── Task F2: Code quality review (unspecified-high)
|
||||
├── Task F3: Real manual QA (unspecified-high)
|
||||
└── Task F4: Scope fidelity check (deep)
|
||||
-> Present results -> Get explicit user okay
|
||||
|
||||
Critical Path: Wave 1 → Wave 2 → Waves 3-6 → F1-F4
|
||||
Parallel Speedup: ~70% faster than sequential
|
||||
Max Concurrent: 8 (Wave 3)
|
||||
```
|
||||
|
||||
### Dependency Matrix
|
||||
|
||||
- **1**: - - 2, 3
|
||||
- **2**: - - 4, 5, 34
|
||||
- **3**: - - 4, 5, 6-32
|
||||
- **4**: 2, 3 - 6-33
|
||||
- **5**: 2, 3 - 6-33
|
||||
- **6-32**: 4, 5 - F1-F4
|
||||
- **33**: 4, 5, 34 - F1-F4
|
||||
- **34**: 2 - 33, F1-F4
|
||||
- **35**: - - F1-F4
|
||||
- **36**: - - 37, F1-F4
|
||||
- **37**: 36 - F1-F4
|
||||
|
||||
### Agent Dispatch Summary
|
||||
|
||||
- **Wave 1**: Tasks 1-3, 35, 36 → `quick` (deps + TDD utilities + CI draft + policy)
|
||||
- **Wave 2**: Tasks 4-5, 34, 37 → `visual-engineering` + `deep` + `quick`
|
||||
- **Waves 3-6**: Tasks 6-33 → `unspecified-high` × 28 (generator refactoring + dice)
|
||||
- **FINAL**: F1-F4 → `oracle`, `unspecified-high`, `deep`
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. Add missing dependencies to package.json
|
||||
|
||||
**What to do**:
|
||||
- Add `zod` to `dependencies` (imported but missing)
|
||||
- Add `typescript` to `devDependencies` (Astro projects need it)
|
||||
- Run `npm install` to update lockfile
|
||||
|
||||
**Must NOT do**:
|
||||
- Change existing dependency versions
|
||||
- Remove existing packages
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 2, 3)
|
||||
- **Blocked By**: None
|
||||
- **Blocks**: Tasks 2, 3 (npm install should complete)
|
||||
|
||||
**References**:
|
||||
- `package.json` — current dependency manifest
|
||||
- `src/lib/generator-schema.ts:1` — `import { z } from "zod"`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `zod` listed in `dependencies`
|
||||
- [ ] `typescript` listed in `devDependencies`
|
||||
- [ ] `npm install` succeeds
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Verify deps installed
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. grep '"zod"' package.json
|
||||
2. grep '"typescript"' package.json
|
||||
3. npm install
|
||||
Expected Result: Both found, npm install exits 0
|
||||
Evidence: .sisyphus/evidence/task-1-deps.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `fix(deps): add zod and typescript to package.json`
|
||||
- Files: `package.json`, `package-lock.json`
|
||||
|
||||
- [x] 2. Create crypto random utility with TDD
|
||||
|
||||
**What to do**:
|
||||
- Create `src/lib/client/random.ts` with:
|
||||
- `randomInt(min: number, max: number): number`
|
||||
- `randomFloat(): number`
|
||||
- `randomBytes(length: number): Uint8Array`
|
||||
- `shuffleArray<T>(array: T[]): T[]`
|
||||
- Create `src/lib/client/random.test.ts` with TDD tests
|
||||
|
||||
**Must NOT do**:
|
||||
- Use `Math.random()` anywhere
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 1, 3)
|
||||
- **Blocked By**: None
|
||||
- **Blocks**: Tasks 4, 5, 6+ (all generators)
|
||||
|
||||
**References**:
|
||||
- `src/components/generators/NumberGenerator.astro` — uses Math.random
|
||||
- `src/components/generators/PasswordGenerator.astro` — uses Math.random
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `src/lib/client/random.ts` exists with all 4 functions
|
||||
- [ ] `src/lib/client/random.test.ts` passes
|
||||
- [ ] No `Math.random()` in module
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: TDD cycle passes
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. npm run test -- src/lib/client/random.test.ts
|
||||
2. grep "Math.random" src/lib/client/random.ts || true
|
||||
Expected Result: Tests pass, no Math.random
|
||||
Evidence: .sisyphus/evidence/task-2-random.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(lib): add cryptographically secure random utility with tests`
|
||||
- Files: `src/lib/client/random.ts`, `src/lib/client/random.test.ts`
|
||||
- Pre-commit: `npm run test -- src/lib/client/random.test.ts`
|
||||
|
||||
- [x] 3. Create client i18n helper with TDD
|
||||
|
||||
**What to do**:
|
||||
- Create `src/lib/client/i18n.ts` with:
|
||||
- `getClientLang(): 'en' | 'ru'`
|
||||
- `getClientT(): T`
|
||||
- Create `src/lib/client/i18n.test.ts`
|
||||
|
||||
**Must NOT do**:
|
||||
- Change `translations.ts` structure
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 1, 2)
|
||||
- **Blocked By**: None
|
||||
- **Blocks**: Tasks 4, 5, 6+
|
||||
|
||||
**References**:
|
||||
- `src/i18n/translations.ts` — translation objects
|
||||
- `src/components/generators/NumberGenerator.astro` — shows current `document.documentElement.lang` pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `src/lib/client/i18n.ts` exists
|
||||
- [ ] `src/lib/client/i18n.test.ts` passes
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: i18n helper tests pass
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. npm run test -- src/lib/client/i18n.test.ts
|
||||
Expected Result: Tests pass
|
||||
Evidence: .sisyphus/evidence/task-3-i18n.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(lib): add client i18n helper with tests`
|
||||
- Files: `src/lib/client/i18n.ts`, `src/lib/client/i18n.test.ts`
|
||||
|
||||
- [ ] 4. Create ResultBox.astro shared component
|
||||
|
||||
**What to do**:
|
||||
- Create `src/components/ResultBox.astro` with:
|
||||
- `result: string` prop
|
||||
- Optional `label?: string` prop
|
||||
- Copy button using existing `CopyFeedback`
|
||||
- i18n via `useT(Astro.currentLocale)` in frontmatter
|
||||
|
||||
**Must NOT do**:
|
||||
- Reimplement clipboard logic
|
||||
- Hardcode English strings
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 2, 3, 5)
|
||||
- **Blocked By**: Tasks 2, 3
|
||||
- **Blocks**: Tasks 6+
|
||||
|
||||
**References**:
|
||||
- `src/lib/client/clipboard.ts` — `CopyFeedback` class
|
||||
- `src/components/generators/NumberGenerator.astro` — result + copy DOM pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `src/components/ResultBox.astro` exists
|
||||
- [ ] Uses `CopyFeedback`
|
||||
- [ ] `npm run build` succeeds
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: ResultBox renders and copies
|
||||
Tool: Playwright
|
||||
Preconditions: Dev server running
|
||||
Steps:
|
||||
1. Create test page with ResultBox result="test123"
|
||||
2. Navigate to page
|
||||
3. Click copy button
|
||||
4. Assert "Copied" appears
|
||||
Expected Result: Copy works, i18n labels correct
|
||||
Evidence: .sisyphus/evidence/task-4-resultbox.png
|
||||
```
|
||||
|
||||
**Commit**: YES (groups with Task 5)
|
||||
|
||||
- [ ] 5. Create CopyButton.astro shared component
|
||||
|
||||
**What to do**:
|
||||
- Create `src/components/CopyButton.astro` with:
|
||||
- `text: string` prop
|
||||
- `label?: string` prop
|
||||
- Uses `CopyFeedback`
|
||||
- i18n via `useT(Astro.currentLocale)`
|
||||
|
||||
**Must NOT do**:
|
||||
- Include result display logic (that's ResultBox)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 2, 3, 4)
|
||||
- **Blocked By**: Tasks 2, 3
|
||||
- **Blocks**: Tasks 6+
|
||||
|
||||
**References**:
|
||||
- `src/lib/client/clipboard.ts`
|
||||
- `src/components/generators/PasswordGenerator.astro`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `src/components/CopyButton.astro` exists
|
||||
- [ ] Uses `CopyFeedback`
|
||||
- [ ] `npm run build` succeeds
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: CopyButton works standalone
|
||||
Tool: Playwright
|
||||
Steps:
|
||||
1. Create test page with CopyButton text="hello"
|
||||
2. Click button
|
||||
3. Assert "Copied" appears
|
||||
Expected Result: Button copies, shows feedback
|
||||
Evidence: .sisyphus/evidence/task-5-copybutton.png
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(components): add ResultBox and CopyButton shared components`
|
||||
- Files: `src/components/ResultBox.astro`, `src/components/CopyButton.astro`
|
||||
|
||||
- [ ] 6. Refactor CardGenerator.astro
|
||||
|
||||
**What to do**:
|
||||
- Replace `Math.random()` with `random.ts` functions
|
||||
- Replace `document.documentElement.lang === "ru"` with `getClientLang()` / `getClientT()`
|
||||
- Use `<CopyButton />` or `<ResultBox />` where applicable
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `unspecified-high`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 7-12, 13-19, 20-26, 27-32)
|
||||
- **Blocked By**: Tasks 4, 5
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] No `Math.random()` in file
|
||||
- [ ] Uses `random.ts` and `i18n.ts`
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: No Math.random in CardGenerator
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. grep "Math.random" src/components/generators/CardGenerator.astro || true
|
||||
Expected Result: Empty output
|
||||
Evidence: .sisyphus/evidence/task-6-card.txt
|
||||
```
|
||||
|
||||
**Commit**: NO (groups with batch)
|
||||
|
||||
- [ ] 7. Refactor CoinGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6, 8-12)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 8. Refactor ColorGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6-7, 9-12)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 9. Refactor CountryGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6-8, 10-12)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 10. Refactor DateGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6-9, 11-12)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 11. Refactor EmojiGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6-10, 12)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 12. Refactor FontPairGenerator.astro
|
||||
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 6-11)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**QA Scenarios**: grep Math.random → empty
|
||||
**Commit**: YES
|
||||
- Message: `refactor(generators): migrate batch 1 to crypto random (Card, Coin, Color, Country, Date, Emoji, FontPair)`
|
||||
- Files: `src/components/generators/{Card,Coin,Color,Country,Date,Emoji,FontPair}Generator.astro`
|
||||
|
||||
- [ ] 13. Refactor GradientGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 14-19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 14. Refactor HashGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13, 15-19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 15. Refactor LetterGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-14, 16-19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 16. Refactor ListGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-15, 17-19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 17. Refactor LoremGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-16, 18-19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 18. Refactor LotteryGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-17, 19, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 19. Refactor Magic8BallGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-18, 20-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: YES
|
||||
- Message: `refactor(generators): migrate batch 2 to crypto random (Gradient, Hash, Letter, List, Lorem, Lottery, Magic8Ball)`
|
||||
- Files: `src/components/generators/{Gradient,Hash,Letter,List,Lorem,Lottery,Magic8Ball}Generator.astro`
|
||||
|
||||
- [ ] 20. Refactor MealGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 21-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 21. Refactor NamesGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20, 22-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 22. Refactor NumberGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-21, 23-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 23. Refactor PaletteGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-22, 24-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 24. Refactor PasswordGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-23, 25-26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 25. Refactor RpsGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-24, 26, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 26. Refactor ShufflerGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-25, 27-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: YES
|
||||
- Message: `refactor(generators): migrate batch 3 to crypto random (Meal, Names, Number, Palette, Password, Rps, Shuffler)`
|
||||
- Files: `src/components/generators/{Meal,Names,Number,Palette,Password,Rps,Shuffler}Generator.astro`
|
||||
|
||||
- [ ] 27. Refactor TeamsGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 28-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 28. Refactor TimeGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27, 29-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 29. Refactor UuidGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-28, 30-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 30. Refactor WeightedGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-29, 31-32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 31. Refactor WheelSpinner.astro
|
||||
**What to do**: Same pattern as Task 6. Note: file is `WheelSpinner.astro`, not `WheelGenerator.astro`.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-30, 32)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 32. Refactor YesNoGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-31)
|
||||
**Blocked By**: Tasks 4, 5
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: YES
|
||||
- Message: `refactor(generators): migrate batch 4 to crypto random (Teams, Time, Uuid, Weighted, WheelSpinner, YesNo)`
|
||||
- Files: `src/components/generators/{Teams,Time,Uuid,Weighted,YesNo}Generator.astro`, `src/components/generators/WheelSpinner.astro`
|
||||
|
||||
- [ ] 33. Refactor DiceGenerator to import dice-engine.ts
|
||||
|
||||
**What to do**:
|
||||
- Replace inline dice engine code in `DiceGenerator.astro:274-958` with imports from `src/lib/dice-engine.ts`
|
||||
- Keep same UI and user-facing behavior
|
||||
- Use `getClientLang()` / `getClientT()` for i18n
|
||||
- Use `<ResultBox />` / `<CopyButton />` for result display
|
||||
|
||||
**Must NOT do**:
|
||||
- Modify `src/lib/dice-engine.ts` in this task (Task 34 does that)
|
||||
- Change dice notation syntax or features
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `deep`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 6-32)
|
||||
- **Blocked By**: Tasks 4, 5, 34 (needs dice-engine.ts with crypto)
|
||||
|
||||
**References**:
|
||||
- `src/components/generators/DiceGenerator.astro` — inline dice engine (lines 274-958)
|
||||
- `src/lib/dice-engine.ts` — canonical dice engine
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `DiceGenerator.astro` imports from `dice-engine.ts`
|
||||
- [ ] File significantly shorter (<300 lines)
|
||||
- [ ] `npm run build` succeeds
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Dice engine imported
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. grep "import.*dice-engine" src/components/generators/DiceGenerator.astro
|
||||
2. wc -l src/components/generators/DiceGenerator.astro
|
||||
Expected Result: Import found, file <300 lines
|
||||
Evidence: .sisyphus/evidence/task-33-dice-import.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `refactor(dice): deduplicate dice engine by importing from lib`
|
||||
- Files: `src/components/generators/DiceGenerator.astro`
|
||||
|
||||
- [ ] 34. Update dice-engine.ts to use crypto random
|
||||
|
||||
**What to do**:
|
||||
- Replace all `Math.random()` in `src/lib/dice-engine.ts` with `random.ts` functions
|
||||
- Update `dice-engine.test.ts` to mock `random.ts` instead of `Math.random`
|
||||
- Ensure all tests pass
|
||||
|
||||
**Must NOT do**:
|
||||
- Change dice notation syntax or parsing
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `deep`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 4, 5, 37)
|
||||
- **Blocked By**: Task 2
|
||||
- **Blocks**: Task 33
|
||||
|
||||
**References**:
|
||||
- `src/lib/dice-engine.ts`
|
||||
- `src/lib/dice-engine.test.ts`
|
||||
- `src/lib/client/random.ts`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] No `Math.random()` in `dice-engine.ts`
|
||||
- [ ] Tests pass
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Dice engine uses crypto
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. grep "Math.random" src/lib/dice-engine.ts || true
|
||||
2. npm run test -- src/lib/dice-engine.test.ts
|
||||
Expected Result: No Math.random, tests pass
|
||||
Evidence: .sisyphus/evidence/task-34-dice-crypto.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `refactor(dice): migrate dice engine to cryptographically secure random`
|
||||
- Files: `src/lib/dice-engine.ts`, `src/lib/dice-engine.test.ts`
|
||||
- Pre-commit: `npm run test -- src/lib/dice-engine.test.ts`
|
||||
|
||||
- [x] 35. Update Privacy Policy translations
|
||||
|
||||
**What to do**:
|
||||
- Update EN and RU `aboutHowItWorks` text in `src/i18n/translations.ts` to accurately describe random generation
|
||||
- Current RU text (line ~172) claims "Web Crypto API" but must reflect actual usage
|
||||
- Also update EN equivalent (line ~78)
|
||||
|
||||
**Must NOT do**:
|
||||
- Change other Privacy Policy sections
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `writing`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 1, 2, 3, 36)
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `src/i18n/translations.ts` — EN aboutHowItWorks (~line 78), RU aboutHowItWorks (~line 172)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] EN aboutHowItWorks accurate
|
||||
- [ ] RU aboutHowItWorks accurate and consistent
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Privacy text updated
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. Read src/i18n/translations.ts lines 72-82 and 166-176
|
||||
Expected Result: Text describes crypto.getRandomValues usage
|
||||
Evidence: .sisyphus/evidence/task-35-privacy.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `fix(i18n): update about page for crypto accuracy`
|
||||
- Files: `src/i18n/translations.ts`
|
||||
|
||||
- [x] 36. Fix deploy.yml quality gates
|
||||
|
||||
**What to do**:
|
||||
- Add `npm run lint` before `npm run build`
|
||||
- Add `npm run test` before `npm run build`
|
||||
- Remove `-o StrictHostKeyChecking=no` from rsync SSH options
|
||||
|
||||
**Must NOT do**:
|
||||
- Change deploy target or SSH keys
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 1, 2, 3, 35)
|
||||
- **Blocked By**: None
|
||||
- **Blocks**: Task 37
|
||||
|
||||
**References**:
|
||||
- `.github/workflows/deploy.yml`
|
||||
- `package.json` — test and lint scripts
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `npm run lint` runs before build
|
||||
- [ ] `npm run test` runs before build
|
||||
- [ ] No `StrictHostKeyChecking=no`
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: CI updated
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. grep "npm run lint" .github/workflows/deploy.yml
|
||||
2. grep "npm run test" .github/workflows/deploy.yml
|
||||
3. grep "StrictHostKeyChecking" .github/workflows/deploy.yml || true
|
||||
Expected Result: lint and test found, StrictHostKeyChecking absent
|
||||
Evidence: .sisyphus/evidence/task-36-ci.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `ci(deploy): add test and lint gates, fix SSH security`
|
||||
- Files: `.github/workflows/deploy.yml`
|
||||
|
||||
- [ ] 37. Verify CI blocks deploy on failure
|
||||
|
||||
**What to do**:
|
||||
- Verify updated `deploy.yml` workflow syntax
|
||||
- Confirm deploy does not run if test/lint fails
|
||||
|
||||
**Must NOT do**:
|
||||
- Trigger actual deploy
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 4, 5, 34)
|
||||
- **Blocked By**: Task 36
|
||||
|
||||
**References**:
|
||||
- `.github/workflows/deploy.yml`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Workflow syntax valid
|
||||
- [ ] Build step after test/lint
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: CI dependencies verified
|
||||
Tool: Bash
|
||||
Steps:
|
||||
1. grep -A5 "npm run test" .github/workflows/deploy.yml
|
||||
Expected Result: Build comes after test
|
||||
Evidence: .sisyphus/evidence/task-37-ci-verify.txt
|
||||
```
|
||||
|
||||
**Commit**: NO (groups with Task 36)
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
> 4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing.
|
||||
|
||||
- [ ] F1. **Plan Compliance Audit** — `oracle`
|
||||
Read the plan end-to-end. For each "Must Have": verify implementation exists. For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist. Compare deliverables against plan.
|
||||
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [ ] F2. **Code Quality Review** — `unspecified-high`
|
||||
Run `tsc --noEmit` + `eslint .` + `vitest run`. Review all changed files for: `as any`/`@ts-ignore`, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction, generic names.
|
||||
Output: `Build [PASS/FAIL] | Lint [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT`
|
||||
|
||||
- [ ] F3. **Real Manual QA** — `unspecified-high`
|
||||
Start from clean state. Execute EVERY QA scenario from EVERY task. Test cross-task integration. Test edge cases: empty state, invalid input. Verify no Math.random() remains (`grep -r "Math.random" src/components/generators/`). Save to `.sisyphus/evidence/final-qa/`.
|
||||
Output: `Scenarios [N/N pass] | Integration [N/N] | Math.random [CLEAN/N found] | VERDICT`
|
||||
|
||||
- [ ] F4. **Scope Fidelity Check** — `deep`
|
||||
For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built, nothing beyond spec was built. Check "Must NOT do" compliance. Detect cross-task contamination. Flag unaccounted changes.
|
||||
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **Wave 1**: `fix(deps): add zod and typescript to package.json` + `feat(lib): add crypto random utility with tests` + `feat(lib): add client i18n helper with tests` + `fix(i18n): update privacy policy for crypto accuracy` + `ci(deploy): add test and lint gates to deploy workflow`
|
||||
- **Wave 2**: `feat(components): add ResultBox and CopyButton shared components` + `refactor(dice): migrate dice engine to cryptographically secure random`
|
||||
- **Waves 3-6**: `refactor(generators): migrate batch N to crypto random and shared components`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
npm run test # Expected: all pass (utilities + existing dice tests)
|
||||
npm run lint # Expected: no errors
|
||||
npm run build # Expected: static build succeeds
|
||||
grep -r "Math.random" src/components/generators/ # Expected: no matches
|
||||
grep -r "dice-engine" src/components/generators/DiceGenerator.astro # Expected: import statement
|
||||
grep "zod" package.json # Expected: found in dependencies
|
||||
grep "test" .github/workflows/deploy.yml # Expected: found
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [ ] All "Must Have" present
|
||||
- [ ] All "Must NOT Have" absent
|
||||
- [ ] All tests pass
|
||||
- [ ] No `Math.random()` in generators
|
||||
- [ ] Dice engine imported, not inlined
|
||||
- [ ] CI runs tests and lint before deploy
|
||||
- [ ] User explicitly approved (no auto-push to main)
|
||||
@@ -0,0 +1,262 @@
|
||||
# init-deep: Generate Hierarchical AGENTS.md
|
||||
|
||||
## TL;DR
|
||||
|
||||
> Generate hierarchical AGENTS.md knowledge base for Randify.pro codebase.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - `./AGENTS.md` (root knowledge base)
|
||||
> - `src/components/generators/AGENTS.md` (generators domain)
|
||||
>
|
||||
> **Estimated Effort**: Short
|
||||
> **Parallel Execution**: YES - 2 files in parallel
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Randify.pro is a bilingual (EN/RU) static site on Astro 4 + Tailwind CSS 4 with 30 random-value generators. The codebase has specific architectural constraints documented in `CLAUDE.md` and discovered through comprehensive audit.
|
||||
|
||||
### Research Findings (from explore agents)
|
||||
|
||||
**Project Structure:**
|
||||
- 270 total files, depth 4, 5 large files (>500 lines)
|
||||
- 30 generators (not 10 as stated in CLAUDE.md — documentation drift)
|
||||
- 62 page files (EN + RU duplicates)
|
||||
- Content collection defined but bypassed (import.meta.glob used instead)
|
||||
|
||||
**Entry Points:**
|
||||
- `astro.config.mjs` — Astro 4, i18n routing, Tailwind v4 Vite plugin, sitemap
|
||||
- `src/pages/index.astro` — homepage
|
||||
- `src/layouts/BaseLayout.astro` — root layout with theme, analytics, SW registration
|
||||
- `src/layouts/GeneratorLayout.astro` — generator wrapper (breadcrumb, ad, SEO, FAQ)
|
||||
|
||||
**Key Conventions:**
|
||||
- ESM only, `@/*` → `src/*`
|
||||
- i18n: `Astro.currentLocale` / `document.documentElement.lang` + `useT(lang)`
|
||||
- Tailwind v4 theme in `BaseLayout.astro` global style (no tailwind.config)
|
||||
- Dark theme: `bg-zinc-950`, accent `#534AB7`
|
||||
- Generator JSON validated by strict Zod schema (`.strict()` — unknown fields fail build)
|
||||
- Tests co-located: `src/**/*.test.ts` (only 1 test exists: `dice-engine.test.ts`)
|
||||
|
||||
**Anti-Patterns from CLAUDE.md:**
|
||||
1. Never use shared dynamic `[slug].astro` for Russian pages (bundling issue)
|
||||
2. Every generator must be created in EN + RU simultaneously
|
||||
3. All UI strings in `translations.ts` — no hardcoding
|
||||
4. No client-side router
|
||||
5. No unknown fields in generator JSON
|
||||
|
||||
**Build/CI:**
|
||||
- GitHub Actions + rsync to reg.ru
|
||||
- No tests/lint in CI despite scripts existing
|
||||
- Dockerfile for local preview only (not used in CI)
|
||||
- nginx:alpine, no HEALTHCHECK
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Generate concise, telegraphic AGENTS.md files that capture project-specific knowledge without generic advice.
|
||||
|
||||
### Concrete Deliverables
|
||||
- `./AGENTS.md` (root, 50-150 lines)
|
||||
- `src/components/generators/AGENTS.md` (subdirectory, 30-80 lines)
|
||||
|
||||
### Must Have
|
||||
- Project overview with stack
|
||||
- Directory structure tree
|
||||
- "WHERE TO LOOK" table mapping tasks to locations
|
||||
- Conventions (deviations from standard only)
|
||||
- Anti-patterns specific to this project
|
||||
- Commands (dev/test/build)
|
||||
- Notes with known issues
|
||||
|
||||
### Must NOT Have
|
||||
- Generic advice applicable to all projects
|
||||
- Redundancy between parent and child AGENTS.md
|
||||
- Boilerplate that doesn't help navigate the codebase
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### QA Scenarios
|
||||
|
||||
**Scenario: Root AGENTS.md completeness**
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. Read `./AGENTS.md`
|
||||
2. Verify it contains: OVERVIEW, STRUCTURE, WHERE TO LOOK, CONVENTIONS, ANTI-PATTERNS, COMMANDS, NOTES
|
||||
3. Verify length is 50-150 lines
|
||||
4. Verify no generic advice (e.g., "use version control", "write tests")
|
||||
Expected Result: File exists with all required sections, telegraphic style, project-specific content only
|
||||
Evidence: .sisyphus/evidence/init-deep-root-agents.md
|
||||
|
||||
**Scenario: Generators AGENTS.md completeness**
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. Read `src/components/generators/AGENTS.md`
|
||||
2. Verify it contains: OVERVIEW, STRUCTURE, WHERE TO LOOK, CONVENTIONS, ANTI-PATTERNS, NOTES
|
||||
3. Verify length is 30-80 lines
|
||||
4. Verify no duplication of parent content (e.g., doesn't repeat stack overview)
|
||||
Expected Result: File exists with generator-specific content, no parent redundancy
|
||||
Evidence: .sisyphus/evidence/init-deep-generators-agents.md
|
||||
|
||||
**Scenario: No drift from actual codebase**
|
||||
Tool: Bash (grep)
|
||||
Steps:
|
||||
1. grep -c "generators" ./AGENTS.md → should reference 30 generators
|
||||
2. grep "Math.random" ./AGENTS.md → should note known discrepancy
|
||||
3. grep "dice-engine" src/components/generators/AGENTS.md → should mention import pattern
|
||||
Expected Result: AGENTS.md reflects actual codebase state, not outdated docs
|
||||
Evidence: .sisyphus/evidence/init-deep-drift-check.txt
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Parallel Execution Waves
|
||||
|
||||
```
|
||||
Wave 1 (Start Immediately - 2 tasks in parallel):
|
||||
├── Task 1: Generate root AGENTS.md
|
||||
└── Task 2: Generate generators/AGENTS.md
|
||||
```
|
||||
|
||||
### Agent Dispatch Summary
|
||||
|
||||
- **T1**: `writing` category — Generate root AGENTS.md
|
||||
- **T2**: `writing` category — Generate generators/AGENTS.md
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] 1. Generate root AGENTS.md
|
||||
|
||||
**What to do**:
|
||||
- Create `./AGENTS.md` with all required sections
|
||||
- Include project-specific knowledge from research findings
|
||||
- Maintain telegraphic style (50-150 lines)
|
||||
- Include known issues: 30 generators (not 10), Math.random discrepancy, dice engine duplication, CI doesn't run tests
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `writing`
|
||||
- **Skills**: []
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Task 2)
|
||||
- **Blocks**: Task 3 (review)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] File created: `./AGENTS.md`
|
||||
- [ ] Contains all sections: OVERVIEW, STRUCTURE, WHERE TO LOOK, CONVENTIONS, ANTI-PATTERNS, COMMANDS, NOTES
|
||||
- [ ] Length: 50-150 lines
|
||||
- [ ] No generic advice
|
||||
- [ ] References 30 generators (not outdated 10)
|
||||
- [ ] Notes Privacy Policy / Math.random discrepancy
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Root AGENTS.md structure
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. Read ./AGENTS.md
|
||||
2. Count lines (should be 50-150)
|
||||
3. Verify all required sections present
|
||||
Expected Result: Complete, concise, project-specific
|
||||
Evidence: .sisyphus/evidence/init-deep-root-check.txt
|
||||
```
|
||||
|
||||
- [ ] 2. Generate generators/AGENTS.md
|
||||
|
||||
**What to do**:
|
||||
- Create `src/components/generators/AGENTS.md` with generator-domain knowledge
|
||||
- Focus on: conventions for creating generators, anti-patterns specific to generator components
|
||||
- Maintain telegraphic style (30-80 lines)
|
||||
- Never repeat parent content (no stack overview, no global commands)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `writing`
|
||||
- **Skills**: []
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Task 1)
|
||||
- **Blocks**: Task 3 (review)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] File created: `src/components/generators/AGENTS.md`
|
||||
- [ ] Contains: OVERVIEW, STRUCTURE, WHERE TO LOOK, CONVENTIONS, ANTI-PATTERNS, NOTES
|
||||
- [ ] Length: 30-80 lines
|
||||
- [ ] No duplication of parent AGENTS.md content
|
||||
- [ ] Mentions dice-engine import pattern
|
||||
- [ ] Mentions 4-artifact requirement for new generators
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Generators AGENTS.md structure
|
||||
Tool: Read
|
||||
Steps:
|
||||
1. Read src/components/generators/AGENTS.md
|
||||
2. Count lines (should be 30-80)
|
||||
3. Verify no parent content duplication
|
||||
Expected Result: Domain-specific, concise
|
||||
Evidence: .sisyphus/evidence/init-deep-generators-check.txt
|
||||
```
|
||||
|
||||
- [ ] 3. Review and validate both files
|
||||
|
||||
**What to do**:
|
||||
- Read both AGENTS.md files
|
||||
- Check for redundancy between parent and child
|
||||
- Verify telegraphic style
|
||||
- Verify no generic advice
|
||||
- Trim if necessary
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: []
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO (depends on Tasks 1, 2)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Both files pass review
|
||||
- [ ] No cross-file redundancy
|
||||
- [ ] Both within line limits
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: Cross-file redundancy check
|
||||
Tool: Bash (grep)
|
||||
Steps:
|
||||
1. grep "Astro 4" src/components/generators/AGENTS.md → should be empty (parent covers this)
|
||||
2. grep "npm run" src/components/generators/AGENTS.md → should be empty
|
||||
Expected Result: Child file doesn't repeat parent knowledge
|
||||
Evidence: .sisyphus/evidence/init-deep-redundancy-check.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [ ] F1. **Plan Compliance Audit** — `oracle`
|
||||
Verify both AGENTS.md files exist, contain required sections, and reflect actual codebase state. Check line limits. Verify no generic advice.
|
||||
Output: `Files [2/2] | Sections [PASS/FAIL] | Line Limits [PASS/FAIL] | VERDICT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **1**: `docs: add AGENTS.md knowledge base`
|
||||
- Files: `AGENTS.md`, `src/components/generators/AGENTS.md`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Root AGENTS.md exists (50-150 lines, all sections, no generic advice)
|
||||
- [ ] Generators AGENTS.md exists (30-80 lines, no parent redundancy)
|
||||
- [ ] Both files reflect actual codebase state (30 generators, known issues)
|
||||
- [ ] No drift from discovered conventions and anti-patterns
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_1de5c2095ffeVqZPNyH9lTgYi0",
|
||||
"updatedAt": "2026-05-13T14:03:33.211Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-05-13T14:03:33.211Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_1de6ede59ffexUwt5C0sddEWwR",
|
||||
"updatedAt": "2026-05-13T13:53:55.077Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-05-13T13:53:55.077Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_1dede957cffekNeZByT8uveQfm",
|
||||
"updatedAt": "2026-05-13T11:39:16.151Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-05-13T11:39:16.151Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+3
-2
@@ -11,7 +11,8 @@
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"astro": "^4.16.0",
|
||||
"tailwindcss": "^4.0.0"
|
||||
"tailwindcss": "^4.0.0",
|
||||
"zod": "^3.25.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -23,6 +24,7 @@
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
@@ -7381,7 +7383,6 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
+3
-1
@@ -18,7 +18,8 @@
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"astro": "^4.16.0",
|
||||
"tailwindcss": "^4.0.0"
|
||||
"tailwindcss": "^4.0.0",
|
||||
"zod": "^3.25.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -30,6 +31,7 @@
|
||||
"globals": "^17.6.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export const translations = {
|
||||
aboutHistory:
|
||||
"The idea for Randify came from a simple frustration: every time we needed a quick random number or a coin flip, we ended up on cluttered websites full of ads, pop-ups, and unnecessary steps. We wanted something cleaner — a tool that just works, without distractions.\n\nWe started with a single number generator and gradually expanded the collection based on what people actually use: dice for board games, wheels for raffles, passwords for security, and more. Each tool was built with the same philosophy — minimal interface, instant results, zero friction.",
|
||||
aboutHowItWorks:
|
||||
"Everything on Randify runs entirely in your browser. No data is sent to any server, no personal information is collected, and no account is required. The random values are generated locally using the Web Crypto API, which provides cryptographically secure randomness. This means your passwords, numbers, and choices stay private — we never see them.",
|
||||
"Everything on Randify runs entirely in your browser. No data is sent to any server, no personal information is collected, and no account is required. The random values are generated locally using crypto.getRandomValues from the Web Crypto API, which provides cryptographically secure randomness. This means your passwords, numbers, and choices stay private — we never see them.",
|
||||
aboutContact:
|
||||
"Have a suggestion, found a bug, or just want to say hello? Drop us a line at hello@randify.pro — we read every message.",
|
||||
aboutCta:
|
||||
@@ -169,7 +169,7 @@ export const translations = {
|
||||
aboutHistory:
|
||||
"Идея Randify родилась из простого раздражения: каждый раз, когда нам нужно было быстро сгенерировать случайное число или подбросить монетку, мы попадали на перегруженные рекламой сайты с всплывающими окнами и лишними шагами. Мы хотели создать что-то чище — инструмент, который просто работает, без отвлекающих элементов.\n\nМы начали с одного генератора чисел и постепенно расширяли коллекцию, ориентируясь на реальные потребности: кубики для настольных игр, колесо фортуны для розыгрышей, генератор паролей для безопасности и многое другое. Каждый инструмент создавался по одному принципу — минимальный интерфейс, мгновенный результат, никаких препятствий.",
|
||||
aboutHowItWorks:
|
||||
"Всё на Randify работает полностью в вашем браузере. Данные не отправляются на сервер, личная информация не собирается, регистрация не требуется. Случайные значения генерируются локально с помощью Web Crypto API, который обеспечивает криптографически стойкую случайность. Это означает, что ваши пароли, числа и выбор остаются приватными — мы их не видим.",
|
||||
"Всё на Randify работает полностью в вашем браузере. Данные не отправляются на сервер, личная информация не собирается, регистрация не требуется. Случайные значения генерируются локально с помощью crypto.getRandomValues из Web Crypto API, который обеспечивает криптографически стойкую случайность. Это означает, что ваши пароли, числа и выбор остаются приватными — мы их не видим.",
|
||||
aboutContact:
|
||||
"Есть предложение, нашли ошибку или просто хотите поздороваться? Напишите нам на hello@randify.pro — мы читаем каждое сообщение.",
|
||||
aboutCta:
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { getClientLang, getClientT } from "./i18n";
|
||||
import type { T } from "@/i18n/translations";
|
||||
|
||||
const mockDocument = {
|
||||
documentElement: {
|
||||
lang: "en",
|
||||
},
|
||||
};
|
||||
|
||||
(globalThis as Record<string, unknown>).document = mockDocument;
|
||||
|
||||
describe("getClientLang", () => {
|
||||
beforeEach(() => {
|
||||
mockDocument.documentElement.lang = "en";
|
||||
});
|
||||
|
||||
it('returns "en" when document.documentElement.lang is "en"', () => {
|
||||
mockDocument.documentElement.lang = "en";
|
||||
expect(getClientLang()).toBe("en");
|
||||
});
|
||||
|
||||
it('returns "ru" when document.documentElement.lang is "ru"', () => {
|
||||
mockDocument.documentElement.lang = "ru";
|
||||
expect(getClientLang()).toBe("ru");
|
||||
});
|
||||
|
||||
it('defaults to "en" for unsupported languages', () => {
|
||||
mockDocument.documentElement.lang = "fr";
|
||||
expect(getClientLang()).toBe("en");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getClientT", () => {
|
||||
beforeEach(() => {
|
||||
mockDocument.documentElement.lang = "en";
|
||||
});
|
||||
|
||||
it("returns English translations when lang is 'en'", () => {
|
||||
mockDocument.documentElement.lang = "en";
|
||||
const t: T = getClientT();
|
||||
expect(t.allGenerators).toBe("All generators");
|
||||
});
|
||||
|
||||
it("returns Russian translations when lang is 'ru'", () => {
|
||||
mockDocument.documentElement.lang = "ru";
|
||||
const t: T = getClientT();
|
||||
expect(t.allGenerators).toBe("Все генераторы");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { useT, type T, type Lang } from "@/i18n/translations";
|
||||
|
||||
export function getClientLang(): Lang {
|
||||
const lang = document.documentElement.lang;
|
||||
return lang === "ru" ? "ru" : "en";
|
||||
}
|
||||
|
||||
export function getClientT(): T {
|
||||
return useT(getClientLang());
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { randomInt, randomFloat, randomBytes, shuffleArray } from "./random";
|
||||
|
||||
describe("randomInt", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns an integer within [min, max]", () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const result = randomInt(1, 6);
|
||||
expect(result).toBeGreaterThanOrEqual(1);
|
||||
expect(result).toBeLessThanOrEqual(6);
|
||||
expect(Number.isInteger(result)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("handles min === max", () => {
|
||||
expect(randomInt(5, 5)).toBe(5);
|
||||
});
|
||||
|
||||
it("handles negative ranges", () => {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const result = randomInt(-10, -5);
|
||||
expect(result).toBeGreaterThanOrEqual(-10);
|
||||
expect(result).toBeLessThanOrEqual(-5);
|
||||
}
|
||||
});
|
||||
|
||||
it("handles range crossing zero", () => {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const result = randomInt(-5, 5);
|
||||
expect(result).toBeGreaterThanOrEqual(-5);
|
||||
expect(result).toBeLessThanOrEqual(5);
|
||||
}
|
||||
});
|
||||
|
||||
it("throws when min > max", () => {
|
||||
expect(() => randomInt(5, 1)).toThrow();
|
||||
});
|
||||
|
||||
it("uses rejection sampling for uniform distribution", () => {
|
||||
const spy = vi
|
||||
.spyOn(globalThis.crypto, "getRandomValues")
|
||||
.mockImplementation((buffer) => {
|
||||
const arr = buffer as Uint32Array;
|
||||
arr[0] = 0;
|
||||
return buffer;
|
||||
});
|
||||
|
||||
expect(randomInt(0, 10)).toBe(0);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("randomFloat", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns a value in [0, 1)", () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const result = randomFloat();
|
||||
expect(result).toBeGreaterThanOrEqual(0);
|
||||
expect(result).toBeLessThan(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("maps 0xFFFFFFFF to just under 1", () => {
|
||||
const spy = vi
|
||||
.spyOn(globalThis.crypto, "getRandomValues")
|
||||
.mockImplementation((buffer) => {
|
||||
const arr = buffer as Uint32Array;
|
||||
arr[0] = 0xffffffff;
|
||||
return buffer;
|
||||
});
|
||||
|
||||
const result = randomFloat();
|
||||
expect(result).toBeGreaterThan(0.999);
|
||||
expect(result).toBeLessThan(1);
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("maps 0 to 0", () => {
|
||||
const spy = vi
|
||||
.spyOn(globalThis.crypto, "getRandomValues")
|
||||
.mockImplementation((buffer) => {
|
||||
const arr = buffer as Uint32Array;
|
||||
arr[0] = 0;
|
||||
return buffer;
|
||||
});
|
||||
|
||||
expect(randomFloat()).toBe(0);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("randomBytes", () => {
|
||||
it("returns a Uint8Array of the requested length", () => {
|
||||
const result = randomBytes(16);
|
||||
expect(result).toBeInstanceOf(Uint8Array);
|
||||
expect(result.length).toBe(16);
|
||||
});
|
||||
|
||||
it("returns empty array for length 0", () => {
|
||||
const result = randomBytes(0);
|
||||
expect(result).toBeInstanceOf(Uint8Array);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shuffleArray", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns a new array with the same elements", () => {
|
||||
const original = [1, 2, 3, 4, 5];
|
||||
const shuffled = shuffleArray(original);
|
||||
expect(shuffled).toHaveLength(original.length);
|
||||
expect([...shuffled].sort((a, b) => a - b)).toEqual(original);
|
||||
expect(shuffled).not.toBe(original);
|
||||
});
|
||||
|
||||
it("does not mutate the original array", () => {
|
||||
const original = [1, 2, 3];
|
||||
const copy = [...original];
|
||||
shuffleArray(original);
|
||||
expect(original).toEqual(copy);
|
||||
});
|
||||
|
||||
it("handles empty arrays", () => {
|
||||
const result = shuffleArray([]);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles single-element arrays", () => {
|
||||
const result = shuffleArray([42]);
|
||||
expect(result).toEqual([42]);
|
||||
});
|
||||
|
||||
it("produces deterministic shuffle with mocked randomness", () => {
|
||||
const spy = vi
|
||||
.spyOn(globalThis.crypto, "getRandomValues")
|
||||
.mockImplementation((buffer) => {
|
||||
const arr = buffer as Uint32Array;
|
||||
arr[0] = 0;
|
||||
return buffer;
|
||||
});
|
||||
|
||||
const original = [1, 2, 3];
|
||||
const shuffled = shuffleArray(original);
|
||||
expect(shuffled).toEqual([2, 3, 1]);
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
const MAX_UINT32_PLUS_ONE = 4294967296;
|
||||
|
||||
export function randomInt(min: number, max: number): number {
|
||||
if (min > max) {
|
||||
throw new Error("min must be less than or equal to max");
|
||||
}
|
||||
|
||||
const range = max - min + 1;
|
||||
const limit = MAX_UINT32_PLUS_ONE - (MAX_UINT32_PLUS_ONE % range);
|
||||
const buf = new Uint32Array(1);
|
||||
|
||||
let value: number;
|
||||
do {
|
||||
crypto.getRandomValues(buf);
|
||||
value = buf[0];
|
||||
} while (value >= limit);
|
||||
|
||||
return min + (value % range);
|
||||
}
|
||||
|
||||
export function randomFloat(): number {
|
||||
const buf = new Uint32Array(1);
|
||||
crypto.getRandomValues(buf);
|
||||
return buf[0] / MAX_UINT32_PLUS_ONE;
|
||||
}
|
||||
|
||||
export function randomBytes(length: number): Uint8Array {
|
||||
return crypto.getRandomValues(new Uint8Array(length));
|
||||
}
|
||||
|
||||
export function shuffleArray<T>(array: T[]): T[] {
|
||||
const result = [...array];
|
||||
for (let i = result.length - 1; i > 0; i--) {
|
||||
const j = randomInt(0, i);
|
||||
[result[i], result[j]] = [result[j], result[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(import.meta.dirname, "./src"),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "node",
|
||||
|
||||
Reference in New Issue
Block a user