diff --git a/.claude/hooks/post-edit.mjs b/.claude/hooks/post-edit.mjs new file mode 100644 index 0000000..fefef8d --- /dev/null +++ b/.claude/hooks/post-edit.mjs @@ -0,0 +1,31 @@ +// Write / Edit の後に実行するフック (.claude/settings.json の PostToolUse) +// - .cs: UTF-8 の BOM がなければ付ける (CLAUDE.md のコード規約) +// - .github/workflows/*.yml: gh actions-lock --verify-local でロックファイルの漏れを確認する +import { readFileSync, writeFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; + +const input = JSON.parse(readFileSync(0, "utf8")); +const file = input.tool_input?.file_path ?? input.tool_response?.filePath; +if (!file) process.exit(0); +const path = file.replaceAll("\\", "/"); + +if (path.endsWith(".cs")) { + const bytes = readFileSync(file); + if (!(bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf)) { + writeFileSync(file, Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), bytes])); + } +} else if (/\/\.github\/workflows\/[^/]+\.ya?ml$/.test(path)) { + const result = spawnSync("gh", ["actions-lock", "--verify-local"], { + cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(), + encoding: "utf8", + }); + // gh や拡張機能がない環境では何もしない + if (result.error) process.exit(0); + if (result.status !== 0) { + console.log(JSON.stringify({ + decision: "block", + reason: "actions.lock がワークフローと一致していません。gh actions-lock でロックし直し、gh actions-lock --verify で確認してください。\n" + + (result.stdout + result.stderr).trim(), + })); + } +} diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..f961153 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit.mjs\"", + "timeout": 30 + } + ] + } + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 7096ecf..c32aad1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,12 @@ ## 言語 -- 日本向けのライブラリなので、ユーザーとのやり取り・ドキュメント(README、CLAUDE.md など)・コードコメント・XML ドキュメントコメント・コミットメッセージはすべて日本語で書く。 +- 日本向けのライブラリなので、ユーザーとのやり取り・ドキュメント(README、CLAUDE.md など)・コードコメント・XML ドキュメントコメント・コミットメッセージ・PR の説明はすべて日本語で書く。 + +## リポジトリ運用 + +- コミット・push・PR 作成は頼まれたときだけ行う。master で作業せず、ブランチを切ってから PR にする(依存する変更が続くときは、頼まれたら stacked PR にする)。 +- PR のマージはユーザーが行う。「マージした」と言われたら master を pull し、マージ済みのブランチを削除する。 ## 構成・ビルド @@ -15,8 +20,9 @@ - `ZipConvertCustomerBarCode.Drawing`: System.Drawing.Common による描画(Windows のみ)。 - `ZipConvertCustomerBarCode.SkiaSharp`: SkiaSharp による描画。名前空間は `SkiaSharp` との衝突を避けるため `ZipConvertCustomerBarCode.Skia`。 - `netstandard2.0` の既定 C# は 7.3。`#if NET8_0_OR_GREATER` の外では `record` や null 許容参照型など新しい構文は使わない。 -- バージョン・著作権表示は `Directory.Build.props` にまとめている。 +- バージョン・著作権表示・パッケージ情報は `Directory.Build.props` にまとめている。 - 公開メンバーには XML ドキュメントコメントを付ける(`GenerateDocumentationFile` 有効のため、ないと警告になる)。 +- `.cs` ファイルは UTF-8(BOM 付き)。新規ファイルにも BOM を付ける(`.claude/settings.json` のフックが Write / Edit 後に自動で補う)。改行は `core.autocrlf=true` に従う(Windows の作業ツリーでは CRLF、リポジトリ内は LF)。 ## テスト @@ -32,19 +38,22 @@ ## CI・リリース - `.github/workflows/build.yml`: push・PR で Ubuntu と Windows × コアの両方のビルドをテストし、パッケージを作る。 -- リリースは `v2.0.0` のようなタグを push すると、タグのバージョンで3パッケージを nuget.org に公開する(Trusted Publishing。ワークフローのファイル名 `build.yml` と Environment 名 `release` を nuget.org に登録しているので、どちらも名前を変えない)。 +- リリースは `v2.0.0` のようなタグを push すると、タグのバージョンで3パッケージを nuget.org に公開する(Trusted Publishing。ワークフローのファイル名 `build.yml` と Environment 名 `release` を nuget.org に登録しているので、どちらも名前を変えない)。nuget.org 側のポリシーは「新しいバージョンの追加のみ」なので、公開済みのバージョンは出し直せない(修正は次のバージョンで出す)。 - 公開ジョブは GitHub の `release` Environment で動く(`v*` タグのみ、cssho の承認後に実行。Secret `NUGET_USER` もこの Environment にある)。`Directory.Build.props` の `Version` はローカル用で、公開時はタグが優先される。 -- ワークフローのアクションは `.github/workflows/actions.lock`(`gh actions-lock`)でコミットに固定している。`uses:` を追加・変更したら `gh actions-lock` でロックし直し、`gh actions-lock --verify` で確認してから、ロックファイルとワークフローを一緒にコミットする。CI の `verify-lockfile.yml` が PR・push・週1回で検証する。アクションの更新は Dependabot(`.github/dependabot.yml`、既定の cooldown でリリースから3日待つ)が、ロックファイルの作り直しも含めて PR にする。 -- パッケージ作成時の検証(`EnablePackageValidation`)で、net8.0 版から netstandard2.0 版の公開 API が欠けているとエラー(CP0002)になる。 - -## コード規約 - -- `.cs` ファイルは UTF-8(BOM 付き)・CRLF 改行。どちらも維持する(新規ファイルにも BOM を付ける)。 +- ワークフローのアクションは `.github/workflows/actions.lock`(`gh actions-lock`)でコミットに固定している。`uses:` を追加・変更したら `gh actions-lock` でロックし直し、`gh actions-lock --verify` で確認してから、ロックファイルとワークフローを一緒にコミットする(ワークフローを編集すると、フックが `--verify-local` で漏れを知らせる)。JSON 出力は `--json=valid,findings` のように `=` でつなぐ(空白区切りだとパスとして解釈される)。 +- CI の `verify-lockfile.yml` が PR・push・週1回でロックファイルを検証する。アクションの更新は Dependabot(`.github/dependabot.yml`、既定の cooldown でリリースから3日待つ)が、ロックファイルの作り直しも含めて PR にする。 +- パッケージ作成時の検証(`EnablePackageValidation`)で、net8.0 版から netstandard2.0 版の公開 API が欠けているとエラー(CP0002)になる(net8.0 版にだけ API を足すのは検出しない)。 ## バーコード処理 - 住所の変換(`BarCode.cs`)は日本郵便のバーコードマニュアル(http://www.post.japanpost.jp/zipcode/zipmanual/p19.html)に従う。コード中の番号付きコメント(1.〜6.、補足ルール1〜5)はマニュアルの手順番号に対応している。 -- マニュアルでは住所を「住所A(町域名まで)」と「住所B(町域名以降)」に分け、住所Bだけから抜き出す。`Create(郵便番号, 住所)` は住所全体を住所Bとして扱うため、町域名に特定文字の前の漢数字を含む住所(一番町、八丁堀など)では誤る。これは既知の制限で、`Create(郵便番号, 住所A, 住所B)` を推奨している。 +- マニュアルでは住所を「住所A(町域名まで)」と「住所B(町域名以降)」に分け、住所Bだけから抜き出す。`Create(郵便番号, 住所)` は住所全体を住所Bとして扱うため、町域名に特定文字の前の漢数字を含む住所(一番町、八丁堀など)では誤る。これは既知の制限で、`Create(郵便番号, 住所A, 住所B)` か `ZipCodeDictionary` を渡す `Create` を推奨している。 - バーコードは常に 23 シンボル: スタート + 郵便番号 7 桁 + 住所表示番号 13 枠(不足分は `CC4` で埋める)+ チェックデジット + ストップ。 - バーの形は `CustomerBarCode.cs` の `BarPatternDic` で定義(1 シンボル = 幅 12px に 3 本、x=0/4/8、幅 2px、上・中・下段 各 4px)。この表は旧バージョンの GIF 画像とピクセル単位で一致することを確認済みなので、変更するときは既存出力と比較する。 - 描画パッケージは `GetBars()` の矩形を白地に黒で塗るだけにし、バーコードのロジックはコアに置く。 + +## 郵便番号データ・住所照合 + +- `ZipCodeDictionary` は利用者が渡す utf_ken_all.csv(UTF-8)と JIGYOSYO.CSV(Shift_JIS)を読む。Shift_JIS は .NET Core 以降では利用者が `CodePagesEncodingProvider` を登録する必要がある(コアに依存を足さないため、ライブラリ側では登録しない)。読み込み時の都道府県名の検証で、文字コードの取り違えを検出している。 +- 住所照合(`AddressMatcher`、`ZipCodeDictionary` の検索)は呼び出し回数が多く、LINQ や `ToUpperInvariant` だけで目に見えて遅くなったことがある。変更したら実データで計測する(英字の大文字化は ASCII のみを手で変換している)。 +- 事業所名での検索は、名前の完全一致を優先する。部分一致は誤検出が多かったため、番地なども一致する場合だけ採用する。