概要

項目内容
案件 IDMAS-231
案件名GAS パフォーマンス最適化パス 1(バッチ I/O + キャッシュ + 数式静的化)
カテゴリDevOps・パフォーマンス
優先度P1 ★★★(全機能波及・SPA 化 / GCP 移行の前段として最小投資で最大効果)
所要時間約 2-3 週間(週 10h 前提・段階適用可)
対象ファイル(新規)200_data/cache_layer.jsCacheLayer 名前空間・約 100 行)/ 900_test/perf_regression_tests.js(約 150 行)
対象ファイル(変更・主要)200_data/202_repository.js(キャッシュ HOF 適用)/ 400_domain/407_rpa_orchestrator.js / 400_domain/410_subledger_engine.js / 600_report/601-608_datamart_*.js(10-15 ファイル合計)
新規 03_sys_params キーMAS231_CACHE_TTL_SECONDS(default 21600 = 6h)/ MAS231_CACHE_ENABLED(default true)
前提案件MAS-233(パフォーマンス診断ツール)— 完了待ち or 並行実装。MAS-233 が 99_perf_log シートと Utils.perfStart/perfEnd を提供する想定
後続連携MAS-232(サイドバー SPA 化・最適化後にレイテンシ計測)/ MAS-236-240(GCP 移行・本案件で習得したパターンを Cloud Run / BigQuery 移行で再利用)
吸収・再定義対象なし(既存最適化案件 dev_mas-134_setup_all_schemas_optimization.mdDDL 専用で本案件と非重複)

目的

既存コードの 10-20% の書換体感 3-5 倍の高速化を達成し、SPA 化(MAS-232)や GCP 移行(MAS-236-240)の前段基盤として最小投資で最大効果を実現。

  • CacheService レイヤー導入で マスタ取得を 10-30 倍高速化 (PartnerRepository.findAsMap 等)
  • バッチ I/O 化で Action A/B / マート更新 / RPA 起票を 5x 高速化
  • flush() / Volatile 関数 / 重い条件付き書式の削減でスプレッドシート全体のレンダリングを改善
  • Jr 採用(MAS-230)後のハンズオン教材として 「読みやすい最適化パターン」 を蓄積する副次効果

現在のコード

利用する既存 API

API定義場所シグネチャ用途
Utils.getSheetByKey(key, fallbackName)000_infra/004_utils.js:302(key, fallback) → Sheetバッチ I/O 対象シート取得
Utils.persistLog(level, funcName, message, detail)000_infra/004_utils.js:577(level, fn, msg, detail)キャッシュ HIT/MISS / 最適化前後の計測ログ
Constants.getParam(key, defaultVal)000_infra/002_constants.js:147(key, default) → string|numberMAS231_CACHE_TTL_SECONDS 等取得
CacheService.getScriptCache()GAS 標準キャッシュ層の基盤
LockService.getDocumentLock()GAS 標準並行実行時のキャッシュレース対策(v2 候補)
SpreadsheetApp.flush()GAS 標準削除対象(途中の flush)or 末尾 1 回保持

既存 Repository 群(キャッシュ適用候補・全 6 件)

Repository用途呼出頻度 (推定)キャッシュ HIT 期待効果
PartnerRepository.findAsMap()取引先マスタ・全 RPA で使用高 (1 実行で 10+ 回)10-30x
AccountRepository.findAsMap()科目マスタ・全 RPA + マートで使用最高 (1 実行で 50+ 回)20-50x
PositionTemplateRepository.findAsMap()HC ポジション5-10x
HurdleRateRepository.findAsMap()投資ハードル5-10x
SocialInsuranceTierRepository.findAsMap()F-57 社保等級5-10x
PerDiemPolicyRepository.findAsMap()F-57 出張規程3-5x

バッチ I/O 化対象関数 (Top 候補)

grep -nE "for.*getValue\(\)|getValue\(\).*for|for.*setValue\(\)|appendRow\b" 400_domain/*.js 600_report/*.js で抽出:

  • 400_domain/410_subledger_engine.js (Action A/B・大量仕訳生成)
  • 400_domain/407_rpa_orchestrator.js (RPA 一括起票)
  • 600_report/601_datamart_ingest.js608_datamart_render.js (マート更新)
  • 200_data/202_repository.js (save / append)

実装着手時に grep で Top 20 関数を絞り込み、各関数の現状の I/O パターンと書換例を spec 着手前に確認する (Phase 1-3 のタスク)。

修正方針

Step 1: バッチ I/O 化(Top 20 関数の書換)

before (典型例):

for (var i = 0; i < n; i++) {
  var v = sheet.getRange(i + 2, 1).getValue();
  if (matches(v)) sheet.getRange(i + 2, 5).setValue('OK');
}

after (バッチ化):

var range = sheet.getRange(2, 1, n, sheet.getLastColumn());
var values = range.getValues();
for (var i = 0; i < n; i++) {
  if (matches(values[i][0])) values[i][4] = 'OK';
}
range.setValues(values);

I/O 回数を 2n + 12 に削減。n=1000 で約 500x 改善。

Step 2: マスタキャッシュ層(200_data/cache_layer.js 新設)

// 200_data/cache_layer.js (新設)
var CacheLayer = (function () {
  function _ttl_() {
    return Number(Constants.getParam('MAS231_CACHE_TTL_SECONDS', 21600));
  }
  function _enabled_() {
    var v = Constants.getParam('MAS231_CACHE_ENABLED', 'true');
    return /^(true|1|yes)$/i.test(String(v));
  }
  function get(key, loaderFn) {
    if (!_enabled_()) return loaderFn();
    var cache = CacheService.getScriptCache();
    var cached = cache.get(key);
    if (cached) {
      try { return JSON.parse(cached); } catch (_) { /* fallthrough */ }
    }
    var value = loaderFn();
    try {
      var serialized = JSON.stringify(value);
      if (serialized.length < 100000) cache.put(key, serialized, _ttl_());
      else Utils.persistLog('WARN', 'CacheLayer.get', 'Value > 100KB skipped: ' + key, '');
    } catch (e) { Utils.persistLog('WARN', 'CacheLayer.get', 'put failed: ' + e.message, key); }
    return value;
  }
  function invalidate(key) { CacheService.getScriptCache().remove(key); }
  function invalidateAll(keys) { CacheService.getScriptCache().removeAll(keys); }
  return { get: get, invalidate: invalidate, invalidateAll: invalidateAll };
})();

各 Repository に HOF パターンで適用:

// 200_data/202_repository.js (PartnerRepository 抜粋)
PartnerRepository.findAsMap = function () {
  return CacheLayer.get('partner_map', function () {
    return _findAsMapImpl_(PartnerRepository._getSheet());
  });
};
PartnerRepository.save = function (dto) {
  var result = _saveImpl_(PartnerRepository._getSheet(), dto);
  CacheLayer.invalidate('partner_map'); // 書込時無効化
  return result;
};

Step 3: flush() 削減

grep -nE "SpreadsheetApp\.flush\(\)" *.js **/*.js で全件抽出し、削除/保持を判定:

  • ループ内の flush()削除 (パフォーマンス劣化主因)
  • 関数末尾の flush()保持または最終 1 回に集約
  • トランザクション境界の flush() → 保持 (整合性のため)

各箇所の意図を spec § 注意事項に明示し、削除根拠を記録。

Step 4: Volatile 関数(NOW() / TODAY() / RAND())の除去

Volatile 関数はシート開閉や任意の編集で全シート再計算を引き起こすため、行数増加で乗数的に劣化する。

GAS 側で静的値セットに置換:

  • ヘッダー行の「最終更新日時」セル → setupAllSchemas 実行時に setValue(new Date()) で固定
  • 試算用 RAND() → 廃止 (試算は GAS 側 Math.random())

Step 5: 条件付き書式の削減

grep -nE "addConditionalFormatRule|newConditionalFormatRule" 100_config/*.js で全件抽出。

  • 数式条件 (whenFormulaSatisfied) で複雑なもの → GAS 側 setBackground() 置換
  • 単純な値範囲条件 → 保持(パフォーマンス影響小)

特にマート系・財務 3 表シートで条件付き書式が多い場合、行数 × 条件数で乗数的にレンダリングコストが増えるため優先削減。

Step 6: onEdit 軽量化

重い処理を onEdit(e) 本体に書くと毎編集ごとに実行され UX を阻害する:

before:

function onEdit(e) {
  // 重い処理 (5 秒以上)
  recalculateAll(e.range.getSheet());
}

after:

function onEdit(e) {
  CacheService.getScriptCache().put('pending_recalc', e.range.getA1Notation(), 60);
  // 即時 return (< 100ms)
}

// Installable trigger で 1 分後に非同期実行
function processPendingRecalc() {
  var pending = CacheService.getScriptCache().get('pending_recalc');
  if (pending) recalculateAll(...);
}

Installable trigger は ScriptApp.newTrigger('processPendingRecalc').timeBased().everyMinutes(1).create() で登録。

Step 7: 効果測定

MAS-233 の 99_perf_log シートで以下を計測し、合格基準を確認:

領域現状 P95 (推定)最適化後目標改善率
Action A (INV 起票)5-10 秒1-2 秒5x
Action B (消込確定)5-15 秒1-3 秒5x
マート全更新 (runDataMart)30-60 秒6-15 秒5x
RPA 起票 (407_rpa_orchestrator)10-30 秒2-6 秒5x
マスタ取得 (PartnerRepository.findAsMap)1-3 秒< 0.1 秒 (HIT)10-30x

合格基準: 全関数で P95 が現状の 1/3 以下 (= 3x 改善)。

影響範囲

対象種別変更内容リスク
200_data/cache_layer.js追加CacheLayer 名前空間 (約 100 行)純粋関数・既存ロジックへの影響なし
200_data/202_repository.js変更全 Repository 6 件に HOF 適用 (約 30 行追加)キャッシュ無効化漏れがあると古いマスタ参照リスク → テストで網羅
400_domain/410_subledger_engine.js変更バッチ I/O 化 (Top 関数 5-7 件)仕訳ロジックの I/O パターンのみ変更・計算結果は不変
400_domain/407_rpa_orchestrator.js変更バッチ I/O 化 + マスタ取得キャッシュ HIT同上
600_report/601-608_datamart_*.js変更バッチ I/O 化 + 条件付き書式削減マート結果の数値は不変・書式のみ変更
100_config/101_sys_config.js変更条件付き書式定義の単純化 / Volatile 関数除去DDL 整合性は setupAllSchemas テストでカバー
900_test/perf_regression_tests.js追加各最適化前後の I/O 回数・実行時間比較 (約 150 行)既存テストへの影響なし
03_sys_params変更MAS231_CACHE_* 2 キー追加既存キーには影響なし
appsscript.json変更なしscript.cache / script.scriptapp 既存スコープ内で完結failure_patterns #26 遵守
docs/_config.json変更nav 1 行追加PR 競合に注意

注意事項

  1. #18-#20(命名造語禁止): CacheLayer / withCacheInvalidation_ / _findAsMapImpl_ は既存命名規則 (SocialInsuranceTierEngine / PersonalTaxEngine 等) と整合する命名。GAS API (CacheService) と衝突しない名前空間。
  2. #21(getLastColumn() 列範囲膨張): バッチ I/O では getDataRange() または明示的列数指定を強制。getLastColumn() を毎回呼び出すと隠れ性能低下を招く。
  3. #25(並列実装対称性): 全 6 Repository で同一の HOF パターンを適用。save / append 後の invalidate() 呼出も完全対称に配置。
  4. #26(oauthScopes 部分宣言禁止): appsscript.json を一切編集しない。CacheService / LockService / ScriptApp.newTrigger は既存 cloud-platform スコープでカバー。
  5. #27(Admin SDK API 変動): 該当なし。
  6. #29(V8→Java Infinity null): 該当なし (本案件は計算結果の値域変更なし・既存挙動を保持)。
  7. キャッシュサイズ上限: CacheService は単一値 100KB / 全体 100MB。取引先 1000 件超のような大きなマスタは分割保管 (partner_map_part1 / partner_map_part2 等) を v2 で検討。v1 では JSON.stringify(value).length < 100000 で skip + WARN ログ。
  8. キャッシュ TTL 6h の妥当性: 税理士月次レビュー周期と整合 (1 日内のマスタ更新は十分カバー・月跨ぎは新セッションで再取得)。実運用で短縮要件が出たら MAS231_CACHE_TTL_SECONDS で調整可。
  9. トランザクション境界での flush() 保持: 整合性 vs パフォーマンスのトレードオフ。仕訳起票 (Action A/B) のような副作用大きい処理は末尾 1 回 flush() を保持し、再実行可能性を担保。
  10. 段階適用戦略: Step 1-7 を独立 PR 化 + 各 PR で回帰テスト必須。まとめて 1 PR にしない (rollback 困難)。順序: Step 1 (バッチ I/O) → Step 2 (キャッシュ) → Step 3 (flush) → Step 4-6 → Step 7 (測定)。
  11. 回帰テスト: 900_test/perf_regression_tests.js最適化前後の I/O 回数・実行時間を比較。期待値テーブルベースで合否判定 (各関数で expectedIOCallCount を spec 化)。
  12. CI ガード: Jr が誤って flush() を再追加 / getValue() ループを書くのを防ぐため、scripts/pre-push-check.shgrep -E "for.*getValue\(\)" 400_domain/ 等のガードを追加 (任意・MAS-224 と連動)。
  13. 数式静的化で失われる再計算自動性: NOW() / TODAY() を削除すると「常に最新時刻」を表示する機能が消える。spec で**「最終更新日時は手動 or バッチ更新」**と明示し、実運用に問題ないことを確認 (人間検討事項 #4)。
  14. onEdit 非同期化による UX 変化: 即時反映 → 1 分後反映に変わるため、UX に「処理中」表示で補完。Installable trigger の 1 分間隔で許容できないケース (リアルタイム要求) は対象外とする。

エッジケース

実装時に必ず以下 12 件を単体テストでカバーする (perf_regression_tests.js)。

#条件検知方法期待される挙動ログ出力
1キャッシュ HIT 時に古いデータ参照Repository.save 後の findAsMap で旧値返却savecache.invalidate を呼出する設計で対処テストで HIT/MISS パターン検証
2キャッシュ書込が 100KB 超過JSON.stringify(value).length >= 100000例外捕捉 + WARN ログ + キャッシュなしで loaderFn 結果返却Utils.persistLog('WARN', 'CacheLayer.get', 'Value > 100KB ...', ...)
3並行実行時のキャッシュレース同時に findAsMap + save 呼出v1 では受容 (最終書込み優先で OK)・v2 で LockService 検討
4バッチ I/O で空配列を setValues([])values.length === 0スキップ + return
5flush() 削除後の不整合連続書込で旧値が新値で上書きされない末尾 1 回 flush() で対処
6Volatile 関数を残す必要があるシート監査ログ等の =NOW() を意図的に保持spec で明示 + 削除対象から除外
7条件付き書式削減で書式情報消失削減前にスナップショット (背景色 / 文字色) を Sheet.getBackgrounds() で記録削減後に GAS 側で setBackgrounds() 復元
8onEdit 非同期化で UX 変化即時反映を期待していたユーザーの戸惑い「処理中」表示 + 1 分後完了通知で補完
9キャッシュ無効化漏れ (Repository.save → cache.invalidate 抜け)テストで HIT パターン検証save 後に findAsMap を呼んで新値が返ることを単体テストで保証
1099_perf_log 計測自体の overheadperfStart / perfEnd 自体の処理時間計測 OFF/ON フラグで運用 + overhead < 1ms 確認
11CacheService クォータ枯渇cache.put 例外 (Cache service quota exceeded)当日のみキャッシュ無効化 + 24h 後に自動回復Utils.persistLog('WARN', ...)
12Jr が誤って flush() を再追加pre-push-check.shgrep flush() ガードCI で push を弾くCI ログ

実データ検証

実装完了後に以下を MAS-233 計測ツールで実行し、合格基準と一致することを仕様書完成の必須条件とする。

1. Action A (INV 起票) 100 件バッチ実行

計測指標現状 P95最適化後目標
実行時間5-10 秒< 2 秒
getValue 呼出回数約 500 回約 5 回 (バッチ化)
マスタ取得回数100 回1 回 (キャッシュ HIT)

2. マート全更新 (runDataMart) 5 年分

計測指標現状 P95最適化後目標
実行時間30-60 秒< 15 秒
flush() 呼出回数10+ 回1 回

3. キャッシュ HIT/MISS 比率

セッション内で findAsMap を 50 回呼出 → 1 回目 MISS / 残り 49 回 HIT を確認。

4. 条件付き書式削減後のレンダリング速度

財務 3 表シート (P/L 60 行 × 12 ヶ月 + B/S 80 行 × 12 ヶ月) を開いて表示完了までの時間:

  • 現状: 5-10 秒
  • 削減後: < 2 秒

5. onEdit 軽量化後の UX

任意セル編集 → onEdit 完了までの時間 < 100ms (現状 5+ 秒)。

6. 回帰テスト (perf_regression_tests.js)

全 12 件のエッジケースが PASS することを確認。

関連ドキュメント

  • MAS-233: パフォーマンス診断ツール — 本案件の前提依存 (計測基盤)
  • MAS-232: サイドバー SPA 化 — 本案件で習得した最適化パターンを SPA 側にも適用
  • MAS-236-240: GCP 移行基盤 — 本案件で蓄積したパターンを Cloud Run / BigQuery 移行で再利用
  • MAS-230: Jr 採用要件定義 — 本案件は Jr ハンズオン教材として適切 (パターン化された最適化)
  • MAS-224: GitHub Actions CI パイプライン — pre-push-check.sh に CI ガード追加候補
  • failure_patterns: docs/_internal/failure_patterns.md — #18-20 / #21 / #25 / #26
  • dev_spec_prompt_template.md v1.10: 仕様書作成標準テンプレート
  • CLAUDE.md: ファイル番号体系・コーディング規約
  • PRD: プロダクトポリシー
  • docs/_internal/biz/financial_metrics_guide.md: パフォーマンス改善が安全性指標 (ランウェイ) に与える間接効果

人間が検討すべき事項

  1. 着手順序: Step 1 (バッチ I/O) → Step 2 (キャッシュ) → Step 3 (flush) → Step 4-6 → Step 7 (効果測定) → Jr ハンズオン
  2. キャッシュ TTL の最適値: 6h vs 1h vs 24h — 実運用後にチューニング
  3. キャッシュ無効化の粒度: Repository 単位 vs 全マスタ一括 — テスト容易性 vs パフォーマンスのトレードオフ
  4. 数式静的化で失われる再計算自動性の許容範囲: 「最終更新日時」セルが手動更新になることの UX 影響
  5. onEdit 非同期化による UX 変化の許容: リアルタイム要求がある機能 (例: 即時バリデーション) を spec で明示分離
  6. Volatile 関数の検知手法: grep vs 実行時 listener (Apps Script getOnValueChange 等) の比較
  7. 効果測定の合格基準: P95 半減 vs 1/5 — 案件価値の定量化
  8. Jr 入社後のハンズオン教材としての適合性: 最適化パターンを docs/ops/perf_handbook.md に整理するか
  9. CI ガード (grep flush()) の実装方針: pre-push-check.sh への追加 or .github/workflows/ci.yml への組込
  10. 段階適用 PR の分割粒度: Step 1-7 を別々 PR vs 関連 Step をまとめて 1 PR
  11. MAS-233 完了待ち vs 並行実装: 並行実装の場合、計測 API シグネチャを spec で fix する必要あり
  12. キャッシュ HIT 率モニタリング: 99_perf_log 拡張で HIT/MISS 率を継続モニタリングするか

実装プロンプト(Claude Code 用)

Claude Opus 4.7 推奨 (Step 2 キャッシュ層は HOF + Repository 横断で複雑)。それ以外の Step は Claude Sonnet 4.6 で OK。

## 案件
MAS-231 — GAS パフォーマンス最適化パス 1(バッチ I/O + キャッシュ + 数式静的化)

## 事前調査(必ず Read する)
1. `200_data/202_repository.js`: 全 6 Repository の `findAsMap` / `save` / `append` 実装パターン
2. `400_domain/410_subledger_engine.js`: Action A/B のバッチ I/O 化対象
3. `400_domain/407_rpa_orchestrator.js`: RPA 起票のキャッシュ HIT 適用候補
4. `600_report/601-608_datamart_*.js`: マート更新のバッチ I/O 化対象
5. `100_config/101_sys_config.js`: 条件付き書式 + Volatile 関数の現状定義
6. `000_infra/004_utils.js`: `persistLog` シグネチャ + `Constants.getParam` パターン
7. MAS-233 spec (存在すれば): `Utils.perfStart` / `perfEnd` API + `99_perf_log` シート構造
8. `failure_patterns.md`: #18-20 / #21 / #25 / #26 の文言

## 実装対象 (8 Step・段階 PR 化推奨)
Step 1: バッチ I/O 化 (Top 20 関数の書換)
Step 2: 200_data/cache_layer.js 新設 + 6 Repository に HOF 適用
Step 3: flush() 削減 (全件抽出 + 削除/保持判定)
Step 4: Volatile 関数除去 (NOW/TODAY/RAND を GAS 側静的化)
Step 5: 条件付き書式削減 (数式条件を setBackground 置換)
Step 6: onEdit 軽量化 (Installable trigger 非同期化)
Step 7: 900_test/perf_regression_tests.js 新設 (各 Step の前後比較)
Step 8: 効果測定 (MAS-233 計測 + 99_perf_log 確認)

## デプロイ手順
- 各 Step を独立 PR 化・dev でテスト → prod
- 各 PR で perf_regression_tests.js が PASS することを必須化
- コミットメッセージ: feat(MAS-231 Step N): <内容>

## failure_patterns チェック
- #18-20: CacheLayer / withCacheInvalidation_ 命名根拠
- #21: バッチ I/O では getDataRange() または明示列数指定を強制
- #25: 全 6 Repository で同一 HOF パターン適用
- #26: appsscript.json を一切編集しない
- #29: 該当なし (計算結果の値域変更なし)

推奨実行モデル

Phase推奨モデル根拠
Step 1 バッチ I/O 化 (機械的書換)Claude Sonnet 4.6パターン適用・判断要素少
Step 2 キャッシュ層新設 (HOF + Repository 横断)Claude Opus 4.7設計判断複雑・全 Repository 対称性確保
Step 3-6 各最適化Claude Sonnet 4.6個別パターン適用
Step 7 回帰テスト実装Claude Sonnet 4.6期待値テーブル既定義・パターン化
Step 8 効果測定Claude Haiku 4.5計測実行 + 数値比較のみ
仕様書レビュー (scripts/4_review_specs_by_gemini.js)Gemini 3 Pro Preview + Deep Thinkパフォーマンス影響の第三者検証

変更履歴

日時バージョン変更内容
2026-04-30v0.1 (仕様書完了)初版作成。tasks/prompts/task_MAS-231.md (PR #438・手動骨格 281 行) + tasks/prompts/task_MAS-231.gemini.md (PR #439・Gemini 3 Pro Preview Deep Think 85 行) を統合 input として Claude Opus 4.7 (1M context) で本体起草。14 セクション全網羅: 概要 + 目的 + 現在のコード (利用 API 6 件 + Repository 6 件 + バッチ I/O 化対象関数) + 修正方針 7 Step (バッチ I/O / キャッシュ層 / flush / Volatile / 条件付き書式 / onEdit / 効果測定) + 影響範囲 + 注意事項 14 件 + エッジケース 12 件 + 実データ検証 6 ケース + 関連ドキュメント 10 件 + 人間検討事項 12 件 + 実装プロンプト + 推奨実行モデル + 変更履歴。主要設計判断: (a) 新規ファイル 200_data/cache_layer.js (CacheLayer HOF パターン) / (b) 段階 PR 化必須 (Step 1-8 を独立 PR + 各 PR で回帰テスト) / (c) MAS-233 完了待ちまたは並行実装 / (d) 効果測定 KPI = 全関数 P95 が 1/3 以下 (3x 改善) を合格基準。実装規模: 約 100 行 (cache_layer) + 各 Repository 修正 30 行 + 既存ファイル 10-15 個のバッチ I/O 化 + テスト 150 行 + 工数 2-3 週間。実装着手可能状態 (MAS-233 並行実装で OK)。

仕様書作成プロンプト

task_MAS-231.md (手動骨格・PR #438) を展開して表示

本仕様書の作成指示プロンプト全文は tasks/prompts/task_MAS-231.md を参照。Gemini Deep Think 比較資料は tasks/prompts/task_MAS-231.gemini.md (PR #439) を参照。パイプラインの再現性確保のため、仕様書末尾への転載は省略し、Git 履歴上の元ファイルを参照する方針とする。

関連コマンド:

# Gemini Deep Think で代替案再生成する場合
TARGET_ID=MAS-231 node scripts/gen_gemini_alt_oneshot.js

# Gemini レビュー実行
SPEC_FILES=docs/dev/dev_mas-231_gas_perf_optimization.md node scripts/4_review_specs_by_gemini.js