ortの灰ログ

人狼のことや技術のことや日々雑感

Claude Code 試行錯誤のメモ書き

Claude Codeを使うようになったので色々試行錯誤しているメモを残しておく (一瞬で環境が変わるため適宜更新する可能性がある)

前提

  • 2025/08/28時点
  • 主に古いWebアプリを最新の技術に置き換える用途で使っている
  • 開発環境はWSL2だったりMacだったりするので適宜読み替えること
  • Max 5xか20x

設定

共通(=非プロジェクト固有)

CLAUDE.md

ユーザーとの応答は日本語で行ってください
  • 英語のほうが性能が良いだろうがすっぱり諦めている

~/.claude/settings.json

{
  "hooks": {
   "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "terminal-notifier -title \"Claude Code\" -message \"Waiting for your response...\" -sound default"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "terminal-notifier -title \"Claude Code\" -message \"Waiting for your response...\" -sound default"
          }
        ]
      }
    ]
  },
  "mcpServers": {
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--config",
        "./playwright-config.json"
      ],
      "env": {}
    }
  },
  "env": {
      "DISABLE_MICROCOMPACT": true
  }
}
  • hookで作業完了やユーザーへの問いかけを通知
  • playwrightのMCP
    • 導入方法は省略
  • 使えば使うほどClaudeCodeくんの頭がどんどん悪くなっていくため DISABLE_MICROCOMPACT を試している

~/.serena/serena_config.yml

gui_log_window: false
web_dashboard: false

プロジェクトごと

よくやる構成

{project}
├── .claude/
│   ├── commands/
│   │   ├── go.md
│   │   └── prepare-command.md
│   ├── bugs.md
│   ├── features.md
│   ├──settings.json
│   └── tasks.md
├── doc/
│   ├── requirements/
│   ├── designs/
│   ├── guidelines/
├── app/
├── CLAUDE.md

CLAUDE.md

# CLAUDE.md

このファイルは、このリポジトリでコードを扱う際の Claude Code (claude.ai/code) へのガイダンスを提供します。

## プロジェクト概要

{プロジェクト概要を記載}

## 重要なルール(必須)

### エラー対応

- エラーを 2 回解決できなかった場合、ユーザーに今後の進め方の確認を取ってください

### 開発フロー

1. **features.md の新規要件を確認**
   1. `{project_root}/.claude/features.md` の新規要件を確認
   2. 要件に基づいて必要な設計ドキュメントを doc/ 配下に作成・更新
   3. 要件を具体的なタスクに分解して `{project_root}/.claude/tasks.md` に追加
   4. `{project_root}/.claude/features.md` から内容を削除
2. **bugs.md の新規バグを確認**:
   1. `{project_root}/.claude/bugs.md` のバグ内容を確認
   2. バグを修正タスクに分解して `{project_root}/.claude/tasks.md` に追加
   3. `{project_root}/.claude/features.md` から内容を削除
3. **tasks.md のタスクを確認**:
   1. `{project_root}/.claude/tasks.md` を参照してタスクを確認
   2. **重要**: 一度に実装するのは**tasksの1セクション(###で区切られた範囲)まで**
   3. タスクに記載されている作業を実施
   4. 実装したページを playwright で確認し、エラーが発生しないことを確認
      1. エラーが発生する場合、ユーザーに確認
   5. タスクが完了したら、`{project_root}/.claude/tasks.md` のチェックボックスに記録
4. **ドキュメンテーション**:
   1. 必要に応じて doc/ディレクトリに記録
5. **commit 前作業**:
   1. **commit 前に必ず lint・format・type-check を実行** - `pnpm lint && pnpm format && pnpm type-check` を実行
6. **ユーザーに確認を依頼**:
   1. commit 前にユーザーに修正内容の確認を依頼する
7. **適切な粒度で commit**:
   1. ユーザーの承認を得られたら、機能追加、バグ修正、リファクタリングなど論理的な単位で commit
   2. **commit message に絵文字を使用しない** - シンプルで読みやすいメッセージにする
8. `{project_root}/.claude/tasks.md` のタスクがなくなるまでこのフローを 1 から繰り返す
   1. 途中で features や bugs に追記されることがある


## 開発コマンド

開発コマンドの詳細は `doc/guidelines/development-commands.md` を参照してください。

## ドキュメンテーション

ドキュメンテーションガイドラインの詳細は `doc/guidelines/documentation-guidelines.md` を参照してください。

## プロジェクト構成

プロジェクト構成の詳細は `doc/designs/project-structure.md` を参照してください。
  • /init したあとに開発ルールを記載
  • doc配下にドキュメントを作らせてこのファイルから参照させる
    • たとえばファイル名ルール等は早めに手をつけないと後で酷い目に合う
  • 最初のほうは doc/requirements/**.md doc/designs/**.md に要件や設計を起こすところから始め、固まってきたら初めてtasksを作らせる

.claude/settings.json

{
  "permissions": {
    "allow": [
      "Bash(mkdir:*)",
      "Bash(mv:*)",
      "Bash(find:*)",
      "Bash(grep:*)",
      "Bash(npm install)",
      "Bash(npm install:*)",
      "Bash(npm run:*)",
      "Bash(npm test)",
      "Bash(npx playwright test:*)",
      "Bash(git add:*)",
      "Bash(git commit:*)",
      "Bash(npm test:*)",
      "mcp__serena",
      "mcp__playwright",
      "mcp__ide__executeCode",
      "mcp__ide__getDiagnostics",
    ],
    "deny": [],
    "ask": [
      "Bash(rm:*)"
    ]
  }
}
  • このあたりはプロジェクト毎に結構違ってくるのでglobalでなく個別に出し入れしている
  • 他人と開発するときは settings.local.json にしてgitignoreするほうが良いかも

MCP追加

  • serena
    • セマンティック解析
    • 導入後に /mcp__serena__initial_instructions する
    • .claude/settings[.local].jsonmcp__serena をallowしておかないと色々確認されて面倒なので早めに入れる
  • playwright
    • playwright testや、画面を開いて確認させるときに使う
  • context7
    • ドキュメントで調べて実装してくれ!なときに use context7 を付けて指示する

commands

.claude/commands/prepare-commit.md

- 必要に応じて、.claude/tasks.md に進捗を記録してください
- lint, format, type-check を行い、修正してください
- 上記完了後に適切な単位で commit してください
  • ユーザーが実装内容を確認した後にコミットさせるときに使う

/.claude/commands/go.md

開発フローに従って進めてください
  • 毎回これを打つのが面倒で定義した
  • 「開発フローに従って」を付けないと酷い目にあう(つけても無視されることもあるが)

ロールをプレイ!というサイトを作った

この記事は

ロールをプレイ!というサイトを作ったので技術面のほうを書いておく

x.com

技術要素

https://github.com/h-orito/chat-role-play-graphql

それぞれ選定理由は「無料か少額でできる範囲で、勉強がてら使いたいものを使う!」といういつも通りのやつ

所感

  • Oracle Cloud無料枠がつよつよ&backendをGoで実装しているのもありGraphQLのqueryレスポンスが超早い
  • backendもfrontendもDBもk8sに載せているのでSSR(=内部通信)が超早い
  • k8sに載せているものも増えてきたのでメモリが足りるのかわからないが、スケールアップも可能といえば可能
    • コンテナ化しているのでやばくなってもCloud Runとかに切り替えられそう
  • 画像アップロードがあるが相当流行らない限りCloudflare r2無料枠は超えないので無料で運用できそうで良い

今後のTwitter連携機能について

この記事は

2023/02/09をもってTwitter API v1.1/v2の無料アクセスがなくなり、有料アクセスのみとなるそうです(利用者でなく、開発者が支払うものです)。
マスク氏の別ツイートでは$100/月~との情報もありました。
私の個人開発webサービスは一部でTwitter連携を利用しているため、
影響範囲と今後の対応予定について記載しておきます。

影響範囲

赤字部分は2023/02/04修正

各所から公式に問い合わせした結果、認証まわりについては今回の無料終了範囲に含まれていないようです。
したがって、有料APIを使用しない場合、それぞれ以下が影響範囲となります。

Scenario Tuker

  • ログイン認証にTwitterを利用しているが、無料終了範囲に含まれないため影響なし
  • 以下の機能はv2 APIを利用しているため、2/9より使用不可能
    • 公開範囲が「フォロワーのみ」「相互フォロワーのみ」の感想を閲覧する際、閲覧できるかの判定
    • ユーザー一覧での検索条件「Twitterでフォローしている人に絞る」
    • シナリオ詳細での通過記録検索条件「Twitterでフォローしている人に絞る」

FIREWOLFLASTWOLF

  • ログイン認証にTwitterを利用しているが、無料終了範囲に含まれないため影響なし
  • その他もv1.1/v2 APIを利用していないため、2/9時点では影響なし

HOWLING WOLF

  • ログイン認証にTwitterを利用しているが、無料終了範囲に含まれないため影響なし
  • 村作成や村開始時の自動ツイートにAPIを利用しているため、2/9より停止

WOLF MANSION

  • 村作成や村開始、エピローグ時の自動ツイートにAPIを利用しているため、2/9より停止

それ以外のサービス

TwitterAPIを利用していないので、影響はありません。

対応予定

ざっくりいうと、

  • 個人開発の無料サービスで$100/月は支払っていられないので、v1.1/v2 API利用機能を閉じていきます(2/9まで予定)
  • Twitterの最近の動向を見ていると、ログイン認証もいつまでもつかわからないので、可能な限りGoogle連携など他の手段に切り替えたり、事前に紐付けを行えるようにします

Scenario Tuker

  • v2 API利用機能を閉じる
    • 公開範囲が「フォロワーのみ」「相互フォロワーのみ」の感想は「自分のみ」に強制変更
  • Twitter連携でログインした状態で追加でGoogle連携認証も行えるようにする
    • これにより、万が一Twitterログイン認証が不可能になっても、Google連携で同アカウントにログインできるようになる

HOWLING WOLF、FIREWOLF、LASTWOLF

  • Twitter連携でログインした状態で追加でGoogle連携認証(もしくはメールアドレス・パスワード認証)も行えるようにする
    • これにより、万が一Twitterログイン認証が不可能になっても、Google連携で同アカウントにログインできるようになる

その他

  • 人狼のほうはid/pass方式にしたいんだけどfirebase authを使っていると実装がかなり面倒そうなのでおそらく他にする
  • ただでさえ忙しいのにこういう後ろ向きな開発はとてもつらい
  • Twitterならそう簡単に使えなくならないやろ、と思っていたここ数年の自分を殴りたい
  • ですます調とかであるとか体言止めとか混在しているけど直す気がおきないのでこのまま投稿することにする

iOS16でFirebase authが通らなくなった問題の対応備忘録

事象

iOS16の各種ブラウザやMacOS SafariからFirebase authenticationの signInWithRedirectgetRedirectResult するとユーザー情報が取得できず null が返される

対応方法概要

サードパーティのストレージ アクセスをブロックするブラウザーでの壊れたリダイレクト サインインを軽減する  |  Firebase
の軽減策 #3の通り対応する

原因調査

ぐぐっていたら
Login to Firebase does not work on Safari 16.1 beta · Issue #6716 · firebase/firebase-js-sdk · GitHub
これに行き着いた。これやん。

リプライの
https://github.com/firebase/firebase-js-sdk/issues/6716#issuecomment-1320593233
ここから
サードパーティのストレージ アクセスをブロックするブラウザーでの壊れたリダイレクト サインインを軽減する  |  Firebase
これに行き着いた。
引用すると、

サードパーティのストレージ アクセスをブロックするブラウザーでは機能しません

.

認証フローがアプリ ドメインに戻ると、サインイン ヘルパー ドメインブラウザー ストレージにアクセスします。この軽減策と次の (コードを自己ホストするための) 軽減策により、ブラウザーによってブロックされるクロスオリジン ストレージ アクセスが排除されます。

とのこと。iOS16やMacOS Safariが先に厳しくなったのね。

対応内容

  • Nuxt2でFirebase authenticationのTwitter認証を利用
  • Netlifyにデプロイしている

自分のアプリでやっていることとしてはこんな感じなので、

アプリ側の設定

軽減策#3のやつ。
https://<app domain>/__/auth/ から https://<project>.firebaseapp.com/__/auth/ にproxyしてあげる必要がある(リダイレクトではダメ)ので、
Nuxt2の場合

static/_redirects

/__/auth/* https://<project>.firebaseapp.com/__/auth/:splat 200

を設定して再デプロイ。

Netlify側の設定

軽減策#1前半のやつ。
自分の場合 appDomain はNetlifyのEnvironment variablesで設定しているので、
環境変数appDomain にあたる内容を

<project>.firebaseapp.com から <app domain> に変更(して再デプロイ)

Twitter側の設定

軽減策#1後半のやつ。
Twitter Developer Portalで連携しているアプリの設定を開き User authentication settings Edit → App infoCallback URI / Redirect URLhttps://<app domain>/__/auth/handler を追加(既存のと変更でも良いかも)

以上。これでiOS16でも動作するようになった。

備考

軽減策#2の signInWithPopup() でも良いはずだが、
自分の場合はPWA対応もしているため、これは使えなかった。
(PWAだとsignInWithPopupが動作しない)

Stable Diffusion試してみたメモ

記事概要

話題の「GitHub - CompVis/stable-diffusion」で遊んでみる

日本語の解説記事とかも充実しているので参考にしたサイトをメモするだけになりそう

環境構築

PC環境

参考記事

画像生成AI「Stable Diffusion」をローカル環境で実行する - パソコン関連もろもろ

WSLからGPUを使えるようにする

上記記事を参考に構築して実行してみると

RuntimeError: Found no NVIDIA driver on your system. Please check that you have an NVIDIA GPU and installed a driver from http://www.nvidia.com/Download/index.aspx

と出た。
調べたところWindows10の場合はOS versionが21H2でないとWSL上でGPUを使えない模様。
(winverで調べたところ21H2だった)

参考: Enable NVIDIA CUDA on WSL 2 | Microsoft Docs

諸般の事情でまだWindows11にしたくないので、書いてある通り21H2をインストール。

蛇足
ぐぐるNVIDIA公式が出てきてWindows Insider Programに参加して適切なOS buildにすると書いてあるが、
今それをやるとWindows11に誘導されるだけなので、2022/8現在はMicrosoftに書いてある通り21H2にするのが良さそう

実行

sampleのを実行してみる

$ python3 scripts/txt2img.py --prompt "a photograph of an astronaut riding a horse" --plms --ckpt sd-v1-4.ckpt --n_samples 1
Your samples are ready and waiting for you here:
outputs/txt2img-samples

Enjoy.

やったぜ。

Cloudflare r2試してみる備忘録

yusukebe.com

この記事を見て、ええやん無料枠で色々やれるし試してみよう!と思ったので試してみた
(ほぼ書いてくださっているとおりに進めたらできたのであまり書くことはない。神。)

サイトを見ながらやったこと

  • cloudflare登録して
  • r2を使えるようにして
    • 2022/05時点ではr2のページから進んでいくとクレカ登録が通らなかった
    • 設定画面から先にクレカを登録しておくことで解決
  • wranglerをインストールして
    • 画面で案内があるので特に詰まるところはないと思う
  • bucketを作って
  • workerを作成して
  • これでworker経由でr2に画像アップロード/表示する準備が整ったはず

以下は、それ以外にやってみたことを記載

Webアプリケーションから画像アップロードしてみる

Nuxt3からアップロードしてみる
こんな感じにすればアップロードできた
(inputまわりは省略、Nuxtあまり関係ないかも)

export const upload = async (imageFile: File): string => {
  const reader = new FileReader()
  reader.readAsDataURL(imageFile)
  await new Promise<void>((resolve) => (reader.onload = () => resolve()))
  // data:${mimeType};base64,${base64EncodedFile} が得られる
  const dataurl = reader.result as string
  const res = await useFetch(
    `${worker_base_url}/upload`,
    {
      method: 'PUT',
      body: {
        // base64部分のみを抽出
        body: dataurl.replace(/data:.*\/.*;base64,/, '')
      }
    }
  )
  const path = (res.data as Ref<string>).value
  return `${worker_base_url}/${path}`
}

ただしこれでlocalhostからアップロードするとCORSエラーとなるので、

worker側でCORS許可

workerのindex.tsに以下を追加する

app.use('*', cors())

https://github.com/honojs/hono を利用しているので簡単に設定できた
ローカルで試したので全許可しているが、本番で利用するときは↓あたりを参考にして調整すれば良さそう

github.com
https://developers.cloudflare.com/workers/examples/cors-header-proxy/

要はOPTIONSのリクエストで 'Access-Control-Allow-Origin': '*' になっているので
本番環境のドメインにしてあげれば良いはず。

おまけ

無料枠がかなり大きいので個人サイトなら十分使い倒せるのではないかと思う
2022/05時点でざっくり以下のようになっていた

R2

  • 10GB
  • 更新系1,000,000回/月
  • 取得系10,000,000回/月

worker

Microsoft Translator使ってみた備忘録

翻訳APIの無料枠あるし、応用すれば自動で人狼の再翻訳村とかできるんじゃね?という思いつきで使ってみた
2022/5現在、月200万文字まで無料っぽい。

https://azure.microsoft.com/ja-jp/free/

ここからアカウント作成
登録完了したらクイックスタートセンターを開く
上部検索でTranslatorで検索して「Cognitive Services | Translator」を開く
いい感じに登録してキーを確認

Microsoft Translator APIを使ってみた - Qiita
Microsoft Azure Cognitive Services テキスト翻訳とは - Azure Cognitive Services | Microsoft Docs

このあたりを参考に叩けばok
情報量なさすぎるけどそのくらい簡単にできたよ、ということで。

WOLF MANSIONで翻訳者の能力を受けると、
発言した内容をランダム言語に翻訳→再度日本語訳して発言されるようにできた。

全員デフォでこの状態になる「再翻訳村」もできるなぁと思ったのだけど、
月200万文字までだとワンチャン超えそうだなと思ったので一旦封印。

あと、翻訳精度が高すぎて、昔と比べてあまり面白い文章に変わらないという難点が。
(翻訳精度高いのはいいことなんだけども)

おまけのざっくりKotlinコード

private fun fromJa(str: String, language: String): String {
    val builder = UriComponentsBuilder.fromUriString(baseUrl)
    val uri = builder.queryParam("api-version", "3.0")
        .queryParam("from", "ja")
        .queryParam("to", language)
        .toUriString()
    return call(uri, str)
}

private fun toJa(str: String, language: String): String {
    val builder = UriComponentsBuilder.fromUriString(baseUrl)
    val uri = builder.queryParam("api-version", "3.0")
        .queryParam("from", language)
        .queryParam("to", "ja-Jpan")
        .toUriString()
    return call(uri, str)
}

private fun call(uri: String, str: String): String {
    val requestEntity = RequestEntity
        .post(URI(uri))
        .header("Ocp-Apim-Subscription-Key", key)
        .header("Ocp-Apim-Subscription-Region", "japaneast")
        .header("Content-Type", "application/json; charset=UTF-8")
        .body(listOf(TranslatorBody(str)))
    val restTemplate = RestTemplate()
    val response = restTemplate.exchange(requestEntity, Array<TranslatorResponse>::class.java)
    return response.body?.firstOrNull()?.translations?.firstOrNull()?.text ?: str
}

data class TranslatorBody(
    val Text: String
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class TranslatorResponse(
    val translations: List<TranslatorResponseContent>
) {
    @JsonIgnoreProperties(ignoreUnknown = true)
    data class TranslatorResponseContent(
        val text: String
    )
}