Download index.html from getreadystack/tdm-reservation-lint: direct link, hf CLI and curl.
- Browser
- Download file 48.9 kB
-
https://huggingface.co/spaces/getreadystack/tdm-reservation-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/tdm-reservation-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/tdm-reservation-lint/resolve/main/index.html
48.9 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Robots txt AI crawler opt out — check your robots.txt free</title> | |
| <style> | |
| :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512} | |
| .wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px} | |
| textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box} | |
| .bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer} | |
| input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px} | |
| table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65} | |
| .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600} | |
| .note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa} | |
| </style> | |
| <script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script> | |
| <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/tdm-reservation-lint"></head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>Robots txt AI crawler opt out — check your robots.txt free</h1> | |
| <p class="lede">Lints robots.txt for the machine-readable TDM reservation EU law expects: dead crawler tokens, shadowed groups, missing AI trainers, and rules that never apply. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false"># robots.txt | |
| Disallow: /wp-admin/ | |
| User-agent: * | |
| Disallow: /wp-admin/ | |
| Disallow: /checkout/ | |
| Crawl-delay: 10 | |
| Noindex: /drafts/ | |
| User-agent: GPTBot | |
| Disallow: / | |
| User-agent: anthropic-ai | |
| Disallow: / | |
| User-agent: ChatGPT-Bot | |
| Disallow: / | |
| User-agent: CCBot | |
| Disallow: | |
| User-agent: ChatGPT-User | |
| Disallow: / | |
| User-agent: Googlebot | |
| Disallow: private/ | |
| User-agent: GPTBot | |
| Disallow: / | |
| Allow all: yes | |
| Sitemap: /sitemap.xml</textarea> | |
| <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div> | |
| <div id="out" aria-live="polite"></div> | |
| <div id="paid"><strong>Free here:</strong> Lints the robots.txt you have open and names every line that fails to reserve your content, with the exact replacement line.. <strong>In the editor:</strong> Scans every robots.txt in the workspace and writes the corrected robots.txt plus a matching /.well-known/tdmrep.json and a dated Markdown evidence file you keep. — that part asks for a licence key.</div> | |
| <p class="note">Same engine as the VS Code extension, byte for byte.</p> | |
| </div> | |
| <script>var TDM_RULES = { | |
| "rules": [ | |
| {"id": "rule_before_group", "sev": "error", "title": "Allow/Disallow before any User-agent", "fix": "Move the line under a User-agent group. RFC 9309 parsers ignore rules that are not inside a group."}, | |
| {"id": "legacy_token", "sev": "error", "title": "Retired crawler token", "fix": "Replace with the token the vendor publishes today."}, | |
| {"id": "unknown_token", "sev": "error", "title": "Token no published crawler uses", "fix": "Use the exact published token; robots.txt matching is on the token, so a near-miss reserves nothing."}, | |
| {"id": "no_reservation", "sev": "error", "title": "No AI training crawler is reserved against", "fix": "Add a group per training crawler with Disallow: / so the reservation is machine-readable."}, | |
| {"id": "trainer_gap", "sev": "error", "title": "Training crawlers left unreserved", "fix": "Add a group with Disallow: / for each missing token."}, | |
| {"id": "trainer_allowed", "sev": "error", "title": "Training crawler explicitly allowed", "fix": "An empty Disallow: or Allow: / grants full access. Use Disallow: / to reserve."}, | |
| {"id": "star_group_shadowed", "sev": "warn", "title": "Its own group cancels the * group", "fix": "A crawler obeys only its most specific group; repeat the * group paths inside it."}, | |
| {"id": "noindex_line", "sev": "warn", "title": "Noindex in robots.txt", "fix": "Google stopped honouring Noindex in robots.txt on 2019-09-01. Use a meta robots tag or X-Robots-Tag header."}, | |
| {"id": "crawl_delay", "sev": "info", "title": "Crawl-delay is not in RFC 9309", "fix": "Googlebot and GPTBot ignore it. Rate-limit at the server or CDN instead."}, | |
| {"id": "path_no_slash", "sev": "error", "title": "Path does not start with /", "fix": "Paths are matched from the site root; write /path, not path."}, | |
| {"id": "sitemap_relative", "sev": "warn", "title": "Sitemap is not an absolute URL", "fix": "Sitemap: takes a full https:// URL, not a relative path."}, | |
| {"id": "duplicate_group", "sev": "warn", "title": "Same token in two groups", "fix": "Merge the groups; which one wins is parser-dependent."}, | |
| {"id": "unknown_directive", "sev": "warn", "title": "Line is not a robots.txt directive", "fix": "Parsers drop the line silently. Use User-agent, Allow, Disallow or Sitemap, or prefix it with #."}, | |
| {"id": "no_tdm_signal", "sev": "warn", "title": "No pointer to a TDMRep signal", "fix": "robots.txt is a crawler instruction. Add /.well-known/tdmrep.json and reference it in a comment so the reservation is machine-readable in the TDMRep sense."}, | |
| {"id": "agent_bot_blocked", "sev": "info", "title": "User-triggered fetcher blocked", "fix": "This token fetches a page because a person asked for it. Blocking it drops the citation and the referral without stopping training."} | |
| ], | |
| "trainers": { | |
| "GPTBot": "OpenAI, model training", | |
| "ClaudeBot": "Anthropic, model training", | |
| "Google-Extended": "Google, Gemini training (robots.txt token only)", | |
| "CCBot": "Common Crawl, the corpus most model builders start from", | |
| "Bytespider": "ByteDance", | |
| "meta-externalagent": "Meta", | |
| "Applebot-Extended": "Apple, training opt-out token", | |
| "PerplexityBot": "Perplexity index", | |
| "Amazonbot": "Amazon" | |
| }, | |
| "trainers_extra": ["Diffbot", "Omgilibot", "AI2Bot", "ImagesiftBot", "Timpibot", "YouBot", "cohere-training-data-crawler"], | |
| "agents": { | |
| "ChatGPT-User": "fetches a page when a person asks ChatGPT about it", | |
| "OAI-SearchBot": "builds the ChatGPT search index that links back to you", | |
| "Claude-User": "fetches a page when a person asks Claude about it", | |
| "Claude-SearchBot": "builds the search index Claude cites from", | |
| "Perplexity-User": "fetches a page a person asked Perplexity about" | |
| }, | |
| "crawlers": ["Googlebot", "Googlebot-Image", "Googlebot-News", "Bingbot", "Applebot", "DuckDuckBot", "Baiduspider", "YandexBot", "Slurp", "facebookexternalhit", "Twitterbot", "LinkedInBot", "Slackbot", "Discordbot", "Pinterestbot", "AhrefsBot", "SemrushBot", "MJ12bot", "DotBot", "archive.org_bot", "ia_archiver"], | |
| "legacy": { | |
| "anthropic-ai": "ClaudeBot", | |
| "Claude-Web": "ClaudeBot", | |
| "FacebookBot": "meta-externalagent" | |
| }, | |
| "directives": ["user-agent", "allow", "disallow", "sitemap", "crawl-delay", "host", "clean-param", "request-rate", "visit-time", "noindex"] | |
| } | |
| ;</script> | |
| <script>/* TDM Reservation Lint — engine. Same file runs in Node (extension) and in the browser (free web page). */ | |
| (function () { | |
| 'use strict'; | |
| var DATA = (typeof module !== 'undefined' && module.exports) | |
| ? require('./rules.json') | |
| : window.TDM_RULES; | |
| var RULES = DATA.rules; | |
| var BY_ID = {}; | |
| RULES.forEach(function (r) { BY_ID[r.id] = r; }); | |
| var TRAINERS = DATA.trainers; | |
| var TRAINER_KEYS = Object.keys(TRAINERS); | |
| var AGENTS = DATA.agents; | |
| var LEGACY = DATA.legacy; | |
| var KNOWN = TRAINER_KEYS | |
| .concat(DATA.trainers_extra) | |
| .concat(Object.keys(AGENTS)) | |
| .concat(DATA.crawlers) | |
| .concat(Object.keys(LEGACY)); | |
| var KNOWN_LC = {}; | |
| KNOWN.forEach(function (t) { KNOWN_LC[t.toLowerCase()] = t; }); | |
| var LEGACY_LC = {}; | |
| Object.keys(LEGACY).forEach(function (t) { LEGACY_LC[t.toLowerCase()] = t; }); | |
| var DIRECTIVES = DATA.directives; | |
| function distance(a, b) { | |
| var m = a.length, n = b.length, prev = [], cur = [], i, j; | |
| for (j = 0; j <= n; j++) prev[j] = j; | |
| for (i = 1; i <= m; i++) { | |
| cur[0] = i; | |
| for (j = 1; j <= n; j++) { | |
| cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)); | |
| } | |
| prev = cur.slice(); | |
| } | |
| return prev[n]; | |
| } | |
| function nearest(token) { | |
| var t = token.toLowerCase(), best = null, bestD = 99; | |
| KNOWN.forEach(function (k) { | |
| var d = distance(t, k.toLowerCase()); | |
| if (d < bestD) { bestD = d; best = k; } | |
| }); | |
| return bestD <= Math.max(3, Math.ceil(t.length / 2)) ? best : null; | |
| } | |
| function parse(text) { | |
| var lines = String(text == null ? '' : text).split(/\r\n|\r|\n/); | |
| var out = []; | |
| lines.forEach(function (raw, idx) { | |
| var line = idx + 1; | |
| var body = raw.replace(/#.*$/, ''); | |
| var trimmed = body.trim(); | |
| if (!trimmed) { out.push({ line: line, raw: raw, kind: 'blank' }); return; } | |
| var m = trimmed.match(/^([^:]+):(.*)$/); | |
| if (!m) { out.push({ line: line, raw: raw, kind: 'bad', text: trimmed }); return; } | |
| out.push({ | |
| line: line, raw: raw, kind: 'pair', | |
| key: m[1].trim().toLowerCase(), | |
| val: m[2].trim() | |
| }); | |
| }); | |
| return out; | |
| } | |
| function group(entries) { | |
| var groups = [], cur = null, orphans = [], expectAgent = false; | |
| entries.forEach(function (e) { | |
| if (e.kind !== 'pair') return; | |
| if (e.key === 'user-agent') { | |
| if (!cur || !expectAgent) { cur = { agents: [], rules: [], line: e.line }; groups.push(cur); } | |
| cur.agents.push({ token: e.val, line: e.line }); | |
| expectAgent = true; | |
| return; | |
| } | |
| if (e.key === 'sitemap' || e.key === 'host') return; | |
| if (!cur) { orphans.push(e); return; } | |
| expectAgent = false; | |
| cur.rules.push(e); | |
| }); | |
| return { groups: groups, orphans: orphans }; | |
| } | |
| function pathsOf(g, key) { | |
| return g.rules.filter(function (r) { return r.key === key && r.val; }).map(function (r) { return r.val; }); | |
| } | |
| function isBlocked(g) { | |
| // reserved = at least one non-empty Disallow, and no Allow: / that re-opens everything | |
| var dis = pathsOf(g, 'disallow'); | |
| var allowAll = g.rules.some(function (r) { return r.key === 'allow' && r.val === '/'; }); | |
| return dis.length > 0 && !allowAll; | |
| } | |
| function isOpen(g) { | |
| var dis = pathsOf(g, 'disallow'); | |
| var emptyDis = g.rules.some(function (r) { return r.key === 'disallow' && !r.val; }); | |
| var allowAll = g.rules.some(function (r) { return r.key === 'allow' && r.val === '/'; }); | |
| return allowAll || (dis.length === 0 && emptyDis); | |
| } | |
| function check(text, opts) { | |
| opts = opts || {}; | |
| var today = opts.today || '2026-09-13'; | |
| var src = String(text == null ? '' : text); | |
| var entries = parse(src); | |
| var gr = group(entries); | |
| var groups = gr.groups; | |
| var findings = []; | |
| function add(id, msg, line) { | |
| var r = BY_ID[id]; | |
| findings.push({ check: id, sev: r ? r.sev : 'warn', msg: msg, line: line || 1 }); | |
| } | |
| // 1. rules outside a group | |
| gr.orphans.forEach(function (e) { | |
| add('rule_before_group', '"' + e.key + ': ' + e.val + '" sits above the first User-agent line, so every RFC 9309 parser drops it. Nothing on this line is in force.', e.line); | |
| }); | |
| // 13. unknown directives / unparseable lines | |
| entries.forEach(function (e) { | |
| if (e.kind === 'bad') { | |
| add('unknown_directive', '"' + e.text + '" has no "field: value" form, so parsers skip it without warning.', e.line); | |
| } else if (e.kind === 'pair' && DIRECTIVES.indexOf(e.key) === -1) { | |
| add('unknown_directive', '"' + e.key + '" is not a robots.txt field. The line is dropped silently — comment it out with # or delete it.', e.line); | |
| } else if (e.kind === 'pair' && e.key === 'noindex') { | |
| add('noindex_line', 'Google stopped honouring Noindex in robots.txt on 2019-09-01. This line does nothing; use a meta robots tag or an X-Robots-Tag header.', e.line); | |
| } | |
| }); | |
| // token-level checks | |
| var seen = {}, reserved = {}, tokensInFile = []; | |
| groups.forEach(function (g) { | |
| g.agents.forEach(function (a) { | |
| var tok = a.token, lc = tok.toLowerCase(); | |
| tokensInFile.push(lc); | |
| if (lc === '*') { return; } | |
| if (LEGACY_LC[lc]) { | |
| var real = LEGACY_LC[lc]; | |
| add('legacy_token', '"' + tok + '" is a retired token — the vendor no longer sends it, so this group matches nothing. The token in use today is "' + LEGACY[real] + '".', a.line); | |
| } else if (!KNOWN_LC[lc]) { | |
| var near = nearest(tok); | |
| add('unknown_token', 'No published crawler sends "' + tok + '". robots.txt matches the token literally, so this group reserves nothing' + (near ? ' — did you mean "' + near + '"?' : '.'), a.line); | |
| } | |
| if (seen[lc]) { | |
| add('duplicate_group', '"' + tok + '" already has a group on line ' + seen[lc] + '. Which group wins is parser-dependent — merge them.', a.line); | |
| } else { | |
| seen[lc] = a.line; | |
| } | |
| var canon = KNOWN_LC[lc]; | |
| if (canon && TRAINERS[canon] && isBlocked(g)) reserved[canon] = true; | |
| if (canon && TRAINERS[canon] && isOpen(g)) { | |
| add('trainer_allowed', '"' + canon + '" (' + TRAINERS[canon] + ') has its own group, but the group grants full access. An empty "Disallow:" or "Allow: /" is permission, not a reservation.', a.line); | |
| } | |
| if (canon && AGENTS[canon] && isBlocked(g)) { | |
| add('agent_bot_blocked', '"' + canon + '" ' + AGENTS[canon] + '. Blocking it removes the citation and the click-through; it does not stop training — that is a separate token.', a.line); | |
| } | |
| }); | |
| }); | |
| // 4/5. coverage | |
| var missing = TRAINER_KEYS.filter(function (t) { return !reserved[t]; }); | |
| if (missing.length === TRAINER_KEYS.length) { | |
| add('no_reservation', 'Not one training crawler is reserved against. Under Article 4(3) of Directive (EU) 2019/790 (in Germany § 44b UrhG) a text-and-data-mining reservation only counts when it is machine-readable — an absent rule reads as consent. Missing: ' + TRAINER_KEYS.join(', ') + '.', 1); | |
| } else if (missing.length) { | |
| add('trainer_gap', missing.length + ' of ' + TRAINER_KEYS.length + ' training crawlers are still unreserved: ' + missing.join(', ') + '. Each token is matched on its own — reserving against GPTBot says nothing to the others.', 1); | |
| } | |
| // 7. specific group cancels the * group | |
| var star = groups.filter(function (g) { return g.agents.some(function (a) { return a.token === '*'; }); })[0]; | |
| if (star) { | |
| var starPaths = pathsOf(star, 'disallow'); | |
| groups.forEach(function (g) { | |
| if (g === star) return; | |
| var own = pathsOf(g, 'disallow'); | |
| if (own.indexOf('/') !== -1) return; | |
| var lost = starPaths.filter(function (p) { return own.indexOf(p) === -1; }); | |
| if (lost.length) { | |
| g.agents.forEach(function (a) { | |
| if (a.token === '*') return; | |
| add('star_group_shadowed', '"' + a.token + '" obeys only this group, so the "User-agent: *" rules stop applying to it. Paths it can now reach: ' + lost.join(', ') + '.', a.line); | |
| }); | |
| } | |
| }); | |
| } | |
| // 9/10/11. line-level hygiene | |
| entries.forEach(function (e) { | |
| if (e.kind !== 'pair') return; | |
| if (e.key === 'crawl-delay') { | |
| add('crawl_delay', 'Crawl-delay is not part of RFC 9309; Googlebot and GPTBot ignore it. Rate-limit at the server or CDN if you need it.', e.line); | |
| } | |
| if ((e.key === 'allow' || e.key === 'disallow') && e.val && e.val.charAt(0) !== '/' && e.val.charAt(0) !== '*') { | |
| add('path_no_slash', '"' + e.val + '" does not start with "/". Paths are matched from the site root, so this rule never fires — write "/' + e.val.replace(/^\/+/, '') + '".', e.line); | |
| } | |
| if (e.key === 'sitemap' && !/^https?:\/\//i.test(e.val)) { | |
| add('sitemap_relative', 'Sitemap: takes an absolute URL. "' + e.val + '" is relative and is discarded.', e.line); | |
| } | |
| }); | |
| // 14. TDMRep pointer | |
| if (!/tdmrep|tdm-reservation/i.test(src)) { | |
| add('no_tdm_signal', 'Nothing here points at a TDMRep signal. robots.txt is an instruction to crawlers; the TDM Reservation Protocol expects /.well-known/tdmrep.json (or a tdm-reservation meta tag) as the machine-readable reservation. As of ' + today + ' add the file and name it in a comment.', 1); | |
| } | |
| findings.sort(function (a, b) { return a.line - b.line || a.check.localeCompare(b.check); }); | |
| return { findings: findings }; | |
| } | |
| var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = API; | |
| if (typeof window !== 'undefined') window.TDMENGINE = API; | |
| })(); | |
| </script> | |
| <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다. | |
| ; | |
| function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } | |
| function summaryLine(res) { | |
| const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length; | |
| return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.'; | |
| } | |
| function toText(res, label) { | |
| const f = (res && res.findings) || []; | |
| const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)]; | |
| f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); }); | |
| return lines.join('\n'); | |
| } | |
| function toHtml(res) { | |
| const f = (res && res.findings) || []; | |
| if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>'; | |
| const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join(''); | |
| return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>'; | |
| } | |
| var TDMREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = TDMREPORT; | |
| else window.TDMREPORT = TDMREPORT; | |
| </script> | |
| <script> | |
| (function(){ | |
| var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today'); | |
| today.value=new Date().toISOString().slice(0,10); | |
| function run(){ try { var res=window.TDMENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.TDMREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } } | |
| go.onclick=run; if (src.value.trim()) run(); | |
| })(); | |
| </script> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://www.npmjs.com/package/@readystack/tdm-reservation-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'tdm-reservation-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/tdm-reservation-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'tdm-reservation-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/tdm-reservation-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'tdm-reservation-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/tdm-reservation-lint <file> | |
| npx @readystack/tdm-reservation-lint --mcp | |
| docker run --rm -v "$PWD:/w" getreadystack/tdm-reservation-lint /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 15 rules TDM Reservation Lint checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"tdm-reservation-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Finds the robots.txt lines that reserve nothing — retired crawler tokens, shadowed groups, rules above the first User-agent.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A media and IP lawyer reviewing a TDM reservation bills from €200 an hour; this reads the file in about a second.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_GWWqGdsgkMU8DtX6kLrbb3vtkdLTWkz82XqUr09YevL" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div> | |
| <script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-17">Updated 2026-09-17</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-17","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku57144_result_card.jpg" alt="Fifteen findings in a robots.txt every validator passes" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Fifteen findings in one 32-line robots.txt — six errors, seven warnings, two notes — for EU publishers and web teams who believed they had opted out.</p> | |
| <pre><code>L1 warn no_tdm_signal nothing points at /.well-known/tdmrep.json | |
| L1 error trainer_gap 8 of 9 training crawlers still unreserved | |
| L2 error rule_before_group Disallow above first User-agent — dropped | |
| L7 info crawl_delay not in RFC 9309; GPTBot ignores it | |
| L8 warn noindex_line unhonoured by Google since 1 September 2019 | |
| L13 error legacy_token anthropic-ai retired — use ClaudeBot | |
| L16 error unknown_token ChatGPT-Bot: no such crawler | |
| L19 error trainer_allowed CCBot + empty Disallow: is permission | |
| L19 warn star_group_shadowed CCBot can now reach /wp-admin/, /checkout/ | |
| L22 info agent_bot_blocked ChatGPT-User blocked — costs the citation | |
| L25 warn star_group_shadowed Googlebot can now reach /wp-admin/, /checkout/ | |
| L26 error path_no_slash private/ has no leading slash — never fires | |
| L28 warn duplicate_group GPTBot already has a group on line 10 | |
| L31 warn unknown_directive "allow all" is not a robots.txt field | |
| L32 warn sitemap_relative /sitemap.xml is relative and is discarded</code></pre> | |
| <p>The file parsed. Any validator returns it clean, because validators check grammar — and grammar was never the problem.</p> | |
| <p>Article 4(3) of Directive (EU) 2019/790 lets a rightsholder reserve text-and-data-mining rights over content published online, and requires that reservation to be machine-readable; Germany transposed it as § 44b UrhG. Since 2 August 2025, Article 53(1)(c) of the EU AI Act makes providers of general-purpose AI models run a copyright policy that identifies and respects those reservations. A reservation a machine cannot parse is, to the machine, an absence — and an absence reads as consent.</p> | |
| <p>Five of the six errors sit on a line you can point at.</p> | |
| <p>Line 2 is a <code>Disallow: /wp-admin/</code> above the first <code>User-agent:</code> line. RFC 9309 defines rules as belonging to a group, and a rule outside one is dropped. It was never in force.</p> | |
| <p>Line 13 is <code>User-agent: anthropic-ai</code>, a retired token. Anthropic sends <code>ClaudeBot</code>, and robots.txt matches the token literally, so the group below it matched no crawler at all. The same trap catches <code>Claude-Web</code> and <code>FacebookBot</code>, replaced by <code>meta-externalagent</code>.</p> | |
| <p>Line 16 is <code>User-agent: ChatGPT-Bot</code>. No such crawler exists — it is the kind of token a generated config file produces: plausible, symmetrical, wrong.</p> | |
| <p>Line 19 is <code>User-agent: CCBot</code> followed by an empty <code>Disallow:</code>. That is not a reservation. It is full permission, in the shape of a restriction, to the crawler feeding the corpus most model builders start from.</p> | |
| <p>Line 26 is <code>Disallow: private/</code>. Paths are matched from the site root; without the leading slash it never fires.</p> | |
| <p>The sixth error is the file as a whole. Of the nine training crawlers tracked by name — GPTBot, ClaudeBot, Google-Extended, CCBot, Bytespider, meta-externalagent, Applebot-Extended, PerplexityBot, Amazonbot — eight are unreserved. Each is matched on its own token, so a group for GPTBot says nothing to the other eight.</p> | |
| <p>TDM Reservation Lint is a VS Code extension running 15 rules over the robots.txt you have open. On a corrected version of the same file it reports nothing.</p> | |
| <p>Nothing points at a TDMRep signal either: robots.txt instructs crawlers, while the W3C TDM Reservation Protocol expects <code>/.well-known/tdmrep.json</code>.</p> | |
| <p>The free layer does the whole job for one file: the robots.txt in front of you, all 15 rules, every finding with its replacement line. The licence key moves the axis, not the depth — the whole workspace, and files you keep: a corrected robots.txt, a matching tdmrep.json, a dated evidence report. A media and IP lawyer reviewing whether a reservation is machine-readable bills from €200 an hour, and then hands the token list to a developer anyway.</p></div><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does TDM Reservation Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It parses the robots.txt open in your editor and reports every line that does not do what it looks like it does: retired crawler tokens, invented tokens, rules sitting above the first User-agent line, paths missing a leading slash, empty Disallow lines under training crawlers, and groups that cancel the User-agent: * block. Fifteen rules, each finding carrying the replacement line.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Publishers, news and magazine developers, agencies and web teams inside the EU who have to make an Article 4(3) text-and-data-mining reservation machine-readable, and anyone outside the EU who wants AI training crawlers to actually receive the instruction they wrote. It is aimed at the person who edits robots.txt, not at the legal department.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a free robots.txt validator not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Syntax validators check grammar. A group naming anthropic-ai is perfect grammar and matches no crawler, because Anthropic sends ClaudeBot. A Disallow above the first User-agent line parses fine and is discarded. Validators pass both. This checks the published token registries and RFC 9309 group semantics, which is where reservations fail.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers the whole job for one file: the robots.txt you have open, all fifteen rules, every finding with its fix. The licence key changes the axis rather than the depth — it scans every robots.txt in the workspace and writes files you keep: a corrected robots.txt, a matching /.well-known/tdmrep.json, and a dated Markdown evidence report.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost done by a person?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A media and IP lawyer reviewing whether a reservation is machine-readable bills from around 200 euro an hour, and they will still hand the token list to a developer to implement. The extension costs 29 dollars once and reads a file in about a second, which is the difference between a review and a check you can run on every commit.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. TDM Reservation Lint checks the file you open against 15 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does TDM Reservation Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It parses the robots.txt open in your editor and reports every line that does not do what it looks like it does: retired crawler tokens, invented tokens, rules sitting above the first User-agent line, paths missing a leading slash, empty Disallow lines under training crawlers, and groups that cancel the User-agent: * block. Fifteen rules, each finding carrying the replacement line."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Publishers, news and magazine developers, agencies and web teams inside the EU who have to make an Article 4(3) text-and-data-mining reservation machine-readable, and anyone outside the EU who wants AI training crawlers to actually receive the instruction they wrote. It is aimed at the person who edits robots.txt, not at the legal department."}}, {"@type": "Question", "name": "Why is a free robots.txt validator not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Syntax validators check grammar. A group naming anthropic-ai is perfect grammar and matches no crawler, because Anthropic sends ClaudeBot. A Disallow above the first User-agent line parses fine and is discarded. Validators pass both. This checks the published token registries and RFC 9309 group semantics, which is where reservations fail."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers the whole job for one file: the robots.txt you have open, all fifteen rules, every finding with its fix. The licence key changes the axis rather than the depth — it scans every robots.txt in the workspace and writes files you keep: a corrected robots.txt, a matching /.well-known/tdmrep.json, and a dated Markdown evidence report."}}, {"@type": "Question", "name": "What would this cost done by a person?", "acceptedAnswer": {"@type": "Answer", "text": "A media and IP lawyer reviewing whether a reservation is machine-readable bills from around 200 euro an hour, and they will still hand the token list to a developer to implement. The extension costs 29 dollars once and reads a file in about a second, which is the difference between a review and a check you can run on every commit."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. TDM Reservation Lint checks the file you open against 15 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:57144,slug:"tdm-reservation-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cra-annex-ii-docs-lint"><span class="yj-rel-t">CRA Annex II User Docs Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/devicetree-config-language-pack"><span class="yj-rel-t">DeviceTree Config Audit: 23 dtc-Style Rules, 50 Snippets</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/en301549-lint"><span class="yj-rel-t">EN 301 549 Lint — European Accessibility Act rules, with the clause number, in</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-act-article-50-audit"><span class="yj-rel-t">EU AI Act Article 50 Transparency Audit for Your Codebase</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body> | |
| </html> | |