fieldsmith 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +499 -7
  2. package/dist/cli/index.js +238 -14
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/schema.js +43 -3
  5. package/dist/cli/schema.js.map +1 -1
  6. package/dist/config.js +36 -3
  7. package/dist/config.js.map +1 -1
  8. package/dist/csv.js +119 -0
  9. package/dist/csv.js.map +1 -0
  10. package/dist/icon/name.js +48 -0
  11. package/dist/icon/name.js.map +1 -0
  12. package/dist/icon/render.js +0 -21
  13. package/dist/icon/render.js.map +1 -1
  14. package/dist/kintone/client.js +6 -1
  15. package/dist/kintone/client.js.map +1 -1
  16. package/dist/kintone/deploy.js +90 -13
  17. package/dist/kintone/deploy.js.map +1 -1
  18. package/dist/kintone/link.js +408 -0
  19. package/dist/kintone/link.js.map +1 -0
  20. package/dist/llm/generate.js +20 -4
  21. package/dist/llm/generate.js.map +1 -1
  22. package/dist/llm/prompt.js +3 -1
  23. package/dist/llm/prompt.js.map +1 -1
  24. package/dist/spec/appSpec.js +189 -3
  25. package/dist/spec/appSpec.js.map +1 -1
  26. package/dist/spec/checkRecords.js +191 -0
  27. package/dist/spec/checkRecords.js.map +1 -0
  28. package/dist/spec/describeForm.js +65 -0
  29. package/dist/spec/describeForm.js.map +1 -0
  30. package/dist/spec/diff.js +133 -6
  31. package/dist/spec/diff.js.map +1 -1
  32. package/dist/spec/fieldSpec.js +60 -8
  33. package/dist/spec/fieldSpec.js.map +1 -1
  34. package/dist/spec/fromKintone.js +127 -15
  35. package/dist/spec/fromKintone.js.map +1 -1
  36. package/dist/spec/layout.js +333 -30
  37. package/dist/spec/layout.js.map +1 -1
  38. package/dist/spec/linkSpec.js +0 -0
  39. package/dist/spec/linkSpec.js.map +1 -0
  40. package/dist/spec/toKintone.js +38 -3
  41. package/dist/spec/toKintone.js.map +1 -1
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -32,6 +32,7 @@ fieldsmith plan -f requirements.md -o bookshelf.json
32
32
  | コマンド | kintone | Claude |
33
33
  |---|---|---|
34
34
  | `deploy` `status` | 触る | **呼ばない** |
35
+ | `check` | **触らない** | 呼ばない |
35
36
  | `plan` | 触らない | 呼ぶ |
36
37
  | `create` | 触る | 呼ぶ(= `plan` + `deploy`) |
37
38
 
@@ -57,6 +58,38 @@ fieldsmith plan -f requirements.md -o bookshelf.json
57
58
  これは「テンプレートから量産する」用途に向く。
58
59
  既存アプリに変更を反映したい場合は `update` を使う。
59
60
 
61
+ ## 先達について
62
+
63
+ このツールの発想は、**[ginue](https://github.com/goqoo-on-kintone/ginue)**([goqoo-on-kintone](https://github.com/goqoo-on-kintone)、MIT)に多くを負っている。
64
+
65
+ ginue は kintone のアプリ設定を JSON として取り出し、Git のワークフローで扱う CLI。
66
+ fieldsmith が受け継いだのは、道具立てそのものと言っていい。
67
+
68
+ | | ginue | fieldsmith |
69
+ |---|---|---|
70
+ | 設定を取り出す | `pull` | `pull` |
71
+ | 動作テスト環境へ反映 | `push` | `update` |
72
+ | 運用環境へ反映 | `deploy` | `update --deploy` |
73
+ | 送信前に確認 | `--dry-run` | `--dry-run` |
74
+
75
+ **「アプリ設定はファイルにして版管理できる」「動作テスト環境で止めて人が確認してから本番へ出す」**
76
+ という考え方は、ginue が先に示したものをそのまま踏襲している。
77
+ fieldsmith の `update` が既定で運用環境に出ないのも、この 2 段階を守るためで、
78
+ ginue の `push` と `deploy` の分け方に倣った。
79
+
80
+ 違いは抽象度にある。ginue は kintone の設定 JSON をそのまま扱い、
81
+ 多くの設定を高い忠実度で往復させる。fieldsmith は `AppSpec` という一段抽象化した形を挟み、
82
+ 表現できる範囲を狭める代わりに、人にも LLM にも書きやすくしている。
83
+ テーブルやルックアップを扱えないのはそのためで、**忠実に環境を移したいなら ginue のほうが適している。**
84
+
85
+ ## サンプル
86
+
87
+ そのまま `deploy` できる AppSpec の実例が [examples/](examples/) にある。
88
+
89
+ ```bash
90
+ npx fieldsmith deploy examples/案件管理.json --dry-run
91
+ ```
92
+
60
93
  ## お試し手順
61
94
 
62
95
  全機能を一通り動かす手順書がある。副作用の小さい順(接続なし → kintone → Claude API)に
@@ -100,6 +133,9 @@ npm run fieldsmith -- create -f requirements.md
100
133
  | `revise <appId> [指示]` | 指示に沿って書き換えた AppSpec を作る(要 Claude API)|
101
134
  | `status <appId>` | 運用環境への反映状況を確認する |
102
135
  | `login` / `logout` | kintone の OAuth トークンの取得・破棄 |
136
+ | `link <file.json>` | アプリ間の結線(ルックアップ・関連レコード一覧)を反映する |
137
+ | `layout <appId>` | フォームの構造を表示する(読み取りのみ)|
138
+ | `check <spec.json> <data.csv>` | 投入するデータが AppSpec に収まるか確かめる(接続しない)|
103
139
  | `schema` | AppSpec の書き方を出力する(AI エージェント向け)|
104
140
  | `plan [prompt]` | 要件から AppSpec を生成する。`-o` で保存 |
105
141
  | `create [prompt]` | 生成 → 確認 → デプロイ |
@@ -118,7 +154,7 @@ cp .env.example .env # 値を埋める
118
154
 
119
155
  | 用途 | 設定するもの |
120
156
  |---|---|
121
- | `deploy` `pull` `diff` `update` `status` | kintone の認証情報(下記) |
157
+ | `deploy` `pull` `diff` `update` `status` `layout` | kintone の認証情報(下記) |
122
158
  | `plan` `create` `revise` | 上記 + `ANTHROPIC_API_KEY` または `ant auth login` |
123
159
 
124
160
  ### kintone の認証は 2 通りから選ぶ
@@ -154,6 +190,22 @@ KINTONE_OAUTH_TOKEN_ENDPOINT=...
154
190
  両方あるときはパスワード認証を使う。`KINTONE_AUTH=oauth` で明示的に選べる。
155
191
  いま何で繋いでいるかは `--verbose` で分かる。
156
192
 
193
+ #### どこに置くか
194
+
195
+ 次の順で探し、**最初に見つかった 1 つだけ**を読む。重ねて読むと、どの値が
196
+ どこから来たのか追えなくなるため。
197
+
198
+ | 順 | 場所 | 使いどころ |
199
+ |---|---|---|
200
+ | 1 | `$FIELDSMITH_ENV` | どれを読むかを完全に決めたいとき |
201
+ | 2 | `./.env` | リポジトリ直下で作業しているとき |
202
+ | 3 | `~/.config/fieldsmith/default.env` | **どのディレクトリから叩いても効かせたいとき** |
203
+
204
+ 3 番目があると、案件のディレクトリから `fieldsmith deploy spec.json` がそのまま通る。
205
+ どのファイルを読んだかは `--verbose` で出る。
206
+
207
+ 既にある環境変数は上書きしない。CI で注入した値のほうが優先される。
208
+
157
209
  いずれの場合も、実行するユーザーに **「アプリの作成」権限**が必要。
158
210
 
159
211
  ### kintone 側の準備
@@ -219,6 +271,310 @@ npm run fieldsmith -- login
219
271
  **kintone に 1 リクエストも投げる前に**理由付きで弾く。
220
272
  - 一覧や `titleFieldCode` から参照するフィールドコードの存在も、事前に検証する。
221
273
 
274
+ ### ルックアップを対象外にしている理由
275
+
276
+ `LOOKUP` と `REFERENCE_TABLE` を「v1 では未対応」としているのは、
277
+ **この 2 つが他のアプリに依存する**ため。
278
+
279
+ グループとテーブルも当初は同じ並びだったが、どちらもアプリ内で完結する構造物なので、
280
+ [`layout: "sections"`](#グループフィールド-sections) と [`table`](#テーブル) で対応した。
281
+ ルックアップと関連レコード一覧は、そこが違う。
282
+
283
+ **1. AppSpec を環境非依存に保つため(主たる理由)**
284
+
285
+ ルックアップの設定には**相手アプリの指定**が要る。これを spec に書くと、その AppSpec は
286
+ 特定の kintone 環境に縛られる。`space` を `examples/` の spec に持たせていないのと
287
+ 同じ理由で、「同じ spec を別の環境・別のスペースにもう 1 つ」ができなくなるのは困る。
288
+
289
+ > 逃げ道が無いわけではない。`relatedApp` はアプリ ID (`app`) のほかに
290
+ > **アプリコード** (`code`) でも書けて、両方指定すると `code` が勝つ。
291
+ > ただしそれは「相手アプリがどの環境でも同じアプリコードで存在している」ことが前提で、
292
+ > fieldsmith はその前提を**作ることも確かめることもできない**。
293
+
294
+ アプリ間の参照は **AppSpec の外に置き、kintone 上で人が繋ぐ**という前提にしている。
295
+ `examples/` がその形になっている。
296
+
297
+ | ファイル | どう扱っているか |
298
+ |---|---|
299
+ | `本棚.json` | 「蔵書アプリはこのアプリの棚 ID をキーにルックアップで引く」と `description` に**文章で**書く |
300
+ | `蔵書.json` / `蔵書管理.json` | `本棚ID` を**ただの文字列フィールド**として持つ |
301
+
302
+ **2. `deploy` の決定性が保てないため**
303
+
304
+ `deploy` は「AppSpec 1 つ → 新しいアプリ 1 つ」を毎回同じ結果で作る操作。ルックアップを
305
+ 認めると、相手アプリが先に存在している必要があり、アプリ間の依存解決と作成順序という
306
+ 別の問題が入り込む。
307
+
308
+ さらに `--dry-run` は kintone に接続しないので、**相手アプリやキーフィールドの存在を
309
+ 検証できない**。「検証は通ったのにデプロイの途中で落ちる」型の失敗が増える。
310
+
311
+ ### 関連レコード一覧を対象外にしている理由
312
+
313
+ `REFERENCE_TABLE` には、ルックアップと重なる理由と、この型だけの事情がある。
314
+
315
+ **1. 相手アプリに依存する(ルックアップと共通)**
316
+
317
+ `referenceTable` にも参照先アプリの指定が要る。上のアプリコードの話もそのまま当てはまる。
318
+
319
+ **2. `--dry-run` が意味を成さない**
320
+
321
+ `condition.relatedField` と `displayFields` は、**相手アプリの**フィールドコード。
322
+ どちらも `referenceTable` を書くなら必須。`--dry-run` は kintone に接続しないので、
323
+ これらが実在するかを確かめられない。
324
+
325
+ fieldsmith の検証は「**kintone に 1 リクエストも投げる前に**弾く」ことを拠り所にしている。
326
+ 確かめようのない項目を通すと、その拠り所が崩れる。
327
+
328
+ **3. データを持たないので、外すコストが低い(この型だけの事情)**
329
+
330
+ 関連レコード一覧は**表示だけの装置**で、レコードに値を持たない。
331
+
332
+ > 関連レコード一覧 | REFERENCE_TABLE | 値の取得はできません。
333
+ > 関連レコード一覧 | REFERENCE_TABLE | 値の登録または更新はできません。
334
+ >
335
+ > — [フィールド形式](https://cybozu.dev/ja/kintone/docs/overview/field-types/)
336
+
337
+ だから AppSpec から外しても、**レコードのデータは 1 件も失われない**。デプロイした後に
338
+ kintone の画面で足しても、既にあるレコードには何の影響もない。
339
+
340
+ 値を持つルックアップとは、ここが違う。ルックアップの実体は `SINGLE_LINE_TEXT` /
341
+ `NUMBER` / `LINK` に設定を付けたもので、フィールドそのものにはデータが入っている。
342
+
343
+ ### `pull` はどちらも黙って捨てない
344
+
345
+ | | `pull` の扱い |
346
+ |---|---|
347
+ | 関連レコード一覧 | フィールドごと落として警告に出す(値を持たないので失われるデータは無い)|
348
+ | ルックアップ | **フィールドは残し**、ルックアップ設定を落としたことを警告に出す |
349
+
350
+ ルックアップを残すのは、そこに**データが入っているから**。落とすと `update` で
351
+ 削除候補に送られてしまう。残った形は `examples/蔵書.json` と同じ「`本棚ID` を
352
+ ただの文字列フィールドとして持つ」状態になる。
353
+
354
+ その spec を `update` で送り返しても、kintone 側のルックアップ設定は消えない。
355
+ フィールド設定変更 API は「[省略したパラメーターの設定は変更されません](https://cybozu.dev/ja/kintone/docs/rest-api/apps/form/update-form-fields/)」ため。
356
+
357
+ ## テーブル
358
+
359
+ 同じ `table` を書いたフィールドが、kintone のテーブルの列になる。
360
+
361
+ ```jsonc
362
+ {
363
+ "fields": [
364
+ { "type": "SINGLE_LINE_TEXT", "label": "件名" },
365
+ { "type": "SINGLE_LINE_TEXT", "label": "品名", "table": "明細" },
366
+ { "type": "NUMBER", "label": "単価", "table": "明細", "unit": "円" },
367
+ { "type": "NUMBER", "label": "数量", "table": "明細" }
368
+ ]
369
+ }
370
+ ```
371
+
372
+ ```
373
+ 件名
374
+ ▦ 明細 (テーブル)
375
+ 品名 | 単価 | 数量
376
+ ```
377
+
378
+ **入れ子では書かない。** `group` がセクションを決めるのと同じく、フィールドに付けた印が
379
+ テーブルを決める。こうしている理由は 2 つある。
380
+
381
+ - AppSpec が平らなままなら、差分・検証・一覧の扱いが今までと変わらない
382
+ - `plan` の構造化出力はプロパティ数の上限が厳しく、入れ子を足す余裕が無い
383
+
384
+ ### 決まり
385
+
386
+ - **同じ `table` は続けて並べる。** 離れていると 2 つのテーブルに割れるので検証で弾く
387
+ - `table` と `group` は**同時に指定できない**。kintone はテーブルの中にグループを置けない
388
+ - `table` の名前がテーブルのフィールドコードになる。フィールドコードと重ならない名前にする
389
+ - テーブルの列は**一覧 (`views`) に指定できない**。1 レコードに複数の値を持つため
390
+
391
+ ### `update` はテーブルを変更しない
392
+
393
+ `deploy` はテーブルを作れるが、**`update` は既存のテーブルに触れない。**
394
+ 列を足す・外す・出し入れしようとすると、1 リクエストも投げずに終了コード 2 で止まる。
395
+
396
+ ```
397
+ ! 数量: テーブル「明細」の列を追加 (update では反映できない)
398
+ ```
399
+
400
+ kintone のテーブルには**列の退避先が無い**。グループは `layout` に行を入れ子で持てるが、
401
+ テーブルは `fields` の平らな配列で、行も入れ子も持てない。
402
+
403
+ ```
404
+ GROUP → "layout": [ ROW, ROW, ... ]
405
+ SUBTABLE → "fields": [ field, field ]
406
+ ```
407
+
408
+ 列を外へ出すのは親を変えることで、kintone では作り直しになり**データが失われる**。
409
+ fieldsmith の「消さずに削除候補へ移す」が成り立たないので、中途半端に適用せず止める。
410
+
411
+ テーブルを変えたいときは kintone の画面で変更し、そのあと `pull` で spec を取り直す。
412
+
413
+ `plan` / `create` / `revise` はテーブルを作らない(構造化出力の上限のため)。
414
+ `revise` にかけても、元の spec のテーブルは**列の所属ごと引き継ぐ**。
415
+
416
+ ## アプリ間の結線
417
+
418
+ ルックアップと関連レコード一覧は AppSpec に入れていない([理由](#ルックアップを対象外にしている理由))。
419
+ かと言って手で繋ぐしかないのも困るので、**環境に依存する部分だけを別のファイルに閉じ込める**。
420
+
421
+ ```jsonc
422
+ {
423
+ "links": [
424
+ {
425
+ "type": "LOOKUP",
426
+ "app": "蔵書", // 文字列 = アプリコード / 数値 = アプリ ID
427
+ "code": "本棚ID", // ← AppSpec が作ったフィールド
428
+ "relatedApp": "本棚",
429
+ "relatedKeyField": "棚ID",
430
+ "fieldMappings": [{ "field": "棚名", "relatedField": "棚名" }]
431
+ },
432
+ {
433
+ "type": "REFERENCE_TABLE",
434
+ "app": "蔵書",
435
+ "code": "同じ棚の本", // ← link が作るフィールド
436
+ "label": "同じ棚の本",
437
+ "relatedApp": "本棚",
438
+ "condition": { "field": "本棚ID", "relatedField": "棚ID" },
439
+ "displayFields": ["棚ID", "棚名"],
440
+ "placeAfter": "本棚ID"
441
+ }
442
+ ]
443
+ }
444
+ ```
445
+
446
+ ```bash
447
+ fieldsmith link links.json --dry-run # 何が起きるか(kintone を変更しない)
448
+ fieldsmith link links.json # 動作テスト環境まで
449
+ fieldsmith link links.json --deploy # 運用環境へ
450
+ ```
451
+
452
+ ### ファイルは環境に依存しない
453
+
454
+ **アプリを文字列で書くとアプリコードとして解決する。** 数値ならアプリ ID。
455
+ アプリコードで書いておけば、**同じファイルが開発環境でも本番でも効く**。
456
+ 接続先は他のコマンドと同じく `.env` から来るので、ファイルには書かない。
457
+
458
+ ### 作るものと作らないもの
459
+
460
+ | | フィールドを作るのは | なぜ |
461
+ |---|---|---|
462
+ | `LOOKUP` | **AppSpec** | 実体は `SINGLE_LINE_TEXT` / `NUMBER` / `LINK` で、**値が入る**。データを持つものは AppSpec が持つ |
463
+ | `REFERENCE_TABLE` | **link** | 値を持たない。AppSpec に無くても失われるデータが無い |
464
+
465
+ ルックアップにするフィールドが AppSpec に無ければ、`link` はそう言って止まる。
466
+
467
+ > 既にデータが入っているフィールドにも後から結線できる。実機で確かめてある
468
+ > (値は消えず、コピーは編集画面で取得したときだけ走る)。
469
+
470
+ ### 送るのは書いた項目だけ
471
+
472
+ **kintone は書かなかった項目を勝手に埋める。** `sort` に `レコード番号 desc`、
473
+ `size` に `5` が入る。これを差分とみなすと、中身が同じなのに毎回「変わった」と
474
+ 言い続けることになるので、**書いた項目だけを比べる**。AppSpec の
475
+ 「書かれていない項目は現状維持」と同じ扱い。
476
+
477
+ だから何度流しても結果は同じになる。
478
+
479
+ ### `--dry-run` は読み取りに接続する
480
+
481
+ `deploy --dry-run` は kintone に一切繋がないが、`link --dry-run` は**読み取りだけ繋ぐ**。
482
+ 相手アプリのフィールドが実在するかは、繋がないと確かめようがないため。
483
+
484
+ 送る前に見るもの。
485
+
486
+ | | どこを読む |
487
+ |---|---|
488
+ | `app` / `relatedApp` が解決できるか | アプリ一覧 |
489
+ | `condition.field` `fieldMappings[].field` `placeAfter` | そのアプリのフォーム |
490
+ | `condition.relatedField` `displayFields` `lookupPickerFields` | **相手アプリ**のフォーム |
491
+ | ルックアップの対象が `SINGLE_LINE_TEXT` / `NUMBER` / `LINK` か | そのアプリのフォーム |
492
+ | キーに**重複禁止**が設定されているか | 相手アプリのフォーム |
493
+
494
+ 最後のは kintone の決まりで、ここで弾かないと反映の途中で落ちる。
495
+
496
+ ### 消さない
497
+
498
+ ファイルに無い結線が kintone 側にあっても**削除しない**。警告で知らせるだけ。
499
+ フィールドを消さない方針と揃えている。
500
+
501
+ ## フォームの構造を読む
502
+
503
+ `pull` は AppSpec を吐くので、**どれが同じ行に並んでいるか**が見えない。
504
+ 「このフィールドの近くに何かを置きたい」ときに要るのは、定義ではなく並び。
505
+
506
+ ```bash
507
+ fieldsmith layout 772
508
+ ```
509
+
510
+ ```
511
+ アプリ 772「在庫明細」 5 行
512
+
513
+ 1: 年月日 (DATE) | 倉庫コード (SINGLE_LINE_TEXT) | 倉庫名 (SINGLE_LINE_TEXT)
514
+ 2: 倉庫情報 (REFERENCE_TABLE) → アプリ 774
515
+ 3: <LABEL>
516
+ 4: ▦ 明細 (テーブル)
517
+ 品名 (SINGLE_LINE_TEXT) | 数量 (NUMBER)
518
+ 5: ▼ _削除候補 (グループ)
519
+ 入数 (NUMBER)
520
+ ```
521
+
522
+ **AppSpec が扱えないものこそ、見えることに価値がある。** 関連レコード一覧は
523
+ 参照先のアプリを併記し、ラベルや罫線も `<LABEL>` として出す。
524
+ `_削除候補` の中身が見えると、型を変えた名残にも気づける。
525
+
526
+ 既定は運用環境。`--preview` を付けると動作テスト環境を見る(反映後の答え合わせに要る)。
527
+
528
+ ## 投入するデータを先に確かめる
529
+
530
+ `deploy --dry-run` が「その spec は正しいか」を接続せずに見るのに対して、
531
+ `check` は「**そのデータはこの spec に入るか**」を見る。
532
+
533
+ ```bash
534
+ fieldsmith check 取引先マスタ.json records.csv
535
+ ```
536
+
537
+ ```
538
+ 3,927 件 / 63 列 を 取引先マスタ.json と突き合わせます
539
+
540
+ ✗ 締日: maxValue 31 を超える値があります (260 件)
541
+ 3421 行目 (取引先コード=51234001001) = "90"
542
+ 3428 行目 (取引先コード=51234002001) = "90"
543
+ ✗ 郵便番号: maxLength 8 を超える値があります (最長 9 文字) (5 件)
544
+ 1122 行目 (取引先コード=51234099001) = "x305-0056"
545
+ ⚠ メールアドレス: spec にありますが CSV に列がありません (空で登録されます)
546
+ ⚠ 与信限度額: CSV にありますが spec にありません (無視されます)
547
+
548
+ 2 件のエラー / 2 件の警告
549
+ ```
550
+
551
+ **kintone のレコード追加は 100 件のかたまり単位で失敗する。** 1 件の違反で
552
+ そのかたまりが丸ごと落ちるので、6 件の不正データが 600 件の取りこぼしになる。
553
+ 投入してから気づくと被害が桁で増えるため、先に突き合わせる。
554
+
555
+ `deploy` の前に流すと、型や制約の決め方にも跳ね返る
556
+ (「締日」に `maxValue 31` を付けてよいのか、そもそも数値なのか)。
557
+
558
+ ### 見るもの
559
+
560
+ | | 内容 |
561
+ |---|---|
562
+ | 必須 | `required` の列が空 |
563
+ | 重複 | `unique` の列に同じ値 |
564
+ | 選択肢 | `options` にない値 |
565
+ | 数値 | 数値として読めない / `minValue` `maxValue` の外 |
566
+ | 文字数 | `minLength` `maxLength` の外 |
567
+ | 日付 | `DATE` `TIME` `DATETIME` として読めない |
568
+ | 列の過不足 | spec にあって CSV に無い、その逆 |
569
+
570
+ CSV は **cli-kintone と同じ形式**を読む。見出しはフィールドコード、複数値は
571
+ セル内の改行区切り、テーブルは `*` 列でレコードの先頭行を示す。
572
+ 同じファイルをそのまま `cli-kintone record import` に渡せる。
573
+
574
+ - kintone にも Claude にも接続しない。認証情報が要らない
575
+ - 違反があれば終了コード 2。`--json` で `issues[]` を機械可読に出す
576
+ - 判定に迷う値は通す。**取りこぼすより誤検出のほうが困る**ため
577
+
222
578
  ## スペースへの配置
223
579
 
224
580
  アプリを特定のスペースに作れる。CLI の `--space` が AppSpec の `space` より優先される。
@@ -258,10 +614,37 @@ kintone がスレッドの指定を求めた場合は `--thread` を使う。ス
258
614
  {
259
615
  "layout": "grouped", // 既定。省略してもこの動作
260
616
  "layout": { "mode": "grouped", "maxPerRow": 2 }, // 1 行あたりの上限を変える (既定 3)
617
+ "layout": "sections", // group をグループフィールドにする
261
618
  "layout": "stacked" // レイアウトに触らない (1 行 1 フィールド)
262
619
  }
263
620
  ```
264
621
 
622
+ ### グループフィールド (`sections`)
623
+
624
+ `grouped` の `group` は横並びのヒントで、kintone 上には現れない。
625
+ `sections` にすると、同じ `group` のフィールドが **kintone のグループフィールド**に入る。
626
+
627
+ ```jsonc
628
+ {
629
+ "layout": "sections",
630
+ "fields": [
631
+ { "type": "SINGLE_LINE_TEXT", "label": "書名", "group": "書誌情報" },
632
+ { "type": "SINGLE_LINE_TEXT", "label": "著者", "group": "書誌情報" },
633
+ { "type": "DATE", "label": "貸出日", "group": "貸出" }
634
+ ]
635
+ }
636
+ ```
637
+
638
+ 書き方は `grouped` と同じ。`group` の付いていないフィールドは、これまで通り素の行になる。
639
+
640
+ - **`group` の名前がグループのフィールドコードになる。** ラベルからコードを導くのと
641
+ 同じ規則を使うので、同じ名前からは毎回同じコードが出る。更新を繰り返しても
642
+ グループは増えない
643
+ - そのためフィールドコードと重なる名前は使えない(検証で弾かれる)
644
+ - グループは開いた状態で作られる(畳んだ状態にするのは `_削除候補` だけ)
645
+ - **AppSpec から消えたグループは、空のまま残る。** フィールドを削除しないのと同じ理由で、
646
+ グループも消さない。不要なら kintone の画面で削除する(`update` が警告で知らせる)
647
+
265
648
  ### 並べ方の規則
266
649
 
267
650
  **並び順は変えない。** AppSpec に書いた順序のまま、隣り合った同じ系統のフィールドを
@@ -344,7 +727,7 @@ memo
344
727
 
345
728
  1. 動作テスト環境にアプリを作成
346
729
  2. フィールドを追加(100 件ずつ分割)
347
- 3. フォームの並びを整える(`layout` が `grouped` の場合のみ。既定で有効)
730
+ 3. フォームの並びを整える(`layout` が `grouped` / `sections` の場合のみ。既定で有効)
348
731
  4. アイコン画像を生成してアップロード(`icon` の指定がある場合のみ)
349
732
  5. 一般設定を変更(アイコンまたは他の設定がある場合のみ)
350
733
  6. 一覧を変更(指定がある場合のみ)
@@ -386,11 +769,42 @@ AppSpec は kintone の設定をすべて表現できるわけではない。落
386
769
  現状で落ちるもの: テーブル・ルックアップ・関連レコード一覧などの未対応フィールド型、
387
770
  カスタマイズ一覧、アプリアイコンの画像、細かなレイアウト。
388
771
 
772
+ ### アイコンは往復する
773
+
774
+ kintone はアプリアイコンを**画像**として持つので、画像そのものからは
775
+ 「🏢 を描いた」ことも「背景色に何を使った」ことも読み取れない。
776
+
777
+ そこで**アップロードするファイル名に書いておく**。
778
+
779
+ ```
780
+ fieldsmith-icon-🏢-2563eb.png
781
+ ```
782
+
783
+ `getAppSettings` はこのファイル名を返すので、`pull` が `icon` と `iconBackground` に
784
+ 戻せる。だから `pull` → 絵文字を書き換える → `update` でアイコンを変えられる。
785
+
786
+ 人が kintone の画面で差し替えた画像はこの形にならない。その場合は従来どおり
787
+ 「AppSpec には含められません」と警告する。
788
+
789
+ > アイコンは**変わったときだけ**上げ直す。毎回上げると、更新のたびに
790
+ > 画像ファイルが増えていく。
791
+
389
792
  ### レイアウトについて
390
793
 
391
- `pull` は `"layout": "stacked"` を付けて返す。AppSpec は任意のレイアウトを
392
- 表現できないので、**既存の並びに手を触れない**という指定にしてある。
393
- 横並びにしたい場合は `"layout": "grouped"` に書き換える。
794
+ `pull` は**読み取れたものだけを名乗る**。
795
+
796
+ | フォームの状態 | `pull` が返す `layout` |
797
+ |---|---|
798
+ | グループフィールドがある | `"sections"`(`group` に所属も復元する)|
799
+ | それ以外 | `"stacked"`(既存の並びに手を触れない)|
800
+
801
+ 横並びは読み取れない。1 行に 3 つ並んでいても、fieldsmith が並べたのか人が画面で
802
+ 並べたのか区別が付かないので、`stacked` と言う。横に並べたい場合は
803
+ `"layout": "grouped"` に書き換える。
804
+
805
+ グループは読み取れる。レイアウトに `GROUP` があれば、それは疑いようもなくセクションなので、
806
+ `sections` と名乗って往復できるようにしてある。**`pull` した spec をそのまま `update` しても
807
+ レイアウトは書き換わらない**(両方が同じ指定・同じ並びなので差分が出ない)。
394
808
 
395
809
  フィールドの順序は、フォームのレイアウト上の位置から復元する
396
810
  (`getFormFields` はオブジェクトを返すので順序が保証されない)。
@@ -424,15 +838,42 @@ fieldsmith diff 761 target.json
424
838
  **順序が原因で追加や削除候補になることはない。** 対応付けはフィールドコードで行うので、
425
839
  並べ替えただけなら誤判定しない。
426
840
 
427
- ただし**並べ替えそのものは差分として拾う**(`layout` が `grouped` のとき)。
841
+ ただし**並べ替えそのものは差分として拾う**(`layout` が `grouped` / `sections` のとき)。
428
842
 
429
843
  ```
430
844
  ~ フォームの並びを組み直す (並び順)
431
845
  ```
432
846
 
433
- `layout` が `stacked`(`pull` の既定)の場合は「既存の並びに手を触れない」指定なので、
847
+ `layout` が `stacked` の場合は「既存の並びに手を触れない」指定なので、
434
848
  並べ替えても何も起きない。差分にも出さない。並びを変えたいときは `"layout": "grouped"` にする。
435
849
 
850
+ `sections` では、フィールドが増減したときも組み直す。レイアウトを書かないと、
851
+ 足したフィールドが kintone によってフォーム末尾に置かれ、**所属するはずのグループの外**に
852
+ 出てしまうため。
853
+
854
+ ```
855
+ ~ フォームの並びを組み直す (フィールドの増減)
856
+ ```
857
+
858
+ ### AppSpec に現れないものは、レイアウト上で保たれる
859
+
860
+ フォームには AppSpec が扱わないものが載っている。
861
+
862
+ | | AppSpec | レイアウト |
863
+ |---|---|---|
864
+ | 関連レコード一覧・ルックアップ設定 | 表現しない | 載っている |
865
+ | ラベル・スペース・罫線 | 表現しない | 載っている(`code` ではなく `elementId` を持つ)|
866
+
867
+ `update` が並びを組み直すのは AppSpec に書かれたフィールドだけで、**それ以外は
868
+ 直前のフィールドの位置に戻す**。組み直しは AppSpec を起点にするので、
869
+ 何もしないと消えてしまうため。
870
+
871
+ フィールドを落とすとレイアウト変更 API が「指定が足りない」で失敗し、
872
+ 飾りは API が求めないぶん黙って消える。どちらも起こさない。
873
+
874
+ > 手掛かりにしていたフィールドごと削除候補へ移った場合だけ、位置を保てずに末尾へ回る。
875
+ > それでも捨てはしない。
876
+
436
877
  ### 同一性はフィールドコードで決まる
437
878
 
438
879
  ラベルだけ変えたなら「同じフィールドの変更」、コードが変わったら「別フィールドの追加 + 元は削除候補」。
@@ -505,6 +946,35 @@ kintone の設定変更 API は、**省略した項目を変更しない**。fie
505
946
  差分にも出さない。出すと「実際には何も起きないのに差分が永遠に消えない」ことになる。
506
947
  値を変えたいときは明示的に書く。
507
948
 
949
+ #### キーを消しても、制約は外れない
950
+
951
+ ここがいちばんハマる。**キーを消す=制約を外す**と読みたくなるが、そうはならない。
952
+
953
+ ```jsonc
954
+ // 現状: maxValue 31 が設定されている
955
+ { "type": "NUMBER", "code": "締日", "minValue": 0 } // → 31 のまま
956
+ { "type": "NUMBER", "code": "締日", "minValue": 0, "maxValue": "" } // → 外れる
957
+ ```
958
+
959
+ **外すときは空文字を書く。** kintone は未設定の上限・下限・初期値を空文字で表すので、
960
+ 空文字がそのまま「制限なし」を意味する。真偽値なら `false` を書く。
961
+
962
+ 黙って据え置くと「差分なし=変わっていない」と読めてしまうので、
963
+ `diff` と `update` が**外し方つきで知らせる**。
964
+
965
+ ```
966
+ 注意:
967
+ ⚠ 締日: maxValue を spec から外しましたが、kintone 側の "31" は据え置かれます
968
+ (外すには "maxValue": "" と書きます)。
969
+ ```
970
+
971
+ 外し方を示せるものだけが対象。`theme` のように「空にする」書き方が無いものは黙って見送る。
972
+
973
+ #### 数値は文字列でも数値でも書ける
974
+
975
+ kintone は上限・下限・初期値を**文字列で返す**(`"31"`)。spec にはどちらでも書けて、
976
+ `0` と `"0"` は同じものとして扱う。書き方の違いが差分に出ることはない。
977
+
508
978
  ### 何度流しても同じ結果になる
509
979
 
510
980
  `update` は運用環境ではなく**動作テスト環境**を読んで差分を取る。
@@ -565,6 +1035,23 @@ fieldsmith revise 761 "受注確度を S/A/B/C の4段階にして、失注理
565
1035
 
566
1036
  ## AI エージェントから使う
567
1037
 
1038
+ Claude Code から使う場合の設定と注意点は **[docs/claude-code.md](docs/claude-code.md)** にまとめてある
1039
+ (`CLAUDE.md` と `.claude/settings.json` の雛形つき)。
1040
+
1041
+ 手順そのものをエージェントに持たせるなら **[skills/kintone-appspec](skills/kintone-appspec)** を使う。
1042
+ 新規作成(`deploy`)と既存アプリの更新(`pull` → `diff` → `update`)の進め方、確認を取る
1043
+ タイミング、よくある失敗の直し方が入っている。fieldsmith 自体のインストールは不要
1044
+ (`npx -y fieldsmith` に落ちる)。
1045
+
1046
+ このリポジトリは Claude Code のプラグイン marketplace を兼ねているので、2 コマンドで入る。
1047
+
1048
+ ```bash
1049
+ claude plugin marketplace add motohasystem/fieldsmith
1050
+ claude plugin install kintone-fieldsmith@fieldsmith
1051
+ ```
1052
+
1053
+ ファイルをコピーして使ってもよい。詳細は [skills/README.md](skills/README.md)。
1054
+
568
1055
  エージェントの仕事は **AppSpec を書くこと**で、デプロイはシェルコマンド 1 つ。
569
1056
  そのために必要なものは揃えてある。
570
1057
 
@@ -841,3 +1328,8 @@ MIT License([LICENSE](LICENSE))
841
1328
 
842
1329
  同梱している日本語フォント **DotGothic16** は SIL Open Font License 1.1 です。
843
1330
  ライセンス全文は [assets/fonts/OFL.txt](assets/fonts/OFL.txt) に同梱しています。
1331
+
1332
+ ## 謝辞
1333
+
1334
+ 設計の下敷きにさせてもらった [ginue](https://github.com/goqoo-on-kintone/ginue) の作者と
1335
+ メンテナの方々に感謝します。詳しくは [先達について](#先達について)。