@analitikisler/ai-tag-debugger 0.16.0 → 0.18.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.
- package/CHANGELOG.md +31 -0
- package/README.md +128 -35
- package/README.tr.md +127 -36
- package/package.json +8 -1
- package/src/agent.js +11 -4
- package/src/audit.js +100 -36
- package/src/baseline.js +90 -0
- package/src/browser.js +36 -1
- package/src/changes.js +9 -3
- package/src/checks.js +10 -3
- package/src/ci-output.js +104 -0
- package/src/cli.js +84 -3
- package/src/consent.js +4 -2
- package/src/doctor.js +68 -0
- package/src/gtm.js +209 -3
- package/src/help.js +33 -3
- package/src/history.js +302 -0
- package/src/html/consent.js +152 -0
- package/src/html/css.js +1180 -0
- package/src/html/findings.js +493 -0
- package/src/html/journeys.js +449 -0
- package/src/html/lib.js +272 -0
- package/src/html/scope.js +350 -0
- package/src/html/script.js +300 -0
- package/src/html/shell.js +191 -0
- package/src/html/summary.js +288 -0
- package/src/i18n.js +1107 -141
- package/src/mcp.js +22 -4
- package/src/metrics.js +482 -0
- package/src/notify.js +21 -7
- package/src/plan-html.js +71 -25
- package/src/plan.js +6 -1
- package/src/pool.js +34 -0
- package/src/providers.js +90 -12
- package/src/replay.js +28 -1
- package/src/report-shared.js +123 -0
- package/src/report.js +289 -1018
- package/src/schema.js +190 -0
- package/src/sheet.js +11 -5
- package/src/whitelabel.js +35 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
- The HTML report was rebuilt as an inspector: a collapsible rail with seven sections (Summary, Findings, Journeys, Consent, Coverage, Inventory, Run), one screen at a time. The Summary is a score ring banded red, amber and green, the verdict, up to three lines saying what cost the points, status chips that open the findings pre-filtered, and six cards about the run. Findings have a filter bar (severity, owner, platform, journey, viewport, search) over failed, unverified and passed groups, every row collapsed and opening into Evidence, Fix, Where and Verify tabs. Journeys show the steps by page title on the left and the selected step's Hits, Data Layer and Requests tabs on the right, with a consent chip on every hit. Every card starts collapsed except the Summary; every section, filter and card has its own URL. With a model, the Summary shows its one-line headline and an AI summary card in Findings keeps its points and its "worth checking" list; a route that differs from the last run is named under the Run facts. Severity is never colour alone, one hue per kind of chip, light and dark mode, keyboard (`[` folds the rail, `1`–`7` jump), no horizontal scroll on phones, and the stacked no-JavaScript document is the print layout. Design notes and the mockups it was built from are under `docs/design/directions/`.
|
|
6
|
+
- `plan.html` uses the same stylesheet and chips as the report.
|
|
7
|
+
- The Trend table's last column is labelled "Problem points": it is the weighted count of open problems per run (lower is better), not the 0–100 data quality score.
|
|
8
|
+
- Fixed: in `report.md`, the link under a finding with no platform (a journey that did not finish, no Consent Mode default) or under a second finding with the same platform, event and check now opens its card in `report.html` instead of the Summary.
|
|
9
|
+
- Fixed: a `value` that is not a number (an unresolved template, a product name) showed as `NaN` in the platform parity table; the report now shows the text the tag sent, and a number on one platform against text on the other counts as a value difference.
|
|
10
|
+
- Development: the HTML report lives in `src/html/` (one module per section, the stylesheet and the hashed script as constants); the real demo capture is a test fixture; the design record under `docs/design/directions/` holds the UX research, the four directions with their rationales, and the chosen mockup.
|
|
11
|
+
|
|
12
|
+
## 0.17.0 (2026-10-19)
|
|
13
|
+
|
|
14
|
+
- The report opens with a verdict. A deterministic sentence names the lead problem and where it is ("purchase tracking is broken on mobile"), with a data quality score whose weights are printed next to it, so a run without an AI model still says what matters first. The same verdict heads report.md, the CLI output, `summary.json` and the Slack and email alerts.
|
|
15
|
+
- The HTML report was rebuilt: a verdict block with count tiles, "What to fix first" grouped by owner with the points each fix recovers, finding cards with a stable id, impact, cause, evidence, fix, where and how to verify, a journeys view with the hits per step, consent scenarios with a reason per cell, expected-versus-observed and platform parity tables, conversion integrity, plan coverage and a trend card. Severity is never colour alone, platform icons are one inline sprite, system fonts only, light and dark mode, keyboard and screen-reader friendly, no horizontal scroll on phones, and print opens only the flagged steps: the demo report is 22% smaller and prints on 21 pages instead of 55.
|
|
16
|
+
- Every check has a plain-language consequence and a fix direction in both languages, shown when no model explanation is available, and a category (money, legal or hygiene). Finding texts name platforms as GA4 and Meta rather than their keys.
|
|
17
|
+
- `report.md` follows a fixed shape within about 120 lines: verdict, counts, changes since the last run, trend, priorities by owner, the top findings in full, consent, what passed, journeys and flagged steps, inventory. The timeline stays in `timeline.csv`.
|
|
18
|
+
- Trend over runs: each saved run updates `aitd/<domain>/history.json`; the report, the CLI and the alerts show the direction, a sparkline, problems open longest and problems that flap. `aitd history --site <url> [--limit n] [--json]` prints the series.
|
|
19
|
+
- Alerts carry the verdict, the direction with a sparkline, flapping problems and "was: …" for fixed items.
|
|
20
|
+
- `summary.json` names the run folder as `run` and lists file names relative to it, so a summary uploaded as a CI artifact reads the same on any machine. With `--fail-on never`, `junit.xml` no longer marks an unfinished journey as a failure.
|
|
21
|
+
- A baseline written for another site prints a warning, and a problem accepted on some viewports only is reported as partially accepted and still counted.
|
|
22
|
+
- Every completed run saves its steps as `steps.json` in the run folder. `--scripted` replays the last completed run's steps for a journey that has none of its own, so after one run with an AI model the routine CI run needs no model at all.
|
|
23
|
+
- `--concurrency <n>` (config `concurrency`, MCP `run_audit` `concurrency`): journeys and consent scenarios run side by side, each in its own browser, up to 8 at once. Default 1. On the demo shop, 4 runs plus consent scenarios take 17 seconds at 4 instead of 41 at 1.
|
|
24
|
+
- Baseline of accepted findings: `--baseline <file>` (config `baseline`) marks known findings as accepted, so they are listed in the report but left out of the counts and the exit code. `--update-baseline` writes the file. Stale entries are reported, never removed.
|
|
25
|
+
- `junit.xml` and `summary.json` are written with every run. The JUnit file fails the same findings as `--fail-on`, so GitHub, GitLab and Jenkins show findings as test failures; `summary.json` carries the counts, journeys and findings for scripts. The GitHub Actions examples upload `junit.xml`.
|
|
26
|
+
- The config and the measurement plan are checked against a schema when they are read. A wrong viewport, step action, platform or type stops the run before the browser starts with the field name and the allowed values; an unknown key gives a warning with a "did you mean" suggestion.
|
|
27
|
+
- With a `gtm_container`, each tag under a broken finding now says whether its trigger fired and why not, evaluated from the captured dataLayer: no push with that event, a condition such as `ecommerce.value greater than 0` that the push failed, an exception trigger, a paused tag, or a consent type that was denied. No AI model is needed.
|
|
28
|
+
- Transient failures are retried: model requests on 408, 409, 429, 5xx and dropped connections (the Anthropic and OpenAI SDKs' own retries, set to 3; a bounded retry with backoff and `retry-after` for Gemini), and a navigation that fails with a network error or a timeout is tried once more. A URL the host guard refuses is never retried.
|
|
29
|
+
- `aitd doctor` checks Node, the Chromium build Playwright expects, the AI keys, the data folder and, with `--site`, that the host resolves.
|
|
30
|
+
- Run history: `src/history.js` reads the site's run folders (and a small `history.json` index kept per site) into a finding timeline, a trend series, open-longest and flapping lists, and journey stability. Report metrics: `src/metrics.js` computes a deterministic verdict, a quality score with documented weights, plan coverage, journey funnels, platform parity, consent compliance, conversion integrity and timing from the captured data.
|
|
31
|
+
- Fixed: a CSV plan import without a `user_properties.csv` beside it crashed with a ReferenceError instead of warning.
|
|
32
|
+
- Development: `npm run lint` (ESLint, also in CI), `npm run test:unit` for the browser-free tests, an i18n parity test, a prompt-injection regression test, CONTRIBUTING.md, SECURITY.md and CLAUDE.md. Design review of the report under `docs/design/`: brief, copy guide, UX audit, IA spec, accessibility checklist, Markdown spec, visual system and a single-file prototype.
|
|
33
|
+
|
|
3
34
|
## 0.16.0 (2026-10-17)
|
|
4
35
|
|
|
5
36
|
- Campaign checks (`campaign_checks`, on by default): each run opens the first page like an ad click, with UTM parameters, `gclid` or `fbclid` (or the ones already in the start address), and reports when a redirect drops them or the hits show a page address without them. Set `campaign_checks` to `false` to open the plain address.
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Platforms: GA4 (including server-side GTM endpoints and Google tag gateway paths
|
|
|
56
56
|
|
|
57
57
|
When GA4 hits go to a server container on another host, or its loader is served from one, the report adds an info finding: the events it forwards to ad platforms never pass through the browser, so they can't be seen here.
|
|
58
58
|
|
|
59
|
-
With a `gtm_container` export, each broken event also names the GTM tag that sends it and the trigger it fires on, or says that no tag sends it.
|
|
59
|
+
With a `gtm_container` export, each broken event also names the GTM tag that sends it and the trigger it fires on, or says that no tag sends it. Each tag then says whether its trigger fired in that journey and, when it did not, why: the tag is paused, no dataLayer push carried the trigger's event, a trigger condition failed (with the value the push had, read from the cumulative dataLayer the way GTM's Data Layer Variables read it), an exception trigger blocked it, or the consent it needs was denied when the event fired. When everything fired and the hit is still wrong, it says so, pointing at the tag's own setup. Click, form, visibility and timer triggers are evaluated by GTM in the page, so they are reported as not evaluable unless their `gtm.click`-style event reached the dataLayer. All of this is evaluated from the captured dataLayer, with no AI model.
|
|
60
60
|
|
|
61
61
|
### Choose the platforms to check
|
|
62
62
|
|
|
@@ -71,7 +71,7 @@ Each audit also opens the start page once per consent choice, each time in a fre
|
|
|
71
71
|
## How it works
|
|
72
72
|
|
|
73
73
|
1. **Browse:** the AI model gets a goal in plain language ("add any product to the cart and complete checkout with the test payment") and a text view of the page. It acts through two tools, `browser_action` (goto, click, fill, select, scroll, wait) and `finish`, one step at a time with Playwright.
|
|
74
|
-
2. **Capture:** every request to a tracking endpoint is parsed into normalized hits and tagged with the step that caused it. Every dataLayer push is recorded through an init script, including `gtag('consent', ...)` commands. By default, the collection requests of GA4, Google Ads, Meta, TikTok, Microsoft Ads,
|
|
74
|
+
2. **Capture:** every request to a tracking endpoint is parsed into normalized hits and tagged with the step that caused it. Every dataLayer push is recorded through an init script, including `gtag('consent', ...)` commands. By default, the collection requests of every platform the tool can parse (GA4, Google Ads, Meta, TikTok, X, LinkedIn, Microsoft Ads, Pinterest, Snapchat, Criteo, Klaviyo, Adobe Analytics, Matomo and Piwik PRO), plus Google's conversion, remarketing and consent pings, are answered locally, so **test traffic doesn't reach those real accounts**. Tag loads and requests to vendors the tool only recognises by host (such as Clarity and Hotjar) still go out; the network part of the timeline shows each call and whether it was answered locally.
|
|
75
75
|
3. **Check:** deterministic rules compare the hits with your measurement plan. This part needs no API calls, so results are reproducible.
|
|
76
76
|
4. **Explain:** the model reads the findings, the hits per step and (optionally) your GTM container export. It writes the likely cause and fix for each problem, naming the tags and triggers involved.
|
|
77
77
|
|
|
@@ -164,7 +164,7 @@ The global install also adds `aitd`, a short alias: `aitd run --site … --journ
|
|
|
164
164
|
|
|
165
165
|
Or run it without installing: `npx -y @analitikisler/ai-tag-debugger run --site … --journey add_to_cart`.
|
|
166
166
|
|
|
167
|
-
Run `aitd --help` to see the commands, and `aitd run --help` (or `plan`, `replay`, `mcp`) for each command's options.
|
|
167
|
+
Run `aitd --help` to see the commands, and `aitd run --help` (or `plan`, `replay`, `mcp`) for each command's options. If something does not work, `aitd doctor` checks Node, Chromium, your AI key and the data folder, and says what to fix.
|
|
168
168
|
|
|
169
169
|
### Try the demo shop (from source)
|
|
170
170
|
|
|
@@ -284,7 +284,7 @@ Say yes, and the next runs can check against it with `--against latest`. A param
|
|
|
284
284
|
|
|
285
285
|
### Plan vs. site
|
|
286
286
|
|
|
287
|
-
When a run uses `--against`, the report
|
|
287
|
+
When a run uses `--against`, the report has a **Plan vs. site** section: at the top of `report.md`, and in the Coverage section of `report.html`. Each planned event lands in one bucket:
|
|
288
288
|
|
|
289
289
|
| Bucket | Meaning |
|
|
290
290
|
|---|---|
|
|
@@ -301,7 +301,7 @@ When the plan is not the site's first, the header compares it with the version s
|
|
|
301
301
|
|
|
302
302
|
### Tag inventory
|
|
303
303
|
|
|
304
|
-
Every report also has a **tag inventory**: each vendor the site sent requests to, marked first party when its requests went to the site's own domain, third party when they went to the vendor, or both. It shows how many pages the vendor was seen on and the cookies tied to it. Cookies that no vendor owns are listed apart, as first or third party. Only cookie names are recorded, never their values. It is
|
|
304
|
+
Every report also has a **tag inventory**: each vendor the site sent requests to, marked first party when its requests went to the site's own domain, third party when they went to the vendor, or both. It shows how many pages the vendor was seen on and the cookies tied to it. Cookies that no vendor owns are listed apart, as first or third party. Only cookie names are recorded, never their values. It is the **Inventory** section of `report.html` and a table in `report.md`.
|
|
305
305
|
|
|
306
306
|
### Folder layout and plan IDs
|
|
307
307
|
|
|
@@ -311,13 +311,32 @@ Plans and runs are kept in `aitd/` in the folder you run the tool from:
|
|
|
311
311
|
aitd/
|
|
312
312
|
example.com/ the site's host, without www.
|
|
313
313
|
plans/2026-10-16_0900/ measurement-plan.json, plan.html, plan.xlsx (after an export)
|
|
314
|
-
runs/2026-10-16_0912/ report.html, report.md, timeline.csv, capture.json
|
|
315
|
-
and plan-check.json (with --against)
|
|
314
|
+
runs/2026-10-16_0912/ report.html, report.md, timeline.csv, capture.json, steps.json,
|
|
315
|
+
junit.xml, summary.json and plan-check.json (with --against)
|
|
316
|
+
history.json a small summary of every saved run, for the trend (see below)
|
|
316
317
|
```
|
|
317
318
|
|
|
318
319
|
An ID is the local time, `YYYY-MM-DD_HHMM`. When that minute is already taken, seconds are added (`_HHMMSS`). Nothing is ever replaced: each draft, import and run gets a new folder. Use `--data-dir <dir>` to keep the `aitd/` folder somewhere else. `replay` and `--save-site` folders are not affected.
|
|
319
320
|
|
|
320
|
-
Each report
|
|
321
|
+
Each report has a "Changed since last run" section that compares the run with the site's previous one in `runs/`, for example `2 events fixed, 1 new problem`, and lists each one: what was fixed (with what it was), what is new, and what is still open. When both runs used `--against`, it compares plan coverage (an event that went from Missing to Implemented is fixed); otherwise it compares the health findings. The first run for a site says there is nothing to compare yet. There is no flag to set. The comparison is also saved under `changes` in `capture.json`.
|
|
322
|
+
|
|
323
|
+
### Trend over runs
|
|
324
|
+
|
|
325
|
+
From the second saved run on, the tool also looks at the site's last 30 runs together. After the counts, the CLI prints the verdict line and up to three trend lines:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
2 broken · 1 risk · 1 warning · 0 info · 4 ok
|
|
329
|
+
Verdict: Fail · purchase tracking is broken on mobile · score 25/100
|
|
330
|
+
Trend over 5 runs (2026-10-01 → 2026-10-07): improving. Now 0 broken · 1 risk · 2 warning; 3 runs ago 2 broken · 1 risk · 0 warning.
|
|
331
|
+
Open longest: Meta PageView before_consent in 4 places since 2026-10-01 (5 runs); GA4 add_to_cart duplicate in 4 places since 2026-10-03 (3 runs).
|
|
332
|
+
Journeys that do not finish every time: checkout (mobile) 80%.
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The same lines, with a text sparkline of the runs (one character per run, `·` for a run whose journeys did not all finish), make the **Trend** section of `report.md` and `report.html`. A problem counts as *open* while the last run that completed its journey still showed it; a run whose journey stopped early proves nothing, so it neither fixes nor reintroduces anything. *Flapping* problems (seen, gone, back again) are named, because they usually depend on timing or on the product the AI picked rather than on a fix. The direction compares the last few complete runs with the ones before them.
|
|
336
|
+
|
|
337
|
+
`aitd history --site <url>` prints the trend on its own: one row per run with its counts and score (`3·broken + 2·risk + warning`, lower is better), the sparkline and the lines above. `--limit <n>` reads only the newest `n` runs (default 30), `--lang tr` switches the lines to Turkish, and `--json` prints the trend series, the per-finding timeline (first seen, last seen, open since, fixed in, flapping) and the journey completion rates for scripts.
|
|
338
|
+
|
|
339
|
+
The summaries live in `aitd/<domain>/history.json`, about 2 KB per run, which every saved run updates after writing its `capture.json`. The file is a cache: a run folder that is not in it is read from its `capture.json`, a folder that was deleted drops out on the next update, and deleting the file is always safe. Runs sent to `--out` and replays are not part of the history.
|
|
321
340
|
|
|
322
341
|
## Choose what to test
|
|
323
342
|
|
|
@@ -359,8 +378,11 @@ Each browsing step is one model call, so usage grows with **journeys × viewport
|
|
|
359
378
|
- **Expect bigger pages to cost more.** Each step sends the page view (up to 250 elements and 3,000 characters of text) to the model, so busy pages use more tokens per step than simple ones.
|
|
360
379
|
- **Repeat runs start from the last route.** Once a journey has completed on a site, the next run gives the AI the pages that run went through, in order, so it goes straight to the goal instead of exploring. The route is reused only for the same goal, from runs made with 0.14.0 or later, and only for AI runs. It isn't used with `--out` or `replay`. If the site changed and the route no longer fits, the AI takes another path.
|
|
361
380
|
- **Use `--events`** to keep the report focused. It doesn't shorten browsing, so it barely changes usage.
|
|
362
|
-
- **Replay without AI.**
|
|
381
|
+
- **Replay without AI.** Every completed run saves the steps it took as `steps.json` in its run folder, and `--scripted --no-ai` repeats the last completed run's steps for a journey that has no fixed steps of its own. Run a journey once with the AI, then replay it on every deploy. See [Save the steps and replay them without AI](#save-the-steps-and-replay-them-without-ai).
|
|
363
382
|
- **Use a chat app.** Over MCP, the chat app's own model does the browsing, so no API key is used at all.
|
|
383
|
+
- **Transient failures don't cost a run.** A rate limit, an overloaded answer or a dropped connection from the model API (429, 529, 5xx and the like) is tried again up to 3 times with a growing wait, honoring `retry-after`, and a page load the network dropped or that timed out is tried once more after 2 seconds. Other errors, such as a bad key or a refused page, fail at once.
|
|
384
|
+
|
|
385
|
+
Running time is another matter: `--concurrency 3` runs up to three journeys at once, each in its own browser, so a run with several journeys and viewports finishes much sooner. It makes the same model calls, only closer together, so stay at `1` or `2` when the provider rate-limits you.
|
|
364
386
|
|
|
365
387
|
### Check a new version against saved sites
|
|
366
388
|
|
|
@@ -377,14 +399,34 @@ The first run's findings become the expected ones. When a change is right, for e
|
|
|
377
399
|
|
|
378
400
|
### Save the steps and replay them without AI
|
|
379
401
|
|
|
380
|
-
|
|
402
|
+
Every completed run saves the steps each journey took in its run folder, `aitd/<domain>/runs/<id>/steps.json`. A `--scripted --no-ai` run then repeats them for every journey that has no `steps` of its own: it takes the site's newest run that completed the journey, on the same viewport when there is one and on another otherwise, and says which run it is replaying under the journey's `▶` line. So the AI is needed once per journey, and every run after that costs nothing:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
ai-tag-debugger run --site https://www.yourshop.com --journey purchase
|
|
406
|
+
ai-tag-debugger run --site https://www.yourshop.com --journey purchase --scripted --no-ai
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
When the site changes and a saved step no longer finds its element, the journey ends as incomplete with a note that says the steps of that run no longer fit; run it with the AI once more and the next replay uses the new steps. A journey that has never completed on the site stops the run before the browser starts. Only runs saved in `aitd/` are replayed from: a run sent elsewhere with `--out` or made over MCP still writes `steps.json`, but the next run doesn't look there. The steps are stored as they were taken, typed values included (an email address typed into a form is in `steps.json` as typed, since a replay has to type it again), so use test data on the journeys and keep the run folder out of version control.
|
|
410
|
+
|
|
411
|
+
To keep the steps in your repository instead, `--save-steps <file>` writes them into `journeys[].steps` of a config file. It creates the file if it's missing, and it also saves the run's plan when the config has none. The same config then repeats the journey with no AI model and no API key:
|
|
381
412
|
|
|
382
413
|
```bash
|
|
383
414
|
ai-tag-debugger run --site https://www.yourshop.com --journey purchase --save-steps ai-tag-debugger.config.json
|
|
384
415
|
ai-tag-debugger run --config ai-tag-debugger.config.json --scripted --no-ai
|
|
385
416
|
```
|
|
386
417
|
|
|
387
|
-
When a journey ran on both viewports, only the first viewport's steps are kept.
|
|
418
|
+
When a journey ran on both viewports, only the first viewport's steps are kept. Run the second command on every deploy to check the same journey without any model calls.
|
|
419
|
+
|
|
420
|
+
### Accept known findings
|
|
421
|
+
|
|
422
|
+
`--fail-on` fails the build on every finding at a level. When a finding is known and won't be fixed soon, for example a vendor pixel that fires before consent, a baseline lets later runs fail only on new findings. Run once with `--update-baseline` to write every current finding into a file, look through it, and commit it:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
ai-tag-debugger run --config ai-tag-debugger.config.json --scripted --no-ai --baseline baseline.json --update-baseline
|
|
426
|
+
ai-tag-debugger run --config ai-tag-debugger.config.json --scripted --no-ai --baseline baseline.json --fail-on warning
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The file holds one entry per finding, with a `key` the run matches on (the check, platform, event, journey, viewport and, for missing parameters, which ones) and the rest for whoever reads it. Delete an entry to start failing on it again, or add a `"note"` saying why it's accepted; notes survive the next `--update-baseline`. An accepted finding stays in the report with an "Accepted" label, is left out of the counts and the AI explanation, and doesn't count toward `--fail-on`. After the counts, the run prints how many findings were accepted and how many baseline entries are stale, that is no longer seen, so the file can be trimmed. A problem accepted on some viewports or journeys only is reported as partially accepted and still counted, and a baseline written for another site prints a warning. `"baseline": "baseline.json"` in the config, relative to the config file, saves passing the option; the MCP `run_audit` tool takes `baseline` too.
|
|
388
430
|
|
|
389
431
|
## Configure your site
|
|
390
432
|
|
|
@@ -409,17 +451,19 @@ When a journey ran on both viewports, only the first viewport's steps are kept.
|
|
|
409
451
|
|---|---|
|
|
410
452
|
| `site` | Base URL. Use staging when you can. |
|
|
411
453
|
| `plan` | Measurement plan: a file path or an inline object (below). Optional with ready-made journeys. A run with `--against` uses that plan instead. |
|
|
412
|
-
| `gtm_container` | Optional GTM container export (Admin → Export container). Each broken event then names the tag that sends it and the trigger it fires on (or says no tag sends it),
|
|
454
|
+
| `gtm_container` | Optional GTM container export (Admin → Export container). Each broken event then names the tag that sends it and the trigger it fires on (or says no tag sends it), and says whether that trigger fired in the journey and why not (paused tag, no push with the event, a failed condition with the value seen, a blocking trigger, denied consent), evaluated from the captured dataLayer without an AI model. |
|
|
413
455
|
| `viewports` | `desktop` and/or `mobile`. |
|
|
456
|
+
| `concurrency` | Default `1`. How many journeys (and consent scenarios) run at once, each in its own browser, up to `8`. Same as `--concurrency`. |
|
|
414
457
|
| `journeys` | Optional when you pick ready-made journeys with `--journey`, describe one with `--goal`, or check against a plan with `--against`. |
|
|
415
458
|
| `journeys[].goal` | What the AI should do, in plain language. |
|
|
416
459
|
| `journeys[].start` | Optional start path (default `/`). |
|
|
417
|
-
| `journeys[].steps` | Optional fixed steps for `--scripted` runs: `{ "action": "click", "selector": "#add-to-cart" }`. |
|
|
460
|
+
| `journeys[].steps` | Optional fixed steps for `--scripted` runs: `{ "action": "click", "selector": "#add-to-cart" }`. Without them, `--scripted` repeats the steps of the site's last completed run of the journey. |
|
|
418
461
|
| `allowed_hosts` | Other hosts journeys may visit, e.g. `["checkout.payments-provider.com"]` for a hosted checkout. The browser otherwise stays on the `site` host and its subdomains. Same as `--allow-host`. |
|
|
419
462
|
| `providers` | Tracking platforms to check, e.g. `["ga4", "meta"]`. Default `["ga4", "google_ads", "meta", "tiktok"]`, plus any other platform the site sends to while this is unset. Same as `--platforms`. |
|
|
420
463
|
| `campaign_checks` | Default `true`. Each run opens the first page like an ad click (UTM parameters, `gclid`, `fbclid`, or the ones already in the start address) and checks they survive redirects and reach the hits. Set to `false` to open the plain address. |
|
|
421
464
|
| `block_hits` | Default `true`. Set to `false` to let hits reach your real endpoints. |
|
|
422
465
|
| `consent_scenarios` | Default `true`. Set to `false` to skip the [consent scenarios](#consent-scenarios), which visit the start page once per consent choice. |
|
|
466
|
+
| `baseline` | Optional path, relative to the config file, of a baseline of accepted findings. Same as `--baseline`. See [Accept known findings](#accept-known-findings). |
|
|
423
467
|
| `language` | Report language, `en` (default) or `tr`. Same as `--lang`. |
|
|
424
468
|
| `keep_bodies` | Default `false`. Set to `true` to store tracking requests as sent, without redacting user data. Same as `--keep-bodies`. |
|
|
425
469
|
| `branding` | Default `true`. Set to `false` to leave the Analitik İşler logo and the "Generated by AI Tag Debugger · analitikisler.com" line out of the reports. Same as `--no-branding`, or `branding: false` on the MCP `finish_audit` and `run_audit` tools. |
|
|
@@ -428,6 +472,8 @@ When a journey ran on both viewports, only the first viewport's steps are kept.
|
|
|
428
472
|
| `base_url`, `model` | Optional: the API address and model id, e.g. for `openai-compatible`. Same as `--base-url` and `--model`. |
|
|
429
473
|
| `notify` | Optional alerts after each saved run: `slack` (a webhook URL, or `true` to read `SLACK_WEBHOOK_URL`) and `email` (an address or a list). See [Alerts](#alerts). |
|
|
430
474
|
|
|
475
|
+
The config and the plan are checked as soon as they are read, before the browser starts. An error names the field and, for a fixed choice, the allowed values, for example `viewports[1]: must be one of desktop, mobile (got "tablet")` or `journeys[0].steps[2].action: must be one of goto, click, fill, select, scroll, wait`. A key the tool does not know is ignored with a warning that suggests the closest one (`Unknown key "viewport" ... Did you mean "viewports"?`), so an extra note in the file does not stop a run. Plans saved by `aitd plan` and imported from a spreadsheet get the same check.
|
|
476
|
+
|
|
431
477
|
### Alerts
|
|
432
478
|
|
|
433
479
|
After each saved run, `notify` sends the "Changed since last run" diff to your team, so a change on the site reaches you without opening a report:
|
|
@@ -442,7 +488,18 @@ After each saved run, `notify` sends the "Changed since last run" diff to your t
|
|
|
442
488
|
- **Slack** takes the webhook URL. `"slack": true` reads it from the `SLACK_WEBHOOK_URL` environment variable instead, so the URL stays out of the file.
|
|
443
489
|
- **Email** takes one address or a list. Mail goes through the server in `SMTP_URL`, for example `smtps://user:password@mail.example.com:465`. `SMTP_FROM` sets the sender (default: the first recipient). Use `smtps://`, which encrypts the connection from the start. A plain `smtp://` URL sends the login and the mail unencrypted, and STARTTLS isn't supported.
|
|
444
490
|
|
|
445
|
-
An alert goes out only for a saved run (not one sent to `--out`) that has an earlier run to compare with, and only when something changed since. The
|
|
491
|
+
An alert goes out only for a saved run (not one sent to `--out`) that has an earlier run to compare with, and only when something changed since. The subject carries the verdict first, then the counts and the headline: `shop.example: Fail · 2 new problems · purchase tracking is broken on mobile`. The body repeats the verdict line, then lists what was fixed (with what it *was*) and what is new, each new item with its severity and owner, then the trend line with its sparkline, the flapping problems, and "Still open after N runs" when a problem reaches 5, 10, 20 runs open (then every 10), so the same open list does not repeat in every message:
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
shop.example
|
|
495
|
+
Verdict: Fail · purchase tracking is broken on mobile · score 25/100
|
|
496
|
+
1 problem fixed, 1 new problem. Compared with run 2026-10-15_1000, by health findings (2026-10-16_1000).
|
|
497
|
+
- Fixed: GA4 page_view (was: Sent before consent)
|
|
498
|
+
- New: Meta Purchase · Broken · "Purchase" was sent without "value". · Developer
|
|
499
|
+
Trend over 5 runs (2026-10-12 → 2026-10-16): worsening. Now 2 broken · 1 risk · 0 warning; 3 runs ago 0 broken · 1 risk · 0 warning. `▃▃·▅█`
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The trend never triggers an alert on its own. If a channel fails, the run prints why and carries on. The exit code still follows `--fail-on`.
|
|
446
503
|
|
|
447
504
|
## CLI
|
|
448
505
|
|
|
@@ -453,12 +510,15 @@ aitd plan list --site <url> [--data-dir <dir>]
|
|
|
453
510
|
aitd plan import <file> --site <url> [--data-dir <dir>]
|
|
454
511
|
aitd plan export <plan> --site <url> [--format xlsx|csv] [--data-dir <dir>]
|
|
455
512
|
aitd run --site <url> (--against <plan> | --journey <id> | --goal "<text>") [--config <file>] [--events <names>]
|
|
456
|
-
[--max-steps <n>] [--viewport desktop|mobile] [--platforms <list>] [--data-dir <dir>]
|
|
513
|
+
[--max-steps <n>] [--viewport desktop|mobile] [--concurrency <n>] [--platforms <list>] [--data-dir <dir>]
|
|
457
514
|
[--provider anthropic|openai|gemini|openai-compatible] [--model <id>] [--lang en|tr] [--headed]
|
|
458
|
-
[--fail-on broken|risk|warning|never] [--
|
|
515
|
+
[--fail-on broken|risk|warning|never] [--baseline <file>] [--update-baseline]
|
|
516
|
+
[--scripted] [--no-ai] [--save-site <dir>] [--save-steps <file>]
|
|
459
517
|
aitd replay <dir>... [--update] [--out <dir>]
|
|
460
518
|
aitd mcp [--http] [--port <n>] [--host <addr>] [--token <secret>] [--public-url <https url>] [--reports <dir>]
|
|
461
519
|
[--allow-private-network]
|
|
520
|
+
aitd doctor [--site <url>] [--data-dir <dir>]
|
|
521
|
+
aitd history --site <url> [--limit <n>] [--json] [--lang en|tr] [--data-dir <dir>]
|
|
462
522
|
```
|
|
463
523
|
|
|
464
524
|
`aitd` is a short alias for `ai-tag-debugger` and takes the same commands and options. Run `aitd <command> --help` for the essential options of each command.
|
|
@@ -467,24 +527,29 @@ aitd mcp [--http] [--port <n>] [--host <addr>] [--token <secret>] [--public-url
|
|
|
467
527
|
|
|
468
528
|
| Option | Commands | Meaning |
|
|
469
529
|
|---|---|---|
|
|
470
|
-
| `--site <url>` | plan, run | The site. Required for `plan`; for `run`, needed unless `--against` or the config gives it. |
|
|
530
|
+
| `--site <url>` | plan, run, doctor, history | The site. Required for `plan` and `history`; for `run`, needed unless `--against` or the config gives it. `doctor` only checks that the address is http(s) and its host resolves. |
|
|
471
531
|
| `--against <plan>` | run | Check against a plan: `latest` (or `last`), `first`, a plan ID, or a path to a plan file or folder. |
|
|
472
532
|
| `--journey <id>` | run | A ready-made journey (repeatable). |
|
|
473
533
|
| `--goal "<text>"` | run | A journey in plain language (repeatable). |
|
|
474
534
|
| `--config <file>` | run | Take the site, journeys and plan from a config file. |
|
|
475
535
|
| `--events <names>` | run | Only check these events, e.g. `purchase,add_to_cart`. |
|
|
476
536
|
| `--viewport <name>` | run | `desktop` or `mobile` (repeatable). |
|
|
537
|
+
| `--concurrency <n>` | run | How many journeys to run at once, each in its own browser: `1` (default) to `8`. Side by side, each journey's log lines start with `[journey/viewport]`, and `--headed` opens that many browser windows. Same as `concurrency` in the config. |
|
|
477
538
|
| `--platforms <list>` | run | Platforms to check, e.g. `ga4,meta`. Replaces `--providers`, which still works for one more minor version. |
|
|
478
539
|
| `--max-steps <n>` | plan, run | The most AI steps per journey (default 25 for `plan`). |
|
|
479
|
-
| `--lang <code>` | run | Report language: `en` or `tr`. |
|
|
540
|
+
| `--lang <code>` | run, history | Report language: `en` or `tr`. For `history`, the language of the trend lines. |
|
|
480
541
|
| `--fail-on <level>` | run | Exit with 1 at this level: `broken` (default), `risk`, `warning` or `never`. |
|
|
542
|
+
| `--baseline <file>` | run | A file of accepted findings. They are still reported, but don't count toward `--fail-on`. See [Accept known findings](#accept-known-findings). |
|
|
543
|
+
| `--update-baseline` | run | Write every finding of this run into the baseline file after the run, creating it if needed. |
|
|
481
544
|
| `--headed` | plan, run | Show the browser window. |
|
|
482
545
|
| `--provider <name>`, `--model <id>` | plan, run | The AI provider and model. Default: whichever API key is set. |
|
|
483
|
-
| `--scripted` | run | Replay each journey's fixed steps, with no AI navigation. |
|
|
546
|
+
| `--scripted` | run | Replay each journey's fixed steps, with no AI navigation. A journey without steps repeats those of the site's last completed run. |
|
|
484
547
|
| `--no-ai` | run | No AI model at all. Implies `--scripted`. |
|
|
485
548
|
| `--save-site <dir>` | run | Save the site's responses for `replay`. |
|
|
486
549
|
| `--save-steps <file>` | run | Write the steps each journey took into a config file. |
|
|
487
|
-
| `--data-dir <dir>` | plan, run | Where `aitd/` (plans and runs) is kept. Default: `./aitd`. Replaces `run --out`. |
|
|
550
|
+
| `--data-dir <dir>` | plan, run, doctor, history | Where `aitd/` (plans and runs) is kept. Default: `./aitd`. Replaces `run --out`. |
|
|
551
|
+
| `--limit <n>` | history | How many of the site's newest runs the trend reads. Default: 30. |
|
|
552
|
+
| `--json` | history | Print the trend series, the finding timeline and the journey stability as JSON instead of text. |
|
|
488
553
|
| `--format xlsx\|csv` | plan export | The spreadsheet format. Default: `xlsx`. |
|
|
489
554
|
| `--update` | replay | Make this replay's findings the expected ones. |
|
|
490
555
|
| `--out <dir>` | replay | Where to write the replay reports. For `run`, `--out` still works as a hidden alias for one minor version and prints a note; use `--data-dir`. |
|
|
@@ -496,27 +561,53 @@ aitd mcp [--http] [--port <n>] [--host <addr>] [--token <secret>] [--public-url
|
|
|
496
561
|
|
|
497
562
|
Removed in 0.10.0: `plan --from`, `plan --out` and `plan --force`. Drafting a plan always saves a new folder, and a removed flag prints what to run instead.
|
|
498
563
|
|
|
499
|
-
The exit code is `1` when findings reach `--fail-on` (default `broken`), so it can gate a deploy. See [`examples/github-actions/ai-tag-debugger.yml`](examples/github-actions/ai-tag-debugger.yml) for running it after every deployment.
|
|
564
|
+
The exit code is `1` when findings reach `--fail-on` (default `broken`), so it can gate a deploy. Findings accepted in a [baseline](#accept-known-findings) don't count. See [`examples/github-actions/ai-tag-debugger.yml`](examples/github-actions/ai-tag-debugger.yml) for running it after every deployment.
|
|
565
|
+
|
|
566
|
+
**Test results in CI.** Every run also writes `junit.xml`, where each journey is a test suite and each finding a test case, failed when it reaches `--fail-on`, so GitHub Actions, GitLab and Jenkins show the findings as test failures. In GitHub Actions, upload it and hand it to a JUnit reporter action:
|
|
567
|
+
|
|
568
|
+
```yaml
|
|
569
|
+
- run: aitd run --config ai-tag-debugger.config.json --data-dir tracking-report
|
|
570
|
+
- uses: actions/upload-artifact@v6
|
|
571
|
+
if: always()
|
|
572
|
+
with:
|
|
573
|
+
name: junit
|
|
574
|
+
path: tracking-report/**/junit.xml
|
|
575
|
+
# Optional: any JUnit reporter action turns the file into annotations on the run.
|
|
576
|
+
- uses: dorny/test-reporter@v2
|
|
577
|
+
if: always()
|
|
578
|
+
with:
|
|
579
|
+
name: tracking
|
|
580
|
+
path: tracking-report/**/junit.xml
|
|
581
|
+
reporter: java-junit
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
In GitLab, point `artifacts: reports: junit:` at the same path. Next to it, `summary.json` holds the counts, the verdict (`status`: `fail`, `attention` or `pass`, and the `headline` key such as `fail_broken`), the data quality `score` (0 to 100), the trend `direction` over the saved runs, the journeys, every finding (check, severity, platform, event, journey, viewport, title, message), the run folder's name as `run` and the names of the other files as `files`, relative to that folder, so a script can read the result without parsing `capture.json`, and nothing in it depends on the machine the run was made on:
|
|
585
|
+
|
|
586
|
+
```sh
|
|
587
|
+
jq '.counts.broken' aitd/example.com/runs/*/summary.json
|
|
588
|
+
jq -r '.verdict.status + " " + (.score | tostring)' aitd/example.com/runs/*/summary.json
|
|
589
|
+
```
|
|
500
590
|
|
|
501
591
|
**Scheduled runs.** To audit on a timer instead of after each deploy, use [`examples/github-actions/scheduled-run.yml`](examples/github-actions/scheduled-run.yml) (weekly by default, with a cron line you can change). It saves the history between runs in a cache, so "Changed since last run" keeps working on a fresh runner. On your own server, [`examples/cron/`](examples/cron) has a script and a crontab line that do the same.
|
|
502
592
|
|
|
503
593
|
Output:
|
|
504
594
|
|
|
505
|
-
- `report.html`: one file
|
|
506
|
-
- **
|
|
507
|
-
- **Findings**:
|
|
508
|
-
- **Journeys**: one journey at a time.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
Events and requests can be filtered by source. GA4 item strings (`pr1`…) and dataLayer `ecommerce.items` are shown as item cards, with custom item parameters in their own group. The report has no scripts: tabs and filters are plain HTML and CSS (they need Chrome 105, Safari 15.4 or Firefox 121 or newer), and printing shows every tab. The logos are embedded, so nothing is loaded from the internet. `--no-branding` leaves out the Analitik İşler logo and credit line.
|
|
595
|
+
- `report.html`: one file, one screen at a time. A collapsible rail on the left lists seven sections; every section, filter and card has its own URL you can bookmark or share, and a link from one section opens the right card in another (a finding's "Where" chip opens that hit in its step).
|
|
596
|
+
- **Summary**: the data quality score as a ring, coloured red below 50, amber to 89 and green from 90, the verdict and its headline, up to three lines saying what cost the points (none for a perfect score), status chips that open the findings filtered by severity, and six cards about the run: vendors checked, journeys with the platforms seen on each, events checked, consent, the AI provider and the run itself.
|
|
597
|
+
- **Findings**: a filter bar (severity, owner, platform, journey, viewport, search) over three groups: failed, unverified and passed. Every row starts collapsed and opens into **Evidence**, **Fix**, **Where** and **Verify** tabs: the impact, the likely cause, the evidence with missing parameters marked, numbered fix steps, the steps where it happened, and the tools to check the fix in, such as Tag Assistant, GA4 DebugView, Meta Test Events or TikTok Pixel Helper. **Changed since last run** sits above the groups when the site has an earlier run.
|
|
598
|
+
- **Journeys**: one journey and viewport at a time. The steps on the left, named after the pages ("Open Home", "Add to cart") and grouped by page, with the hits each step produced; the selected step on the right with **Hits**, **Data Layer** and **Requests** tabs. Each hit shows its event, key parameters and consent state, and opens into the decoded parameters, item cards and the raw request. A batched GA4 request shows each of its events. It opens on the first step with a problem.
|
|
599
|
+
- **Consent**: one line per platform with what it received before a choice, after accept and after reject, the Consent Mode default and updates with their timing, and the [consent scenarios](#consent-scenarios) table.
|
|
600
|
+
- **Coverage**: whether each planned event was sent on each journey, with the parameters it still needs, conversion integrity, and, with `--against`, the [Plan vs. site](#plan-vs-site) buckets.
|
|
601
|
+
- **Inventory**: the [tag inventory](#tag-inventory), one line per platform, opening into its hosts, pages and cookies.
|
|
602
|
+
- **Run**: the facts of the run (site, version, AI provider and model, plan, journeys, viewports, platforms, timing, files) and, when the site has earlier runs, the **Trend**.
|
|
603
|
+
|
|
604
|
+
Every card starts collapsed except the Summary; each section has Expand all and Collapse all. The page carries one small script, allowed by its hash in the page's Content Security Policy, for the rail, the filters, the tabs and the deep links; without JavaScript the sections stack into one document, which is also the print layout. Keyboard: `[` folds the rail, `1`–`7` jump to a section. The logos are embedded, so nothing is loaded from the internet. `--no-branding` leaves out the Analitik İşler logo and credit line.
|
|
516
605
|
- `timeline.csv`: the same timeline as one row per hit, dataLayer push and network call, ready for Excel or Google Sheets.
|
|
517
|
-
- `report.md`: the report in Markdown
|
|
518
|
-
- `capture.json`: everything captured, for your own analysis.
|
|
606
|
+
- `report.md`: the report in Markdown for a pull request comment, a Slack message or a mail, around 100 lines and under 8 KB for a four-journey run. Its title is the verdict (`# shop.example: purchase tracking is broken on mobile`), then one meta line, the status with the data quality score, and the counts. Then, in this order: **Changed since last run** and **Trend** (when the site has earlier runs), **Plan vs. site** (with `--against`), **Priorities** (the to-do list per owner: Developer, Tag Manager, Marketing), **Findings** (up to five full blocks with Where, Impact, Cause, Evidence, Also fine on, Owner, Fix, How to verify and a link into `report.html`; the rest one line each under *Also*), **Consent** (the Consent Mode timing sentence and the scenario table with a reason in every cell), **What passed**, **Journeys** (one row per run, then only the steps that carry a problem), **Not checked** (what the run could not judge, with the config key that enables it) and the **Tag inventory**. The per-hit timeline is not in the file; `timeline.csv` has it.
|
|
607
|
+
- `capture.json`: everything captured, for your own analysis, including the tool `version`, the `ai` provider and the computed `metrics` (verdict, score, plan coverage, funnel, platform parity, consent compliance, conversion integrity, timing, priorities).
|
|
519
608
|
- `plan-check.json`: with `--against`, one entry per planned event with its bucket.
|
|
609
|
+
- `junit.xml`: the findings as JUnit test results, one suite per journey and viewport, for CI systems to show as test failures (see above).
|
|
610
|
+
- `summary.json`: the counts, verdict, score, trend direction, journeys, findings and file names of the run in one small file for scripts.
|
|
520
611
|
- `plan.html` (in the plan's folder): a readable page of the plan's events, parameters and user properties.
|
|
521
612
|
|
|
522
613
|
Reports end with a credit line, "Generated by AI Tag Debugger · analitikisler.com", where the domain is a plain link (no images, no tracking, and no referrer, so report URLs aren't leaked). `--no-branding` removes it along with the logo.
|
|
@@ -537,7 +628,7 @@ Tools the server offers:
|
|
|
537
628
|
| `browser_action` | One click, fill, select, goto, scroll or wait. Returns the page and the tracking hits that step sent. |
|
|
538
629
|
| `get_page` | Shows the current page. |
|
|
539
630
|
| `finish_audit` | Checks everything against your measurement plan (JSON), a saved one given as `against` (`latest`, `first`, a plan ID, or on a local server a path), or the ready-made journey's events, optionally only the `events` you name, and writes the full HTML report with the timeline. The site comes from the audit. |
|
|
540
|
-
| `run_audit` | Runs a config file end to end. With `against`, it checks the run against a saved plan and, when the config has no journeys, walks the journeys derived from that plan. Local only for file paths. |
|
|
631
|
+
| `run_audit` | Runs a config file end to end. With `against`, it checks the run against a saved plan and, when the config has no journeys, walks the journeys derived from that plan. `concurrency` runs that many journeys at once, each in its own browser (default 1), and `baseline` names a file of accepted findings written with `--update-baseline` (default: the config's `baseline`; see [Accept known findings](#accept-known-findings)). Local only for file paths. |
|
|
541
632
|
|
|
542
633
|
`against` takes the same values as the CLI's `--against`. Over HTTP it takes only `latest`, `first` or a plan ID, never a path. Plans are the ones saved by `aitd plan --site <url>` or by `make_plan`. Ask for a plan when you want to know what to track, and for an audit when you want to know what the site sends.
|
|
543
634
|
|
|
@@ -613,6 +704,8 @@ A hosted server, so you don't need a tunnel, is part of the [Analitik İşler](h
|
|
|
613
704
|
```bash
|
|
614
705
|
npm run setup # checks Node, installs the dependencies and Chromium, says whether an AI provider key is set
|
|
615
706
|
npm test # parsers, checks, each AI provider and the MCP server, end to end against the demo shop
|
|
707
|
+
npm run test:unit # only the tests that need no browser, in about ten seconds
|
|
708
|
+
npm run lint # ESLint, the recommended rules only: undefined names, unused imports and the like
|
|
616
709
|
```
|
|
617
710
|
|
|
618
711
|
The repository also has a dev container (`.devcontainer/devcontainer.json`). Open it in VS Code with the Dev Containers extension, or in GitHub Codespaces, and it runs `npm run setup` for you, Chromium's system libraries included. To use an AI model there, add your key as a Codespaces secret or export it in the container's terminal.
|