Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51511e1
chore: add .gitignore (worktrees, Python artifacts)
river0525 Jul 21, 2026
8645e22
docs: add implementation plan for contest discussion automation
river0525 Jul 21, 2026
46e4ecc
feat: initialize uv project for contest-discussions automation
river0525 Jul 21, 2026
6ec76fb
feat: add contest_discussions package skeleton
river0525 Jul 21, 2026
95b6fc9
feat: add fixed repository/category IDs for NoviSteps-Writeups
river0525 Jul 21, 2026
8a56595
ci: add smoke test workflow for GITHUB_TOKEN discussions:write
river0525 Jul 21, 2026
f5c095a
fix: translate constants.py comment to English for consistency
river0525 Jul 21, 2026
62a255c
test: add AtCoder tasks page HTML fixture
river0525 Jul 21, 2026
87c6b38
feat: fetch task list from AtCoder tasks page
river0525 Jul 21, 2026
1e31a95
fix: harden fetch_tasks (byte-decode HTML, cross-check contest_id, do…
river0525 Jul 21, 2026
1f2a819
test: add AtCoder Problems contests.json fixture
river0525 Jul 21, 2026
8b1fd52
feat: find recently finished ABC contest ids
river0525 Jul 21, 2026
6f6a33a
fix: guard limit<=0 edge case, clarify docstrings for find_recently_f…
river0525 Jul 21, 2026
fb0672d
test: add failing tests for github_client discussion query/create
river0525 Jul 21, 2026
5991aef
feat: query existing discussion titles and create new discussions via…
river0525 Jul 21, 2026
446c28b
test: add failing tests for main orchestration/CLI
river0525 Jul 21, 2026
b7b79ff
feat: orchestrate contest discussion creation and add CLI entrypoint
river0525 Jul 21, 2026
9318580
fix: log tracebacks on failure, test CLI entrypoint and same-run dedup
river0525 Jul 21, 2026
a03758d
feat(ci): add production workflow to post contest discussions
river0525 Jul 21, 2026
50d20bd
chore(ci): remove throwaway smoke-test workflow
river0525 Jul 21, 2026
34b12ba
fix: add job timeout/concurrency guard, fail run on systemic fetch fa…
river0525 Jul 21, 2026
0903188
fix: address Copilot review findings
river0525 Jul 29, 2026
95615fc
docs: define PR 30 review follow-up
river0525 Aug 3, 2026
6fb7704
fix: limit automatic discussion duplicate checks
river0525 Aug 3, 2026
f6c1c8a
fix: address human review feedback
river0525 Aug 3, 2026
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
48 changes: 48 additions & 0 deletions .github/workflows/post_discussions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Post contest discussions

on:
schedule:
# GitHub Actions cron is UTC: 08:50 JST every Sunday.
- cron: '50 23 * * 6'
workflow_dispatch:
inputs:
contest_id:
description: 'Contest ID to backfill (e.g. abc467). Leave empty for automatic detection.'
required: false

permissions:
contents: read
discussions: write

concurrency:
group: post-discussions
cancel-in-progress: false

jobs:
post-discussions:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0

- name: Install dependencies
run: uv sync

- name: Post discussions
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTEST_ID: ${{ github.event.inputs.contest_id }}
run: |
if [ -n "$CONTEST_ID" ]; then
uv run python -m contest_discussions.main --contest-id "$CONTEST_ID"
else
uv run python -m contest_discussions.main
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.worktrees/
.venv/
__pycache__/
*.pyc
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# NoviSteps-Writeups
競技プログラミングの問題の感想や気付きを投稿・共有できる場所です。試験的に AtCoder Beginner Contest を対象としています。

Discussion 自動投稿の運用方針は [docs/contest-discussion-automation.md](docs/contest-discussion-automation.md) を参照してください。
37 changes: 37 additions & 0 deletions docs/contest-discussion-automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# コンテスト Discussion 自動投稿

## 目的

毎週の AtCoder Beginner Contest (ABC) 終了後、問題ごとの GitHub Discussion
を作成する。GitHub Actions の標準 `GITHUB_TOKEN` に `discussions: write`
権限を与え、Writeups リポジトリ内だけで実行する。

## スケジュール

GitHub Actions の cron は UTC で評価される。自動実行は `50 23 * * 6`
(日曜日 08:50 JST)で、コンテスト終了直後ではなく AtCoder Problems の
コンテスト情報が反映されるまでの余裕を持たせている。

## データ取得

- 終了済み ABC の特定には軽量な Problems API の `contests.json` を使う。
- 問題一覧は AtCoder のコンテスト別 tasks ページから直接取得する。
Problems API の問題データはコンテスト終了後に遅れることがあるためである。

## 重複防止と手動 backfill

Discussion タイトル `ABC {番号} {問題記号} - {問題名}` が General カテゴリに
既に存在する場合は作成しない。通常の自動実行では最新100件だけを確認する。
`--contest-id abcNNN` で過去コンテストを手動 backfill する場合は、古い
Discussion も確認できるよう全ページを取得する。

## 障害時の扱い

個別の取得・作成失敗は残りの問題の処理を止めず、次回実行で再試行できる。
ただし、対象コンテストの取得が全件失敗した場合、または Discussion 作成を
1件以上試みて全件失敗した場合は、GitHub Actions を失敗させる。

## 実運用確認

ローカルの HTTP モックテストは実行済み。実際の Discussion を作成する
workflow_dispatch による E2E 実行は、明示的な承認を得てから行う。
172 changes: 172 additions & 0 deletions docs/dev-notes/2026-07-21/contest-discussion-automation/phase-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Phase 1: プロジェクト初期化 + GITHUB_TOKEN 権限検証

**目的:** uv プロジェクトの雛形を作り、「Actions 標準の `GITHUB_TOKEN` + `permissions: discussions: write` で Discussion 作成が可能」という設計上の前提を、実際にワークフローを1回動かして検証する。ここで検証できなければ、後続フェーズの認証方式を PAT に差し替える必要があるため、最初に確認する。

**注意:** このフェーズの最終ステップ (ワークフローの手動実行) は、公開リポジトリに実際の Discussion を作成する。**ユーザーの明示的な許可を得てから実行すること。** 無断でリモートに push したりワークフローを実行したりしない。

---

### Task 1: uv プロジェクトの初期化

**Files:**
- Create: `pyproject.toml`

- [ ] **Step 1: pyproject.toml を作成**

```toml
[project]
name = "contest-discussions"
version = "0.1.0"
description = "Automatically create GitHub Discussions for each ABC problem after the contest ends."
readme = "README.md"
requires-python = ">=3.13,<4.0"
dependencies = [
"requests>=2.32",
"selectolax>=0.4.6",
]

[dependency-groups]
dev = [
"pytest>=8.3",
"responses>=0.25",
]

[tool.pytest.ini_options]
pythonpath = ["src"]
```

- [ ] **Step 2: 依存関係をインストール**

Run: `uv sync`
Expected: `uv.lock` が生成され、`.venv/` が作成される

- [ ] **Step 3: Commit**

```bash
git add pyproject.toml uv.lock
git commit -m "chore: initialize uv project for contest discussion automation"
```

---

### Task 2: パッケージ雛形の作成

**Files:**
- Create: `src/contest_discussions/__init__.py`

- [ ] **Step 1: 空の `__init__.py` を作成**

```python
```

- [ ] **Step 2: Commit**

```bash
git add src/contest_discussions/__init__.py
git commit -m "chore: add contest_discussions package skeleton"
```

---

### Task 3: 定数モジュールの作成

**Files:**
- Create: `src/contest_discussions/constants.py`

- [ ] **Step 1: 定数を定義**

```python
"""Fixed IDs and text for the NoviSteps-Writeups repository.

See: https://github.com/AtCoder-NoviSteps/NoviSteps-Writeups
"""

# `gh api graphql -f query='query { repository(owner: "AtCoder-NoviSteps", name: "NoviSteps-Writeups") { id } }'`
REPOSITORY_ID = "R_kgDOTNgHyw"

# "General" discussion category.
# `gh api graphql -f query='query { repository(owner: "AtCoder-NoviSteps", name: "NoviSteps-Writeups") { discussionCategories(first: 10) { nodes { id name } } } }'`
GENERAL_CATEGORY_ID = "DIC_kwDOTNgHy84DAe9x"

DISCUSSION_BODY = "問題の感想や気づきを投稿・共有するスペースです"

# 週次 cron が1回失敗しても次回実行で埋め合わせられるよう、直近何件のABCを
# 重複チェック対象にするか。3件あれば3週分の取りこぼしまでカバーできる。
CONTESTS_TO_CHECK = 3
```

- [ ] **Step 2: Commit**

```bash
git add src/contest_discussions/constants.py
git commit -m "feat: add repository/category constants"
```

---

### Task 4: GITHUB_TOKEN の Discussion 書き込み権限を検証するワークフロー

**Files:**
- Create: `.github/workflows/smoke_test_discussion.yml`

- [ ] **Step 1: workflow_dispatch のみで動く検証用ワークフローを作成**

```yaml
name: Smoke test - GITHUB_TOKEN discussions:write

on:
workflow_dispatch:

permissions:
discussions: write

jobs:
smoke-test:
runs-on: ubuntu-latest
steps:
- name: Create a throwaway test discussion
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api graphql -f query='
mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
createDiscussion(input: {
repositoryId: $repositoryId,
categoryId: $categoryId,
title: $title,
body: $body
}) {
discussion { id url }
}
}' \
-f repositoryId="R_kgDOTNgHyw" \
-f categoryId="DIC_kwDOTNgHy84DAe9x" \
-f title="[smoke test] GITHUB_TOKEN discussions:write check (safe to delete)" \
-f body="Created by CI to verify that the default GITHUB_TOKEN can create Discussions. Safe to delete."
```

- [ ] **Step 2: Commit**

```bash
git add .github/workflows/smoke_test_discussion.yml
git commit -m "ci: add smoke test workflow for GITHUB_TOKEN discussions permission"
```

- [ ] **Step 3 (ユーザー確認必須・手動): push してワークフローを実行**

ここは実際に公開リポジトリへ push し、ワークフローを実行して Discussion を1件作成する。**必ずユーザーに実行してよいか確認してから進めること。**

```bash
git push -u origin <branch-name>
gh workflow run smoke_test_discussion.yml
gh run watch
```

- [ ] **Step 4: 結果を確認**

- 成功した場合: NoviSteps-Writeups の Discussions に "[smoke test] ..." が作成されているので、`gh api graphql` の `deleteDiscussion` mutation か GitHub UI から削除する。以降のフェーズは `GITHUB_TOKEN` + `permissions: discussions: write` の方針で進める。
- 失敗した場合 (権限エラー等): `plan.md` の「却下した代替案」を更新し、Fine-grained PAT をリポジトリシークレット (`GITHUB_DISCUSSIONS_TOKEN` 等) として発行する方針に切り替える。Phase 4/6 の認証部分をその前提で書き直す。

- [ ] **Step 5: 検証用ワークフローを削除 (恒久ワークフローに置き換えるため)**

Phase 6 で正式なワークフローに差し替えるため、このスモークテスト用ファイルは Phase 6 で削除する (このフェーズでは残しておいてよい)。
Loading