Part of a set of zero-dependency CI tools for AI-agent repos — start with reflint.
Did a redesign quietly stop your form from tracking conversions? A conversion-tracking integrity linter. It reads your HTML/JSX source and fails the PR (exit 1 + inline annotations) when a form or CTA isn't wired for tracking. Zero-dependency, language-agnostic, runs in CI.
その改修、フォームのコンバージョン計測を静かに壊していませんか? 送信ボタン・フォーム・サンクスページが計測に 配線されているか を CI で検証するリンタ。表示は正常なのに計測だけ壊れる—を PR 時点で落とす。依存ゼロ・言語非依存。
Conversion tracking is the one layer that looks fine when it breaks. A redesign turns a <button type="submit"> into a <div>, the GTM click trigger silently stops firing, unit tests still pass, visual review misses it — and nobody notices until the numbers don't add up months later (sometimes they even go up, because a thank-you page with no noindex double-counts). Runtime QA tools (ObservePoint, Trackingplan, browser debuggers) only catch this after it ships. tracklint checks the source, so it fails the build before merge. It checks facts, not prose — so it works in any language.
# .github/workflows/tracklint.yml
name: tracklint
on: [push, pull_request]
jobs:
tracklint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: <you>/tracklint@v1 # auto-detects files containing <form>Findings show up as inline PR annotations, and the job fails (exit 1) so broken tracking can't be merged.
npx tracklint # <form> を含むファイルを自動検出
npx tracklint src/ pages/ # ファイル/ディレクトリ指定も可| rule | severity | 何を落とすか |
|---|---|---|
submit-not-button |
error | 送信コントロールが <div> / <span> / <a> で、JS でフォームを submit している → クリック計測が要素を特定できない |
submit-missing-tracking |
error | 送信ボタンに一意の id も計測属性(data-gtm-event 等)も無い |
submit-duplicate-id |
error | 送信ボタンの id が重複 → クリックの二重計上/取り違え |
submit-dynamic-id |
warn | id がテンプレート展開 → 実行時の値・一意性を静的に検証できない |
ajax-no-conversion |
warn | AJAX 送信なのに成功時の dataLayer.push / gtag / analytics.track が無い |
thankyou-unresolved |
error | フォームのサンクスページがリポジトリに存在しない |
thankyou-indexable |
error | サンクスページに noindex が無い → インデックス漏れ・CV 二重計上 |
Exit code 1 when any error is found = a CI gate.(warn は既定では CI を落としません)
配線系のルール(submit-missing-tracking / submit-missing-gtm-event-attr / ajax-no-conversion)は、
次のどちらかに当てはまるとき黙ります。公開リポジトリ 1,380 本に当てた監査の結果、
この2つが誤検知のほぼ全てだったためです(v0.4.0)。
- 計測基盤がプロジェクトのどこにも無いとき。GTM / gtag / fbq / analytics.track 等が 対象ファイルのどれにも出てこなければ、そのリポジトリは計測していない=「計測漏れ」を 指摘する意味がない。GTM は共通ヘッダ側に置かれることが多いので、判定は対象ファイル全体で行う。 この理由で黙ったときは CLI がその旨を出力する(黙った理由が分からないのが一番困るため)。
- 連絡先を集めていないフォームのとき。検索・絞り込み・並び替えのフォームはコンバージョンではない。
判定はボタンの文言ではなく入力欄で行う(「空室を検索」のような本物の CV を取りこぼさないため)。
type=email/type=tel/textarea/ name・placeholder が連絡先を示す欄があれば対象。
tracklint.config.json をリポジトリ直下に置くと上書きできます(無くても動きます)。
{
"trackingAttributes": ["data-gtm-event", "data-track"],
"conversionCalls": ["dataLayer.push", "gtag", "analytics.track"],
"rules": {
"ajax-no-conversion": "error",
"submit-missing-gtm-event-attr": "warn"
}
}フォームフレームワークや計測基盤ごとに「正しい配線」の形は違います。プリセットを有効化すると、その知識に基づく追加ルールが入り、同時に フレームワーク特有の誤爆を抑制 します(例: Contact Form 7 は既定でサンクスへ遷移しないので thankyou-* を出さない)。各完了シグナルは公式ドキュメントで裏取り済み。
// tracklint.config.json
{ "presets": ["wordpress", "meta"] }npx tracklint --preset=wordpress,meta # CLI でも指定可("=" と "," 区切り)| プラグイン | 検出 | 完了シグナル | 検証 |
|---|---|---|---|
| Contact Form 7 | form.wpcf7-form |
wpcf7mailsent(成功時のみ発火) |
✅ 完了イベントに計測が配線されているか |
| Snow Monkey Forms | form.snow-monkey-form |
smf.complete |
✅ 同上 |
| WPForms | form.wpforms-form |
確認タイプ3種で可変 | 🔍 検出のみ(誤爆抑制) |
| MW WP Form | .mw_wp_form / submitSend |
完了画面 or 別URL | 🔍 検出のみ(誤爆抑制) |
CF7 / Snow Monkey は「送信完了で別URLへ遷移しない」ため、<button> のクリック計測ではなく 完了イベントに計測を配線 するのが正解です。tracklint はこれを検証し、逆にクリック計測ルール(submit-*)を課しません。WPForms / MW WP Form は完了がサーバ再描画・設定依存で静的に確定できないため、検出(=サンクス誤爆の抑制)のみ にとどめます(正直に success-tracking は課しません)。
| rule | severity | 何を落とすか |
|---|---|---|
wp-form-no-success-tracking |
warn | CF7/Snow Monkey で、完了イベントのリスナ内に計測(gtag / dataLayer.push / fbq)が無い |
wp-form-tracking-on-wrong-event |
warn | CF7 で CV を wpcf7submit(無効送信・スパムでも発火)に紐付けている → CV 水増し |
fbq(...) をコンバージョン呼び出しとして認識し、静的に確定できる破損だけを検出します。
| rule | severity | 何を落とすか |
|---|---|---|
meta-pixel-track-without-base |
error | fbq('track', …) があるのに base code(fbq('init') / fbevents.js)が無い → 送信されない |
meta-pixel-duplicate-init |
warn | 異なる Pixel ID で fbq('init') が複数 → PageView/CV の二重計上 |
Meta Pixel の 実発火(本当に
CompleteRegistrationが飛んだか)は静的には見えません(--runtimeは Roadmap 参照)。GTM 経由でのみ設置している場合、生 HTML にfbqが現れないため検出対象外です(誤って「未設置」とは判定しません)。
サンプル: examples/wordpress/(cf7-bad.html / cf7-good.html / meta-pixel-bad.html)
これは 静的解析 です。「配線が存在し正しく書かれている」ことを検証しますが、GTM/GA4 が実行時に実際に発火したことは保証しません。以下は構造的に見えないので、warn/info に留めるか設定で抑制します。動的検証(E2E)は置き換えではなく補完です。
- GTM のコンテナ側(UI)だけで組んだトリガーは見えない(ページ側に手掛かり=id/属性があるかだけを見る)
- 別ファイル/バンドル/フレームワークのハンドラに配線された計測は追い切れない
- CMS やサーバーテンプレートが実行時に注入する
dataLayer.pushは静的ソースに無い - HTTP ヘッダ(
X-Robots-Tag)で付けたnoindexは<meta>が無いと拾えない robots.txtのDisallowはnoindexとは別物(クロール禁止であってインデックス禁止ではない)
- Static core: submit control / tracking hook / AJAX conversion / thank-you page — zero-dep (
src/scan.mjs) - GitHub Action (
action.yml) + inline PR annotations + self-CI dogfood -
--runtimemode (Playwright): 実際に送信してイベント発火とサンクス遷移を検証 - SARIF 出力(GitHub code-scanning 連携)
- baseline/allowlist(既存リポは新規違反だけで落とす)
- presets:
wordpress(Contact Form 7 / Snow Monkey Forms / WPForms / MW WP Form)+meta(Meta Pixel) - presets: Shopify / HubSpot
node --test # unit tests
npm run poc # 壊れたサンプルで検出デモ → exit 1
npm run selfcheck # 正しいサンプルの自己検査 → exit 0Zero-dependency CI linters for repos where AI agents do the work. Each one fails the PR on something that breaks quietly.
| Catches | |
|---|---|
| reflint | AGENTS.md / llms.txt / CLAUDE.md pointing at commands, scripts, or paths that no longer exist |
| skills-lint | SKILL.md broken references + name/trigger collisions between skills |
| carrylint | Skills with the author's machine or model baked in — absolute paths, undeclared CLIs, unresolved placeholders |
| genchi | Agents reporting "done" without re-fetching real-world state |
| tracklint ← you are here | Forms and CTAs that quietly stopped being wired for conversion tracking |
| tokenlint | Hardcoded colors that bypass your design tokens |
| reflint for VS Code | The same reflint checks, inline in the editor as you save |
| orogami | Not a linter — natural Japanese/CJK line breaking for OGP images (BudouX + font subsetting) |
MIT