Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude/skills/add-contest-table-provider/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: add-contest-table-provider
description: Add a new ContestType and ContestTableProvider across 5 layers using TDD. Covers all 3 patterns. Asks targeted questions to gather pattern-specific requirements before touching code.
argument-hint: '<ContestType> <contest_id>'
---

Add a new contest table provider for: $ARGUMENTS

> When in doubt at any step, use AskUserQuestion before proceeding.

0. **Seed check** — grep `prisma/tasks.ts` for the contest_id(s); report count + task_ids; if absent or incomplete, ask the user to add missing rows (reference: `https://kenkoooo.com/atcoder/resources/problems.json`)
1. **Gather requirements** — infer the implementation pattern; confirm per [instructions.md §Requirements](instructions.md)
2. **Implement** — follow [instructions.md](instructions.md) for the confirmed pattern across 5 layers (TDD)
123 changes: 123 additions & 0 deletions .claude/skills/add-contest-table-provider/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Add Contest Table Provider — Implementation Checklist

Reference: `docs/guides/how-to-add-contest-table-provider.md`

---

## Requirements gathering

Step 0 (seed check) is already done. Confirm the following before touching code:

**All patterns:**

- Which pattern? (State your inference from the data, ask to confirm)
- Pattern 1: numeric range filter (e.g. ABC 001–041)
- Pattern 2: single fixed contest_id (e.g. NDPC, TDPC, FPS_24)
- Pattern 3: multiple contest_ids unified in one table (e.g. ABS, ABC-Like)
- Nearest neighbor ContestType for insertion order in `contestTypePriorities`?
- New group or merge into existing? If new: group name / `buttonLabel` / `ariaLabel`?

**Pattern 1 additional:**

- Numeric range: start and end (open-ended if no upper bound)?
- Shared problems with another contest (e.g. ARC–ABC overlap)? Which contest_ids appear in both?
- Round label format (e.g. `ABC 042`)?

**Pattern 3 additional:**

- Show the full contest_id list found in `prisma/tasks.ts` — any missing or to exclude?
- Does `prisma/contest_task_pairs.ts` need updating (shared task_ids across contests)?
- task_table_index format: numeric (`001–`) or alphabetic (`A–`)?
- Section splits needed? If yes: split key and section names?

---

## Layer 1 — Prisma schema

- [ ] Add `XXX // Full Contest Name` to `prisma/schema.prisma` ContestType enum (after nearest neighbor)
- [ ] `pnpm exec prisma generate` — non-interactive env; `migrate dev` requires interactive shell
- [ ] `pnpm check` — expect a type error in `src/lib/types/contest.ts` (confirms client regenerated)

## Layer 2 — TypeScript ContestType constant

- [ ] Add `XXX: 'XXX', // Full Contest Name` to `ContestType` in `src/lib/types/contest.ts` (same position as schema)
- [ ] `pnpm check` — error should be gone

## Layer 3 — Contest utilities (TDD)

### Write tests first

- [ ] Add export to `src/test/lib/utils/test_cases/contest_type.ts` (after nearest neighbor)
- [ ] Add export to `src/test/lib/utils/test_cases/contest_name_labels.ts` (after nearest neighbor)
- [ ] Add three `describe('when contest_id is xxx')` blocks to `src/test/lib/utils/contest.test.ts`:
- under `classify contest`
- under `get contest priority`
- under `get contest name label`
- [ ] `pnpm test:unit src/test/lib/utils/contest.test.ts` — **expect RED**

### Implement

- [ ] Add `classifyContest` branch after nearest neighbor's branch in `src/lib/utils/contest.ts`
- [ ] Insert `[ContestType.XXX, N]` into `contestTypePriorities` after nearest neighbor
- All entries after the insertion point shift by +1
- **Update the JSDoc numeric ranges** — do NOT rename or split the existing four categories
(Educational / Contests for genius / Special contests / External platforms)
- **Search `src/test/lib/utils/task.test.ts` for hardcoded priority-diff expected values**
and decrement by 1 for every ContestType that shifted
- [ ] Add `getContestNameLabel` branch after nearest neighbor's branch
- [ ] `pnpm test:unit src/test/lib/utils/contest.test.ts` — **expect GREEN**

---

## Layer 4 — Provider class (TDD)

### Pattern 2: single source

- [ ] Add entry to `describe.each` array in `dp_providers.test.ts` (or the appropriate `*_providers.test.ts`)
- [ ] Add import of new Provider class
- [ ] `pnpm test:unit <providers.test.ts>` — **expect RED**
- [ ] Implement Provider class in the appropriate `*_providers.ts` after nearest neighbor
- [ ] `pnpm test:unit <providers.test.ts>` — **expect GREEN**

### Pattern 1: range filter

- [ ] Add test cases covering range boundaries and at least one mid-range value
- [ ] If shared problems exist: add a test case with mixed contest_ids to confirm exclusion
- [ ] `pnpm test:unit <providers.test.ts>` — **expect RED**
- [ ] Implement Provider using `parseContestRound()` range check
- [ ] `pnpm test:unit <providers.test.ts>` — **expect GREEN**

### Pattern 3: composite

- [ ] Confirm whether `prisma/contest_task_pairs.ts` needs new entries before writing tests
- [ ] Add test cases for each constituent contest_id, plus a mixed-source test
- [ ] If section splits: add one test per section
- [ ] `pnpm test:unit <providers.test.ts>` — **expect RED**
- [ ] Implement Provider (filter by `classifyContest` equality; add section subclasses if needed)
- [ ] `pnpm test:unit <providers.test.ts>` — **expect GREEN**

---

## Layer 5 — Group registration (TDD)

- [ ] Update `contest_table_provider_groups.test.ts`:
- New group name string, `buttonLabel`, `ariaLabel`
- `getSize()` incremented to reflect the new provider count
- Add `getProvider(ContestType.XXX)` assertion
- Add import of new Provider class
- [ ] `pnpm test:unit contest_table_provider_groups.test.ts` — **expect RED**
- [ ] Update `contest_table_provider_groups.ts`:
- Add import of new Provider class
- Update group name string, `buttonLabel`, `ariaLabel`
- Add `new XXXProvider(ContestType.XXX)` to `addProviders()`
- [ ] `pnpm test:unit src/features/tasks/utils/contest-table/` — **expect GREEN**

---

## Final verification

- [ ] `pnpm test:unit`
- [ ] `pnpm check`
- [ ] `pnpm lint`

Commit Layer 1–3 and Layer 4–5 as separate commits.
13 changes: 7 additions & 6 deletions docs/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ paths:

**本プロジェクトの skills(`.claude/skills/` および superpowers plugin):**

| スキル | 用途 |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `/writing-plans` | 新機能・追加実装の詳細計画を生成(2-5分単位のタスク分解)。superpowers plugin 提供 |
| `/refactor-plan` | Issue 番号またはパスを渡してリファクタリング計画を出力(実装はしない) |
| `/session-close` | セッション終了時のルーティン:テスト確認 → plan.md 更新 → rules 候補提示 → 肥大化チェック → 繰り返し指示検出 |
| `/dep-upgrade` | ライブラリのメジャーバージョンアップ分析:破壊的変更の整理・本プロジェクトへの影響・新機能提案 → plan.md 生成 → アップグレード実行 |
| スキル | 用途 |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/writing-plans` | 新機能・追加実装の詳細計画を生成(2-5分単位のタスク分解)。superpowers plugin 提供 |
| `/add-contest-table-provider` | 新しい ContestType と ContestTableProvider を TDD で 5 層実装(Prisma → 型 → ユーティリティ → Provider → グループ登録)。3 パターン対応。実装前に要件を確認する |
| `/refactor-plan` | Issue 番号またはパスを渡してリファクタリング計画を出力(実装はしない) |
| `/session-close` | セッション終了時のルーティン:テスト確認 → plan.md 更新 → rules 候補提示 → 肥大化チェック → 繰り返し指示検出 |
| `/dep-upgrade` | ライブラリのメジャーバージョンアップ分析:破壊的変更の整理・本プロジェクトへの影響・新機能提案 → plan.md 生成 → アップグレード実行 |

**`/dep-upgrade` の使い方:**

Expand Down
34 changes: 31 additions & 3 deletions docs/guides/how-to-add-contest-table-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,14 @@ class TessokuBookSectionProvider extends TessokuBookProvider {
| -------------- | ------------- | ---------- | ------------ |
| EDPC | `'dp'` | 26問 | A~Z |
| TDPC | `'tdpc'` | 26問 | A~Z |
| NDPC | `'ndpc'` | 20問 | A~T |
| FPS_24 | `'fps-24'` | 24問 | A~X |
| ACL_PRACTICE | `'practice2'` | 12問 | A~L |
| ACL_BEGINNER\* | `'abl'` | 6問 | A~F |
| ACL_CONTEST1\* | `'acl1'` | 6問 | A~F |

\*注: ACL_PRACTICE、ACL_BEGINNER、ACL_CONTEST1 は `Acl` グループの下で 3 つのコンテストが統一管理されています。
\*\*注: EDPC・TDPC・NDPC・FPS 24 は `dps` グループ下で 4 つのコンテストが統一管理されています。

### 複合ソース型

Expand Down Expand Up @@ -317,7 +319,7 @@ describe('MyNewProvider', () => {
test('filters tasks correctly', () => {
const provider = new MyNewProvider(ContestType.MY_NEW);
const filtered = provider.filter(taskResultsForMyNew);
expect(filtered.every((t) => t.contest_id === 'my-contest')).toBe(true);
expect(filtered.every((task) => task.contest_id === 'my-contest')).toBe(true);
});

test('returns correct metadata', () => {
Expand Down Expand Up @@ -426,7 +428,7 @@ export const taskResultsForNewProvider: TaskResults = [

---

## よくあるミス Top 4
## よくあるミス Top 5

### 1. **getDisplayConfig() での属性漏れ**

Expand Down Expand Up @@ -502,6 +504,31 @@ describe('CustomProvider with unique config', () => {

---

### 5. **contestTypePriorities の JSDoc カテゴリ名を変更してしまう**

**問題**: 新しい ContestType を挿入して数値範囲が変わったとき、既存の4カテゴリ名
(`Educational` / `Contests for genius` / `Special contests` / `External platforms`)を
意図せず改名・分割・合体してしまい、歴史的経緯や分類上の意味が失われる。

**解決策**: **カテゴリ名は絶対に変更しない**。変えてよいのは括弧内の数値範囲だけ。

```typescript
// Before: [ContestType.TDPC, 5] ... [ContestType.PAST, 6]
// After inserting NDPC at 6:
// [ContestType.NDPC, 6], [ContestType.PAST, 7], ...

// ✅ 数値範囲だけ更新
// Educational contests (0–11, 17)
// Contests for genius (12–16)
// Special contests (18–20)
// External platforms (21–23)

// ❌ カテゴリを改名・分割・合体しない
// Educational / DP contests (0–6) ← NG
```

---

## 実装完了後

### ドキュメント更新チェックリスト
Expand Down Expand Up @@ -532,6 +559,7 @@ describe('CustomProvider with unique config', () => {
- [#2797](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2797) - FPS24Provider
- [#2920](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/2920)、[#3120](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3120) - ACLPracticeProvider、ACLBeginnerProvider、ACLProvider
- [#3152](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/3152) - JOISemiFinalRoundProvider(本選 → セミファイナルステージ への対応)
- NDPC実装 - NDPCProvider(パターン2: 単一ソース型、prisma/tasks.ts に 20 問存在)

### 実装ファイル

Expand All @@ -541,4 +569,4 @@ describe('CustomProvider with unique config', () => {

---

**最終更新**: 2026-02-22
**最終更新**: 2026-05-10
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- @updatedAt is managed by Prisma ORM, not the DB layer. The original
-- split_atcoder_account migration incorrectly added DEFAULT CURRENT_TIMESTAMP
-- to updatedAt. This migration aligns the migration history with the actual DB state.
ALTER TABLE "atcoder_account" ALTER COLUMN "updatedAt" DROP DEFAULT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ContestType" ADD VALUE 'NDPC';
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ enum ContestType {
PAST // Practical Algorithm Skill Test (アルゴリズム実技検定)
EDPC // Educational DP Contest / DP まとめコンテスト
TDPC // Typical DP Contest
NDPC // Next DP Contest
JOI // Japanese Olympiad in Informatics
TYPICAL90 // 競プロ典型 90 問
TESSOKU_BOOK // 競技プログラミングの鉄則
Expand Down
140 changes: 140 additions & 0 deletions prisma/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6346,6 +6346,146 @@ export const tasks = [
title: 'A. コンテスト',
grade: 'Q2',
},
{
id: 'ndpc2026_t',
contest_id: 'ndpc',
problem_index: 'T',
name: 'Independent Set',
title: 'T. Independent Set',
},
{
id: 'ndpc2026_s',
contest_id: 'ndpc',
problem_index: 'S',
name: 'Two doors',
title: 'S. Two doors',
},
{
id: 'ndpc2026_r',
contest_id: 'ndpc',
problem_index: 'R',
name: 'Triples',
title: 'R. Triples',
},
{
id: 'ndpc2026_q',
contest_id: 'ndpc',
problem_index: 'Q',
name: 'Union of Intervals',
title: 'Q. Union of Intervals',
},
{
id: 'ndpc2026_p',
contest_id: 'ndpc',
problem_index: 'P',
name: 'LIS',
title: 'P. LIS',
},
{
id: 'ndpc2026_o',
contest_id: 'ndpc',
problem_index: 'O',
name: 'Game',
title: 'O. Game',
},
{
id: 'ndpc2026_n',
contest_id: 'ndpc',
problem_index: 'N',
name: 'Knapsack',
title: 'N. Knapsack',
},
{
id: 'ndpc2026_m',
contest_id: 'ndpc',
problem_index: 'M',
name: 'Numeral',
title: 'M. Numeral',
},
{
id: 'ndpc2026_l',
contest_id: 'ndpc',
problem_index: 'L',
name: 'LCM',
title: 'L. LCM',
},
{
id: 'ndpc2026_k',
contest_id: 'ndpc',
problem_index: 'K',
name: 'Addition and Subtraction',
title: 'K. Addition and Subtraction',
},
{
id: 'ndpc2026_j',
contest_id: 'ndpc',
problem_index: 'J',
name: 'Number and Total',
title: 'J. Number and Total',
},
{
id: 'ndpc2026_i',
contest_id: 'ndpc',
problem_index: 'I',
name: 'Update Positions',
title: 'I. Update Positions',
},
{
id: 'ndpc2026_h',
contest_id: 'ndpc',
problem_index: 'H',
name: 'Coin',
title: 'H. Coin',
},
{
id: 'ndpc2026_g',
contest_id: 'ndpc',
problem_index: 'G',
name: 'Mouth',
title: 'G. Mouth',
},
{
id: 'ndpc2026_f',
contest_id: 'ndpc',
problem_index: 'F',
name: 'Set',
title: 'F. Set',
},
{
id: 'ndpc2026_e',
contest_id: 'ndpc',
problem_index: 'E',
name: 'Summer Vacation',
title: 'E. Summer Vacation',
},
{
id: 'ndpc2026_d',
contest_id: 'ndpc',
problem_index: 'D',
name: 'Banknote',
title: 'D. Banknote',
},
{
id: 'ndpc2026_c',
contest_id: 'ndpc',
problem_index: 'C',
name: 'String',
title: 'C. String',
},
{
id: 'ndpc2026_b',
contest_id: 'ndpc',
problem_index: 'B',
name: 'DAG',
title: 'B. DAG',
},
{
id: 'ndpc2026_a',
contest_id: 'ndpc',
problem_index: 'A',
name: 'Polyomino',
title: 'A. Polyomino',
},
{
id: 'math_and_algorithm_bn',
contest_id: 'math-and-algorithm',
Expand Down
Loading
Loading