法令改正チェックを定期実行する:e-Gov法令API law_revisions の差分検知とClaude Code連携
e-Gov法令API v2の law_revisions を定期取得し、履歴IDと施行状態の変化を検知する70行のNode.jsスクリプトを実行確認。変化時だけ claude -p で要約し、タスクスケジューラやcronで回す設計を2026年9月14日の実測で整理します。
法令の改正を見逃さない仕組みは、本文の差分を取らなくても作れます。e-Gov法令API v2の law_revisions を1日1回取り、履歴IDの集合と施行状態を前回と比べれば、改正法の公布も施行日の到来も検知できます。 変化があった日だけ claude -p に差分を渡して要約させれば、毎日AIを呼ぶ必要もありません。
この記事では、70行のNode.jsスクリプトを実際に4パターンで実行し、Windowsタスクスケジューラとcronで回す設計、そしてClaude Codeのヘッドレス実行に渡すところまでを整理します。claude -p の呼び出し自体はこの記事では実行しておらず、Claude Code公式ドキュメントで確認した範囲で書いています。ヘッドレス実行の基本はclaude -p の記事にあります。
- 監視対象は
law_revisions。 本文(数百KB〜16MB)ではなく履歴の一覧(労働基準法で12KB)を比べます。 - 検知するのは3種類。 履歴IDの追加、
current_revision_statusの変化、updatedの変化です。 - 終了コードで分岐する。 0=変化なし、2=変化あり、1=エラー。スケジューラ側はこれだけ見ます。
- AIは要約役。 差分JSONを
claude -pに渡して日本語にする。施行日の判断は人が原文で確認します。
なぜ law_revisions を監視するのか
労働基準法(322AC0000000049)の law_revisions を2026年9月14日に取ると、15件の履歴のうち未施行が2件ありました。
- 2025-06-01令和四年法律第六十八号が施行322AC0000000049_20250601_504AC0000000068。現在は PreviousEnforced
- 2026-06-24令和八年法律第四十六号が公布・一部施行322AC0000000049_20260624_508AC0000000046。同じ改正法による 2028-12-23 施行予定の版も別の履歴として登録されている
- 2026-07-17令和八年法律第六十号が公布・施行322AC0000000049_20260717_508AC0000000060。現行版(CurrentEnforced)
- 2027-04-01令和八年法律第六十号の残りが施行予定322AC0000000049_20270401_508AC0000000060。UnEnforced
- 2028-12-23令和八年法律第四十六号の残りが施行予定322AC0000000049_20281223_508AC0000000046。UnEnforced。データ更新は 2026-07-23
ここで分かるのは、改正が「公布」と「施行」の2段階でデータに現れることです。公布されると新しい履歴IDが UnEnforced で追加され、施行日が来ると CurrentEnforced に変わります。本文を毎日ダウンロードして diff を取るより、この2つの変化を見る方が軽く、しかも「いつ効力を持つか」まで分かります。asof を使った時点指定の考え方はasof指定の記事と同じです。
スクリプト(70行、依存なし)
watch-revisions.mjs と、監視対象を書く watch.json の2ファイルです。状態は同じフォルダの state.json に保存し、変化があれば diff.json を書きます。
{ "law_ids": ["322AC0000000049", "331AC0000000120", "417AC0000000086"]}// e-Gov 法令API v2 の law_revisions を定期取得し、前回スナップショットとの差分を報告する// 使い方: node watch-revisions.mjs (watch.json の law_ids を監視)// 終了コード: 0 = 変化なし / 2 = 変化あり(diff.json を出力) / 1 = エラーimport { readFile, writeFile } from 'node:fs/promises';
const BASE = 'https://laws.e-gov.go.jp/api/2';const CONFIG = new URL('./watch.json', import.meta.url);const STATE = new URL('./state.json', import.meta.url);const DIFF = new URL('./diff.json', import.meta.url);
const config = JSON.parse(await readFile(CONFIG, 'utf8'));let state = {};try { state = JSON.parse(await readFile(STATE, 'utf8')); } catch { /* 初回 */ }
const changes = [];const errors = [];const next = {};
for (const lawId of config.law_ids) { const res = await fetch(`${BASE}/law_revisions/${lawId}`, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(30_000), }); if (!res.ok) { // 存在しない ID は 404 {"code":"404001"} で返る。前回の状態を保持して次へ errors.push({ law_id: lawId, status: res.status, body: (await res.text()).slice(0, 120) }); if (state[lawId]) next[lawId] = state[lawId]; continue; } const data = await res.json();
// 比較に使う項目だけ残す(本文は取らない) const revisions = data.revisions.map((r) => ({ law_revision_id: r.law_revision_id, law_title: r.law_title, amendment_enforcement_date: r.amendment_enforcement_date, amendment_law_num: r.amendment_law_num, current_revision_status: r.current_revision_status, updated: r.updated, })); next[lawId] = { law_num: data.law_info.law_num, revisions };
const prev = state[lawId]; if (!prev) { changes.push({ law_id: lawId, kind: 'initial', title: revisions[0]?.law_title, count: revisions.length }); continue; }
const prevById = new Map(prev.revisions.map((r) => [r.law_revision_id, r])); for (const r of revisions) { const p = prevById.get(r.law_revision_id); if (!p) { changes.push({ law_id: lawId, kind: 'new_revision', ...r }); continue; } if (p.current_revision_status !== r.current_revision_status) changes.push({ law_id: lawId, kind: 'status_changed', law_revision_id: r.law_revision_id, from: p.current_revision_status, to: r.current_revision_status, title: r.law_title }); else if (p.updated !== r.updated) changes.push({ law_id: lawId, kind: 'updated', law_revision_id: r.law_revision_id, from: p.updated, to: r.updated, title: r.law_title }); } for (const p of prev.revisions) if (!revisions.some((r) => r.law_revision_id === p.law_revision_id)) changes.push({ law_id: lawId, kind: 'removed', law_revision_id: p.law_revision_id });}
await writeFile(STATE, JSON.stringify(next, null, 2), 'utf8');const checkedAt = new Date().toISOString();const real = changes.filter((c) => c.kind !== 'initial');
for (const e of errors) console.error(`[error] ${e.law_id} HTTP ${e.status} ${e.body}`);if (changes.length === 0) { console.log(`${checkedAt} 変化なし (${config.law_ids.length - errors.length} 法令)`);} else { await writeFile(DIFF, JSON.stringify({ checked_at: checkedAt, changes }, null, 2), 'utf8'); for (const c of changes) console.log(`[${c.kind}] ${c.law_id} ${c.law_revision_id ?? ''} ${c.title ?? ''} ${c.from ? `${c.from} -> ${c.to}` : ''}`.trim());}process.exitCode = errors.length > 0 ? 1 : real.length > 0 ? 2 : 0;比較に使う項目だけを state.json に残すのがポイントです。本文を保存しないので、3法令(労働基準法15履歴・旧下請法3履歴・会社法32履歴)でも状態ファイルは小さく、git管理しても差分が読めます。
実行結果:4パターン
Node.js 24.8.0で、初回、変化なし、変化あり(state.json を手で書き換えて再現)、存在しないIDの4パターンを流しました。
### 初回(state.json なし)[initial] 322AC0000000049 労働基準法[initial] 331AC0000000120 製造委託等に係る中小受託事業者に対する代金の支払の遅延等の防止に関する法律[initial] 417AC0000000086 会社法exit=0
### 2回目(変化なし)2026-09-14T04:37:09.167Z 変化なし (3 法令)exit=0
### state.json から最新履歴を1件消して再実行(改正公布を模擬)[new_revision] 322AC0000000049 322AC0000000049_20281223_508AC0000000046exit=2
### 存在しないIDを watch.json に混ぜる[error] 999AC9999999999 HTTP 404 {"code":"404001","message":"取得結果が0件です。"}2026-09-14T04:37:10.961Z 変化なし (1 法令)exit=1初回は全法令が initial として出ますが、これは「変化」ではないので終了コードは0です。変化ありのときに書かれた diff.json はこの形でした。
{ "checked_at": "2026-09-14T04:37:09.943Z", "changes": [ { "law_id": "322AC0000000049", "kind": "new_revision", "law_revision_id": "322AC0000000049_20281223_508AC0000000046", "law_title": "労働基準法", "amendment_enforcement_date": "2028-12-23", "amendment_law_num": "令和八年法律第四十六号", "current_revision_status": "UnEnforced", "updated": "2026-07-23T15:54:56+09:00" } ]}改正法令番号と施行予定日が入っているので、この時点で「令和八年法律第四十六号による改正が2028年12月23日に施行される」まで機械的に分かります。存在しないIDは404(コード404001)を記録して他の法令の処理を続け、終了コードを1にします。監視対象の書き間違いに翌朝気づける仕組みです。エラーコードの意味はエラーと制限の記事にまとめています。
変化があった日だけ claude -p に渡す
スクリプトの終了コードが2のときだけClaude Codeを呼ぶラッパーです。claude -p はstdinを読めるので、diff.json をそのまま流し込みます。
#!/usr/bin/env bash# run-watch.sh : 差分検知 → 変化があれば claude -p で要約cd "$(dirname "$0")"node watch-revisions.mjs >> watch.log 2>&1code=$?if [ "$code" -eq 2 ]; then claude --bare -p "以下は e-Gov 法令API の law_revisions の差分(JSON)です。法令ごとに、何が起きたか(新しい版の追加 / 施行状態の変化 / 更新日時のみ)と施行日を日本語で3行以内に要約してください。差分に含まれない情報は書かないでください。" \ --output-format json < diff.json > summary.json jq -r '.result' summary.json >> watch.logfiexit "$code"公式ドキュメントで確認したフラグの意味は次のとおりです。
-p:非対話で1回だけ実行し、結果を標準出力に出す。stdinから入力を読める(上限10MB)--bare:フック・スキル・MCP・CLAUDE.md などの自動読み込みを省いて起動する。スクリプト用に推奨されているが、ANTHROPIC_API_KEYが必要でサブスクリプションのログインは使われない--output-format json:結果をresultフィールドに持つJSONで返す。total_cost_usdも含まれる- 終了コード:成功で0、失敗で非0
要約させる内容は「差分に含まれる事実」に限定し、--json-schema で structured_output を強制すればさらに機械的に扱えます。改正の中身(どの条がどう変わったか)を書かせるなら、diff.json だけでは足りないので law_data で新旧の本文を取って渡す段階を挟みます。そこは検知とは別のジョブに分けた方が、毎日の処理が軽いままです。
通知先はチーム次第です。watch.log への追記だけでも成り立ちますし、summary.json の result を curl -X POST でチャットのWebhookに送るのも1行で済みます。
スケジューラで回す
Windowsは schtasks、Linux/macOSはcronで、1日1回で十分です。e-Govのデータ更新は日単位で、updated も日時までしか入っていません。
# Windows: 毎日 7:00 に実行(/SC DAILY /ST 開始時刻 /TN タスク名 /TR 実行コマンド)schtasks /Create /SC DAILY /ST 07:00 /TN "egov-law-watch" ^ /TR "cmd /c cd /d C:\egov-watch && node watch-revisions.mjs >> watch.log 2>&1"# Linux / macOS: crontab -e0 7 * * * cd /opt/egov-watch && bash run-watch.shschtasks /Create の /SC /ST /TN /TR は schtasks /Create /? のヘルプで確認した構文です。実際のタスク登録はこの記事では行っていません。運用で決めておくことを3つ挙げます。
state.jsonはスクリプトと同じフォルダに置く。import.meta.url基準で解決しているので、スケジューラの作業ディレクトリに依存しません。- 終了コード1(エラー)を放置しない。 404が続くならIDの書き間違い、タイムアウトが続くならネットワークです。ログの
[error]行を週1回は見ます。 - 新法は別の入口で拾う。
law_revisionsは既知のIDが前提なので、新しく制定された法令はlaws?promulgation_date_from=YYYY-MM-DD&law_type=Actで取ります。2026-06-01以降では8件(508AC0000000040 など)が返りました。
人が確認する線
検知と要約まで自動化しても、「この改正がうちの契約に効くか」は自動化しません。diff.json にある law_revision_id と amendment_enforcement_date を持って、e-Gov法令検索の原文で新旧を読む段階は人の作業として残します。AIに任せる範囲と人が確認する範囲の線引きは法令をAIで扱うときの安全境界にまとめたチェックリストと同じで、履歴IDを出力に残すのはそのためです。
まとめ
- 監視対象は本文ではなく
law_revisions。履歴IDの追加(公布)とcurrent_revision_statusの変化(施行)で改正の2段階を検知できる - 70行のスクリプトで、初回0・変化なし0・変化あり2・エラー1の終了コードを確認。変化時は
diff.jsonに改正法令番号と施行予定日が入る claude -pは終了コード2のときだけ呼ぶ。--bareと--output-format jsonで要約をJSONに固定する(呼び出しは公式ドキュメントで確認、未実行)- Windowsは
schtasks /SC DAILY、cronは0 7 * * *。新法はlaws?promulgation_date_fromで別途拾う
Claude Codeに条文の内容まで調べさせる構成はe-Gov法令API活用ガイド、MCP経由で条文を引く手順はClaude Codeでの設定と使い方から辿れます。
関連して読む
legal-tech・japanese-lawを続けて読む
· 参考リンク 5件契約書レビューをClaude Code+e-Gov法令MCPで回す実務フロー:条文特定から人の確認まで
契約書レビュー・コンプライアンス確認をClaude Codeとe-Gov法令MCPで進める手順。対象条文の特定、asofでの施行時点確認、条文引用、人が確認する境界の順に、フリーランス法・取適法をe-Gov法令API v2で実際にcurl実行した2026-09-14のログを根拠に解説。CLAUDE.md追記例付き。
egov・japanese-lawを続けて読む
· 参考リンク 3件e-Gov法令API v2エンドポイント逆引きリファレンス:6本の用途・パラメータ・実測レスポンス
e-Gov法令API v2の6エンドポイント(laws / law_revisions / law_data / keyword / law_file / attachment)を、やりたいこと別に逆引きできる形で整理。全エンドポイントをcurlで実行し、主要パラメータと応答を2026年9月14日に実測しました。
この記事の情報・検証メモ
- egov
- japanese-law
- legal-tech
- claude-code
- workflow
- automation
- windows
- 公開日
- 情報確認
- 参考リンク
- 3件
- 更新性
- 定期更新
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。