Claude Media
Claude Codeでkintoneカスタマイズを書く — フィールドコードをCLAUDE.mdに固定

Claude Codeでkintoneカスタマイズを書く — フィールドコードをCLAUDE.mdに固定

kintoneのJavaScriptカスタマイズをClaude Codeに書かせるとき、フィールドコード表とイベント種別をCLAUDE.mdとrulesに置く方法と、書いたコードを表と突き合わせる検証スクリプトの例をまとめます。

kintoneのJavaScriptカスタマイズは、アプリごとに違う「フィールドコード」を文字列で指定して書きます。Claude Codeに任せるときに事故が起きやすいのも、この文字列です。コードの見た目は正しくても、フィールドコードが1文字違うだけでイベントは発火せず、エラーも出ません。対策は、アプリのフィールドコード表とイベント種別の早見表をCLAUDE.mdとrulesに置き、書き終えたコードを表と機械的に突き合わせることです。この記事では、その構成と実際に動かした検証スクリプトを順に示します。

kintoneのレコード操作をMCPサーバー経由で行う方法は、Claude kintone連携ガイドとkintoneレコード検索・更新をClaudeで効率化する方法で扱っています。ここで扱うのは、アプリに読み込ませるJavaScriptファイルを書く開発作業のほうです。

取り違えが起きるのは3か所

kintoneのカスタマイズで、Claude Codeが自力では確定できない情報は3つあります。

1つ目は、フィールドコードそのものです。画面に出る「表示名」と、コードから参照する「フィールドコード」は別物で、レイアウトを見ても分かりません。record['顧客名']のような参照が正しいかどうかは、アプリ側の設定次第です。リポジトリのコードだけ読んでも、いま本番のアプリがどのコードを持つのかは分かりません。

2つ目は、イベントタイプにフィールドコードが埋め込まれる点です。フィールド値の変更を受けるイベントは、app.record.create.change.フィールドコードのように、タイプ名の末尾にフィールドコードを付けて指定します。モバイルはmobile.が先頭に付いた別のタイプです。ここを間違えると、ハンドラーは登録されても呼ばれません。

3つ目は、テーブル(サブテーブル)の構造です。テーブル内の項目はrecord['テーブル_0'].value[0].value['文字列_1']のように、行の配列を挟んで参照します。トップレベルのフィールドと同じ書き方では届きません。

どれも「リポジトリの外にある事実」です。CLAUDE.mdに書く価値があるのは、まさにこの種の情報です。公式のメモリ解説でも、CLAUDE.mdは同じ間違いを繰り返されたときや、毎回チャットで補足している内容を書き留める場所だと説明されています。

CLAUDE.mdには何を置き、何を置かないか

CLAUDE.mdはプロジェクト直下の./CLAUDE.mdか./.claude/CLAUDE.mdに置きます。内容は強制される設定ではなく、Claudeが文脈として読む指示です。公式は1ファイル200行未満を目安にしており、長いと遵守率が下がります。そのため、kintone向けには次の割り分けが現実的です。

置く場所入れるもの理由
CLAUDE.md入れるものアプリID、環境の別、コード規約、検証コマンド、フィールドコード表への@参照理由毎セッション必要な前提
.claude/rules/(paths付き)入れるものイベントタイプの早見表、return eventなどの書き方の約束理由カスタマイズのJSを触るときだけ必要
生成するMarkdown入れるものフィールドコード表そのもの理由アプリの変更に合わせて作り直す

CLAUDE.mdの例は次のとおりです。アプリID、パス、コマンドは架空の例です。

# kintoneカスタマイズ
 
## 対象
- アプリ12(顧客管理)。JSは`customize/app12/`に置く
- 本番とは別に動作テスト環境で確認する。本番へ反映するのは人が行う
 
## フィールドコード
- コードは必ず @kintone/app12.md の表から選ぶ。表示名から推測しない
- 表に無いコードが必要になったら、コードを作らず質問する
- テーブル内の項目は表の「テーブル.項目」の形で載っている
 
## 規約
- イベントは`kintone.events.on`で登録し、変更したら必ず`return event`する
- 編集後は`node scripts/check-field-codes.mjs kintone/app12.fields.json customize/app12/*.js`を実行し、出力が空になるまで直す

「表から選ぶ」「無ければ質問する」は、公式が勧める「検証できる具体的な書き方」に沿った指示です。「フィールドコードを正しく使う」のような抽象的な文では、守れたかどうかを誰も判定できません。

CLAUDE.mdから@kintone/app12.mdのように他のファイルを読み込む記法も使えます。相対パスは、importを書いたファイルの位置から解決されます。ただしimportしたファイルはCLAUDE.mdと一緒に読み込まれるので、文脈の節約にはなりません。表が大きいアプリを何十本も抱えるなら、CLAUDE.mdでは@で読まずに「customize/app<ID>/を編集する前にkintone/app<ID>.mdを読む」と書いておくほうが、読み込み量を抑えられます。

フィールドコード表をAPIから作る

表を手で写すと、その時点で取り違えが混入します。kintoneにはフォームのフィールド一覧を返すREST APIがあるため、そこから生成します。

GET /k/v1/app/form/fields.json?app=<アプリID>は、フィールドの一覧と設定を返します。レスポンスはproperties.フィールドコードをキーにしたオブジェクトで、各フィールドにlabel、code、typeが入ります。typeの値はSINGLE_LINE_TEXT、DROP_DOWN、SUBTABLEなどです。ルックアップは、コピー元のフィールドのtypeで返ります。つまりtypeを見ても、そのフィールドがルックアップかどうかは区別できません。

運用中のアプリを読むには、アプリのレコード閲覧権限かレコード追加権限が必要です。動作テスト環境の設定を読む場合はURLの先頭が/k/v1/preview/app/form/fields.jsonに変わり、必要なのはアプリ管理権限です。認証は、パスワード認証、APIトークン認証、セッション認証、OAuth認証のいずれかで、APIトークンならX-Cybozu-API-Tokenヘッダーで渡します。

curl -s -H "X-Cybozu-API-Token: $KINTONE_TOKEN" \
  "https://example.cybozu.com/k/v1/app/form/fields.json?app=12" \
  > kintone/app12.fields.json
node scripts/fields-to-md.mjs kintone/app12.fields.json > kintone/app12.md

トークンは環境変数から渡し、JSONにもCLAUDE.mdにも書き込みません。ドメインのexampleは置き換えてください。

JSONをMarkdownの表にするスクリプトは次のとおりです。テーブル内の項目は明細.数量のように親のコードをつないで1行にします。

import { readFileSync } from 'node:fs';
 
const { properties } = JSON.parse(readFileSync(process.argv[2], 'utf8'));
const rows = [];
for (const f of Object.values(properties)) {
  rows.push([f.code, f.type, f.label]);
  if (f.type === 'SUBTABLE') {
    for (const s of Object.values(f.fields)) {
      rows.push([`${f.code}.${s.code}`, s.type, s.label]);
    }
  }
}
console.log('| フィールドコード | type | 表示名 |\n|---|---|---|');
for (const r of rows) console.log(`| ${r.join(' | ')} |`);

顧客名、ステータス(ドロップダウン)、明細(テーブル)の3フィールドだけを持つ手元のサンプルJSONで実行すると、次の表が出ました。

| フィールドコード | type | 表示名 |
|---|---|---|
| 顧客名 | SINGLE_LINE_TEXT | 顧客名 |
| ステータス | DROP_DOWN | ステータス |
| 明細 | SUBTABLE | 明細 |
| 明細.数量 | NUMBER | 数量 |

アプリのフォームを変えたら、同じ2コマンドで作り直します。表は生成物なので、手で編集しないと決めておくと運用が崩れません。

イベント種別の早見表をrulesに置く

イベントの一覧は、毎回CLAUDE.mdに載せるほどの量ではありません。.claude/rules/にpaths付きで置くと、該当するJSファイルをRead、Write、Editしたときに読み込まれます。

---
paths:
  - "customize/**/*.js"
---
 
# kintoneイベントの約束
 
- 登録は`kintone.events.on(type, handler)`。複数タイプには配列を渡せる
- 値の変更は`app.record.<画面>.change.<フィールドコード>`。画面は`create` / `edit` / `index.edit`
- 保存前は`app.record.<create|edit>.submit`。`event.error`か`return false`で保存を止める
- 一覧表示は`app.record.index.show`。`event.viewType`で表示形式を見分ける
- モバイルを対象にするときは各タイプの先頭に`mobile.`を付けた別登録にする
- 書き換えたら必ず`return event`する

この早見表は、kintoneのイベントのページに載っている仕様の要約です。主なイベントの発生タイミングは次のとおりです。

イベントタイプ発生するタイミング
app.record.index.show発生するタイミングレコード一覧の表示時。ページ送り、絞り込み、カテゴリー変更、ソートでも発生
app.record.create.show / app.record.edit.show発生するタイミング追加画面・編集画面の表示後
app.record.create.change.フィールドコード発生するタイミング追加画面または再利用画面で、指定フィールドの値を変更したとき
app.record.create.submit発生するタイミング追加画面または再利用画面で【保存】を押したとき
app.record.detail.show発生するタイミング詳細画面の表示後

注目したいのはindex.showの発生範囲です。表示したときだけでなく、ページ送りや絞り込みでも発生します。ここに重い処理や通知を入れると、一覧を操作するたびに何度も走ります。ハンドラーを書かせるときは「一覧表示で1回だけのつもりか、操作のたびでよいか」を指示に含めます。

書いたコードを表と突き合わせる

CLAUDE.mdは文脈であり、強制ではありません。公式も、必ず実行させたい処理はhookにするよう案内しています。フィールドコードの取り違えは検証コマンドで機械的に拾えるので、Claudeに「実行して出力を読む」ことを指示に入れておきます。

検証スクリプトは、JSの中の['...']形式の参照と、.change.xxx'形式のイベントタイプからコード部分を取り出し、フィールド一覧に無いものを報告します。

import { readFileSync } from 'node:fs';
 
const [fieldsJson, ...files] = process.argv.slice(2);
const { properties } = JSON.parse(readFileSync(fieldsJson, 'utf8'));
const known = new Set();
for (const f of Object.values(properties)) {
  known.add(f.code);
  if (f.type === 'SUBTABLE') for (const s of Object.values(f.fields)) known.add(s.code);
}
const patterns = [/\[['"]([^'"]+)['"]\]/g, /\.change\.([^'"\s]+)['"]/g];
let bad = 0;
for (const file of files) {
  readFileSync(file, 'utf8').split('\n').forEach((line, i) => {
    for (const re of patterns) {
      for (const m of line.matchAll(re)) {
        if (!known.has(m[1])) {
          console.log(`${file}:${i + 1}: 未定義のフィールドコード ${m[1]}`);
          bad++;
        }
      }
    }
  });
}
process.exit(bad ? 1 : 0);

意図的にコードを間違えたJSで試しました。フィールドコードを顧客名称と書いた行と、イベントタイプ側をステ-タスと書いた行を混ぜています。

node scripts/check-field-codes.mjs kintone/app12.fields.json customize/app12/main.js
customize/app12/main.js:4: 未定義のフィールドコード 顧客名称
customize/app12/main.js:8: 未定義のフィールドコード ステ-タス

終了コードは1です。正しいコードだけのファイルなら出力は空で、終了コードは0になります。この仕組みでは、フィールドコード自体が存在するかどうかは確かめられますが、「存在するコードだが、取り違えて別のフィールドを書き換えている」ケースは拾えません。コード表に表示名とtypeを併記してあるのは、レビューで人が意味を判断する材料にするためです。

このスクリプトは単純な正規表現なので、配列の添字のように['...']で囲む文字列も拾います。実際のプロジェクトでは、誤検出が出たら対象パターンを絞り込んでください。

検証の流れは、テストコマンドを回させる運用と同じ型です。Vitestで同じことをする例はClaude CodeでVitestを回すに、VBAで規約をCLAUDE.mdに寄せる例はClaude CodeでExcel VBAマクロの書き方にあります。

つまずきやすい点

落とし穴

動作テスト環境と本番でフィールドコードが違う

動作テスト環境のアプリは、運用環境へ反映するまで設定が別です。フィールドを追加・変更した直後は、preview付きのURLで取得した表と、本番の表がずれます。コード表には「どちらの環境から取得したか」をファイル名か先頭行に書いておきます。

  • ハンドラーは上書きされず、追加される。同じイベントタイプに登録したハンドラーは足されていくだけで、前のものは消えません。Claudeに修正を頼んだ結果、古い登録と新しい登録の両方が残る形になっていないか確認します。置き換えたいときはkintone.events.offで外してから登録し直します。
  • return eventを忘れると書き換えが反映されない。値の書き換えや編集不可の設定は、イベントオブジェクトを返して初めて反映されます。CLAUDE.mdに「変更したら必ずreturn event」と書いたのはこのためです。
  • エラーを設定すると書き換えが取り消される。フィールドや画面上部にエラーを設定したイベントでは、同じハンドラーで行ったフィールド値の書き換えと編集可否の設定がキャンセルされます。バリデーションと自動補完を同じハンドラーに混ぜない設計が安全です。
  • 変更イベントが発火する条件はフィールドの種類で違う。文字列1行や数値は、コントロールからフォーカスが外れたときに発生します。ルックアップで値がコピーされたときも発生します。入力のたびに反応させたいという指示は、このイベントでは満たせません。
  • 保存前イベントでは添付ファイルの情報が取れない。 create.submitのイベントオブジェクトでは、添付ファイルフィールドの情報を取得できません。添付の有無で保存を止める検証は、このイベントでは組めません。
  • モバイルは別登録。PC用の登録がモバイルに効くわけではありません。両方を対象にするなら、配列で複数のタイプを渡します。

CLAUDE.mdが大きくなってきたら

アプリが増えると、コード表をCLAUDE.mdからimportし続けるのは苦しくなります。CLAUDE.mdは200行未満が目安で、importしても読み込み量は減りません。

サブディレクトリに置いたCLAUDE.mdや、オンデマンドで読み込まれるrulesには、1ファイルあたりの上限もあります。200Kコンテキストのセッションでは40,000文字で、超えた分はClaudeが自分でReadしない限り文脈に入りません。大きなアプリの表をrulesに直書きするときは注意が必要です。上限と確認方法はClaude CodeでCLAUDE.mdが途中までしか読まれない上限と確認法で扱っています。

ルールが効いているかは、セッション中に/contextで確認できます。サブディレクトリのファイルは起動時には読み込まれず、該当ファイルに触れた時点で読み込まれる点も押さえておきます。

まとめ

kintoneカスタマイズで繰り返し起きる取り違えは、リポジトリの外にあるフィールドコードに由来します。APIから生成した表をimportし、イベントの約束をrulesに分け、書き終えたら検証スクリプトに通す。この3点を揃えると、Claude Codeの出力は「コードが存在するか」を機械で確かめられます。残る課題は、存在するコードを意味どおりに使っているかで、ここは差分レビューで人が見る部分です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/memory
  2. cybozu.dev/ja/kintone/docs/js-api/events/event-handling
  3. cybozu.dev/ja/kintone/docs/rest-api/apps/form/get-form-fields
  4. cybozu.dev/ja/kintone/docs/js-api/events/create/create-change-event
この記事を共有:XはてブLinkedIn