REVELUP
shwld
shwld
作ることに関わるひとを幸福にしたい スキ: 個人開発 設計 アジャイル 関数型 DDD

Notes

Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read

ドメインガード関数で不変条件を一元管理する

複数のフィールドの組み合わせでパネルや状態を決定するUIで、ドラッグ&ドロップなどの操作を追加すると矛盾した状態が生まれやすい。パネル判定ロジックが複数箇所に散らばっていたのが原因だった。

解決策として、不変条件をドメインガード関数に集約し、パネル判定も単一の関数に一元化した。

構造:

domain/
  item-input.ts         ← ドメインガード(不変条件のバリデーション)
  item-panel-grouping.ts ← パネル判定の唯一の情報源
ui/
  multi-panel-screen.tsx ← ガード関数を呼ぶだけ

ドメインガードの例:

// isArchived=true と activeGroupId!=null は共存できない
function validateItemInput(input: ItemInput) {
  if (input.isArchived && input.activeGroupId != null) {
    throw new Error("Archived items cannot belong to an active group");
  }
}

パネル判定の一元化:

// グルーピングと同じロジックで単一アイテムのパネルも判定する
function determinePanelForItem(item: Item): PanelType {
  return groupItemsByPanel([item])[0].panel;
}

// DnDハンドラは source × target のマトリクスで明示的に許可
function handleDragEnd(source: PanelType, target: PanelType) {
  const allowed = { Backlog: ["Archive"], Archive: ["Backlog"] };
  if (!allowed[source]?.includes(target)) return; // 無効な遷移は無視
  // ...
}

なぜこうしたか:

  • UI側にパネル判定の逆ロジックを持つと、グルーピング関数との間で不整合が起きる。判定を1箇所にすることで「UIが見ているパネル」と「ドメインが認識するパネル」のズレがなくなる
  • ドメインガードを永続化層の手前に置くと、APIからもUIからも同じ不変条件が強制される
  • DnDの遷移をマトリクスで管理すると、許可されない遷移を明示的にドキュメントできる
a month ago

Cloudflare Access applicationを再作成するとaudが変わる

Cloudflare Accessで保護したアプリのAccess applicationを削除→再作成すると、JWTのaud(Audience Tag)が新しい値に変わる。Worker側でaudを検証している場合、環境変数を更新しないと全APIが401になる。

症状がわかりにくいのがハマりポイント。Cloudflare Accessのログイン自体は成功するので「認証は通っている」ように見えるが、Worker側のJWT検証でaud不一致になりAPIが全滅する。フロントエンドは「session expired」的な表示になるため、Access側の問題だと気づきにくい。

// Worker側のJWT検証コード(簡略化)
const payload = await verify(jwt, publicKey);
if (payload.aud !== env.ACCESS_AUD) {
  // Access applicationを再作成すると、ここで落ちる
  return new Response('Unauthorized', { status: 401 });
}

調査の勘所:

  • ブラウザでAccess認証は通るのにAPIが401 → audの不一致を疑う
  • curl -v でレスポンスヘッダを確認し、Access自体は通過していることを確認する
  • JWTをデコードしてaudの値を現在のAccess applicationのAudience Tagと突き合わせる

ポイント:

  • Access applicationを再作成・置換したら、Worker側のACCESS_AUD環境変数を必ず更新する
  • IaCで管理しているなら、applicationリソースのaud出力をWorker設定に自動連携しておくと安全
a month ago

MCPツールは1機能1PRで積み上げると安定して進む

MCPサーバーにCRUD系のツールを追加するとき、一度に全部作ろうとせず1ツール1PRで積み上げるアプローチが効果的だった。1日で get / create / update / comment の4ツール+関連情報取得を安定してリリースできた。

構造:

mcp-server/
  tools/
    get-resource.ts      # PR #1
    create-resource.ts   # PR #2 (get-resourceの存在を前提に)
    update-resource.ts   # PR #3
    create-comment.ts    # PR #4
    get-workspace.ts     # PR #5

なぜこうしたか:

  • 依存の方向が自然に決まる — getを先に作り、create/updateはgetの動作を前提にできる。テストもgetの結果を使って検証できる
  • CIが毎回グリーンになる — 差分が小さいのでレビューもCI修正も局所的で済む
  • ロールバックが容易 — 問題が出ても1ツール単位で切り戻せる
  • 途中で方針変更しやすい — 3つ目を作る頃にはパターンが固まり、最初の2つをリファクタする判断もしやすい

代替案として「全ツールを1つの大きなPRで出す」方法もあるが、コンフリクトやCI修正が複雑になりがちで、途中で手が止まるリスクが高い。

a month ago

GitHub ActionsでPR本文に必須セクションを強制する

PR本文にADR(Architecture Decision Records)セクションを含めるルールを作ったが、AIエージェントがルールを守らないことがあった。CLAUDE.mdに書いてもスキップされるケースがあるため、CIで機械的にブロックする方が確実。

Before(手動):

レビュー時に「ADRセクションがない」と指摘して差し戻し。見落としも発生する。

After(自動):

# .github/workflows/pr-body-check.yml
name: PR Body Check
on:
  pull_request:
    types: [opened, edited]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Check required sections
        uses: actions/github-script@v7
        with:
          script: |
            const body = context.payload.pull_request.body || '';
            const required = ['## Related ADRs'];
            const missing = required.filter(s => !body.includes(s));
            if (missing.length > 0) {
              core.setFailed(`PR本文に必須セクションがありません: ${missing.join(', ')}`);
            }

ポイント:

  • types: [opened, edited] で作成時と編集時の両方をチェックする
  • AIエージェントに「ルールを守れ」と書くより、CIで落とすほうが確実に機能する
  • 必須セクションが増えたら required 配列に追加するだけで拡張できる
a month ago
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read

macOS標準のbash 3は連想配列が使えない

macOSに標準搭載されている bash は 3.x 系で、declare -A (連想配列)が使えない。bash 4以降の機能。

# bash 4+: 動く
declare -A counts
counts["key"]="value"

# bash 3 (macOS default): syntax error

Homebrew で bash 5 を入れていれば使えるが、CI環境やチームメンバーの環境では入っていない前提で書くほうが安全。

回避策:

  • 連想配列の代わりに grep + 一時ファイルや awk でキーバリューを管理する
  • /bin/bash ではなく /usr/bin/env bash を使い、PATH上の新しい bash を使う手もあるが、全環境に bash 4+ がある保証はない
  • 複雑なデータ構造が必要ならシェルスクリプトをやめて Python や jq に切り替える

ポイント:

  • #!/bin/bash で書くスクリプトはmacOSのbash 3互換を意識する
  • declare -A 以外にも、${var,,}(小文字変換)、|&(パイプのstderrリダイレクト)なども bash 4+ 限定
a month ago
Friend only
This note is available to friends only.
a month ago
Sign in to read

画面統合は「機能の棚卸し→移植→廃止」の順で進める

同じドメインの表示バリエーションが複数画面に分散しているとき、一気にリライトするのではなく段階的に統合する。

手順:

  1. 棚卸し — 各画面の固有機能を洗い出す。一覧・フィルタ・並べ替え・一括操作・削除など、画面ごとに何ができるかをテーブルにまとめる
  2. ベース画面の選定 — 最も機能が多い画面、または目標UIに最も近い画面をベースにする
  3. 機能の移植 — 他の画面にしかない機能をベース画面に移植する。このとき新しい抽象化は作らず、まず動く状態にする
  4. 参照の付け替え — ルーティング・ナビゲーション・テストの参照先をベース画面に切り替える
  5. 旧画面の削除 — 参照がなくなった画面ファイルとそのテストを削除。不要になった型定義やユーティリティも一緒に消す

実際に5画面→1画面の統合をやったとき、棚卸しで「バックログ画面のD&D並べ替え」が他の画面にないことが分かり、これをベース画面に移植する必要があった。棚卸しを飛ばすと、統合後に「あの機能どこ行った?」が起きる。

削除フェーズでは grep で旧画面名・旧ルートヘルパーへの参照が残っていないか確認する。テストファイルのimportに残りがちなので注意。

a month ago
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read

週次GCレポートの「次サイクルアクション」をwork item化する

コード品質の週次監査(harness GC)で見つかった逸脱を、レポートに書いて終わりにせず、追跡可能なwork itemまで落とし込むパイプラインを回した。

Before(手動):

週次レポートを書いて「次やること」リストを作るが、翌週には忘れて同じ逸脱が再発する。レポートが読まれるだけのドキュメントになりがち。

After(自動):

  1. 週次GCレポートを自動生成する(KPI集計、逸脱の列挙、次サイクルアクションの記載)
  2. レポートのPRをマージしたら、次サイクルアクションをwork itemとしてBacklogに登録する
  3. 通常のスプリント計画で優先順位づけされ、実装に回る
## レポートの「次サイクルアクション」の例

1. PRテンプレートに Verification セクションを必須化する
2. session-complete で verify 省略理由を自動追記する
3. Review Finding Rate の N/A 表示ルールを明文化する

↓ それぞれ独立したwork itemとしてBacklogに登録

ポイント:

  • レポートとwork itemの変換を明示的にやることで「書いて終わり」を防ぐ
  • 1つのアクションは1つのwork item。粒度を揃えると優先順位づけしやすい
  • work itemの「関連情報」にレポートPRへのリンクを残しておくと、なぜその作業が必要かの文脈が辿れる
a month ago

osascriptに変数を渡すときはon run argvを使う

macOSの osascript でシェル変数を文字列展開すると、ダブルクォートを含む値でAppleScriptの構文が壊れる。

# NG: 変数にダブルクォートが含まれると構文エラー
TITLE='He said "hello"'
osascript -e "display notification \"$TITLE\""

on run argv を使えば引数として安全に渡せる。

# OK: 引数として渡すので展開の問題がない
osascript - "$TITLE" "$MESSAGE" <<'EOF'
on run argv
  display notification (item 2 of argv) with title (item 1 of argv)
end run
EOF

ポイント:

  • ヒアドキュメントは <<'EOF'(シングルクォート)にして、シェルの変数展開を防ぐ
  • on run argv でAppleScript側の引数として受け取る
  • 通知以外(do shell script 等)でも同じパターンが使える
a month ago

放置worktreeの棚卸し — work-item状態を先に戻してから削除する

AIエージェントがworktreeを作成してタスクに着手すると、対応するwork-itemが in-progress に遷移する。PRを作らず中断したworktreeが溜まると、work-itemが実態と合わない状態で残り続ける。

仕組み:

  • worktreeの一覧を取得し、各ブランチの状態(PRの有無、未マージコミット、mainとの差分)を確認する
  • PRなし+mainと同一 → 安全に削除可能
  • PRなし+未マージコミットあり → 内容を確認して判断(大抵は中途半端な実装で再実装可能)
  • in-progress / in-review のwork-itemを ready に戻す
  • その後worktreeとブランチを削除する

棚卸しの手順:

  1. 全worktreeを列挙し、各ブランチのPR有無・未マージコミット・mainとの差分を確認する
  2. タスク管理の状態を実態に合わせて復元する(in-progress → ready 等)
  3. その後にworktreeとブランチを削除する

順序が重要で、2→3の順でないとタスク管理側に不整合が残る。

やってみてどうだったか:

  • 9つのworktreeが溜まっており、5件のwork-itemの状態修正が必要だった。エージェントが自律的にworktreeを作るプロジェクトでは、定期的な棚卸しが要る
  • 「worktree削除」と「work-item状態の復元」を別々にやると不整合が残る。状態を戻すのが先、削除は後の順序が重要
  • 次はworktree作成時にタイムアウトや自動クリーンアップの仕組みを入れたい
a month ago
Friend only
This note is available to friends only.
a month ago
Sign in to read

GitHub Actionsのpermissionsは未指定だとreadがデフォルト

GitHub Actionsでpermissionsを明示的に書くと、書かなかったスコープはすべてnoneになる。一方、permissionsキー自体を省略すると、リポジトリ設定に依存する(多くの場合read)。

# これだと issues: write 以外は全部 none になる
permissions:
  issues: write

# issueコメントだけしたい場合でも contents: read が必要なケースがある
permissions:
  contents: read
  issues: write
  • id-tokenだけはデフォルトがnone(他のスコープと違う)。OIDC認証が必要な場合は明示的にid-token: writeを追加する
  • ghコマンドはGitHub-hosted runnerにプリインストールされているので、別途インストール不要。GITHUB_TOKENも自動で設定される
  • CIでAIエージェントにgitやghコマンドを使わせる場合、permissionsの設定が実質的なサンドボックスになる。必要最小限のスコープだけ与えることで、エージェントの行動範囲を制限できる

refs: https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

a month ago
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Friend only
This note is available to friends only.
a month ago
Sign in to read
Next Page