soup-list-lint / index.html
getreadystack's picture
Upload index.html with huggingface_hub
1f3b27d verified
Raw History Blame Contribute Delete
51.5 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>IEC 62304 SOUP List — check your SOUP list 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/soup-list-lint"></head>
<body>
<div class="wrap">
<h1>IEC 62304 SOUP List — check your SOUP list free</h1>
<p class="lede">Checks your SOUP list against IEC 62304 8.1.2, 5.3.3, 5.3.4 and 7.1.3: manufacturer, pinned version, requirements, safety class and a dated anomaly-list review. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"># SOUP list — Meridian DR-7 portable radiography viewer
| SOUP item | Manufacturer | Version | Functional and performance requirements | Hardware and software requirements | Safety class | Published anomaly list evaluated |
| --- | --- | --- | --- | --- | --- | --- |
| zlib | Jean-loup Gailly and Mark Adler | 1.2.11 | Decompresses DICOM pixel data | Linux 6.1, glibc 2.36 | B | 2024-11-05 |
| SQLite | SQLite Consortium | ^3.45 | Stores the local study cache | Linux 6.1, 512 MB storage | B | 2026-05-18 |
| libcurl | TBD | 8.7.1 | Uploads study archives to the PACS node | Linux 6.1, OpenSSL 3.0.13 | C | 2026-04-02 |
| OpenSSL | OpenSSL Corporation | 3.0.13 | Provides TLS 1.3 for the PACS uplink | Linux 6.1 | C | No |
| Qt Base | The Qt Company | 6.5.3 | Draws the viewer window and handles touch input | Linux 6.1, Wayland 1.21 | | checked by QA |
| libjpeg-turbo | libjpeg-turbo project | latest | Decodes JPEG-compressed frames | Linux 6.1 | Low | 2026-12-01 |
| zlib | Jean-loup Gailly and Mark Adler | 1.3.1 | | - | B | 2026-07-30 |
| pugixml | Arseny Kapoulkine | 1.14 | Parses the device configuration file at boot | TODO | A | 2025-06-11 |</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> Lint one open SOUP list and see every IEC 62304 record gap on its own line, with the clause that asks for it.. <strong>In the editor:</strong> Scan every SOUP list in the workspace at once and export the dated evidence record (Markdown + CSV) you keep in the technical file. — that part asks for a licence key.</div>
<p id="upgrade"><a href="https://getreadystack.com/api/buy/cl/polar_cl_LO01ePekkWKRaFbmyA8ZMKGiM4Oh215UEuHrO0mzlpP">Workspace scan + dated evidence record — $29</a><br><small>$29 once · one licence key per person or team seat · A regulatory consultant reviewing SOUP documentation bills $150-$250 an hour; a 30-item register is a morning of that, at every version bump.</small></p>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var SOUP_RULES = {
"id": "soup-list-lint",
"standard": "IEC 62304:2006+AMD1:2015",
"stale_days": 365,
"today_default": "2026-09-16",
"columns": {
"title": ["soup", "soup item", "item", "name", "title", "component", "library", "package", "module"],
"manufacturer": ["manufacturer", "supplier", "vendor", "publisher", "maintainer", "author", "origin"],
"version": ["version", "version used", "unique soup designator", "soup designator", "designator", "release", "pinned version"],
"functional": ["functional and performance requirements", "functional \u0026 performance requirements", "functional/performance requirements", "functional requirements", "performance requirements", "purpose and requirements", "requirements"],
"system": ["system requirements", "hardware and software requirements", "hardware/software requirements", "hw/sw requirements", "required hardware and software", "runtime requirements", "platform requirements"],
"safety": ["safety class", "software safety class", "class", "62304 class"],
"anomaly": ["anomaly list", "published anomaly list", "anomaly list review", "anomaly list reviewed", "anomaly review", "known anomalies", "known bugs", "published anomaly list evaluated"]
},
"empty_values": ["", "-", "--", "—", "n/a", "na", "n.a.", "none", "nil", "see above", "same as above", "ditto"],
"placeholder_values": ["tbd", "tba", "tbc", "todo", "to do", "xxx", "?", "??", "???", "pending", "fixme", "later", "ask qa", "unknown"],
"unpinned_markers": ["latest", "*", "any", "current", "stable", "head", "main", "master", "rolling", "see package.json", "see requirements.txt", "see lockfile", "whatever ships"],
"not_reviewed_values": ["no", "not reviewed", "not yet", "none reviewed", "not done", "never", "not applicable yet"],
"rules": [
{"id": "soup_title_missing", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "SOUP item has no title", "msg": "SOUP item has no title. 8.1.2 asks for the title of each SOUP configuration item."},
{"id": "soup_manufacturer_missing", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "Manufacturer of the SOUP item is not recorded", "msg": "\"{item}\": manufacturer is not recorded. 8.1.2 asks for title, manufacturer and unique SOUP designator."},
{"id": "soup_version_missing", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "Unique SOUP designator (version) is not recorded", "msg": "\"{item}\": no version recorded. 8.1.2 asks for the unique SOUP designator — the exact version you shipped."},
{"id": "soup_version_not_pinned", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "Version is a range, not a unique designator", "msg": "\"{item}\": version \"{value}\" is a range or a pointer, not a unique designator. Record the exact version that went into the build."},
{"id": "soup_functional_req_missing", "clause": "IEC 62304 5.3.3", "sev": "error", "title": "Functional and performance requirements for the SOUP item are missing", "msg": "\"{item}\": no functional and performance requirements. 5.3.3 asks what this SOUP item must do for your device."},
{"id": "soup_system_req_missing", "clause": "IEC 62304 5.3.4", "sev": "error", "title": "Hardware and software required by the SOUP item are missing", "msg": "\"{item}\": no hardware/software requirements. 5.3.4 asks what this SOUP item needs to run (OS, runtime, driver, memory)."},
{"id": "soup_safety_class_missing", "clause": "IEC 62304 4.3", "sev": "error", "title": "No software safety class on the SOUP item", "msg": "\"{item}\": no software safety class. 4.3 classifies each software item A, B or C, and SOUP inherits the class of the item that uses it."},
{"id": "soup_safety_class_invalid", "clause": "IEC 62304 4.3", "sev": "error", "title": "Software safety class is not A, B or C", "msg": "\"{item}\": safety class \"{value}\" is not A, B or C. 4.3 allows those three classes only."},
{"id": "soup_anomaly_review_missing", "clause": "IEC 62304 7.1.3", "sev": "error", "title": "Published anomaly list was never evaluated", "msg": "\"{item}\": the published anomaly list was not evaluated. 7.1.3 asks you to read the supplier's known-bug list and judge whether any anomaly can contribute to a hazardous situation."},
{"id": "soup_anomaly_date_missing", "clause": "IEC 62304 7.1.3", "sev": "error", "title": "Anomaly-list evaluation carries no date", "msg": "\"{item}\": the anomaly-list evaluation carries no date. An undated evaluation cannot be tied to the version you shipped."},
{"id": "soup_anomaly_date_stale", "clause": "IEC 62304 7.1.3", "sev": "warn", "title": "Anomaly-list evaluation is older than 365 days", "msg": "\"{item}\": anomaly list last evaluated {value} — {days} days ago. New anomalies published since then have not been judged."},
{"id": "soup_anomaly_date_future", "clause": "IEC 62304 7.1.3", "sev": "error", "title": "Anomaly-list evaluation is dated in the future", "msg": "\"{item}\": anomaly-list evaluation is dated {value}, which is after today ({today}). An auditor reads a future date as a record written from memory."},
{"id": "soup_placeholder_value", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "A required field still holds a placeholder", "msg": "\"{item}\": {field} still says \"{value}\". A placeholder in the technical file is a missing record."},
{"id": "soup_duplicate_item", "clause": "IEC 62304 8.1.2", "sev": "warn", "title": "The same SOUP item is listed twice", "msg": "\"{item}\" is listed more than once (versions: {value}). Two rows for one item means the auditor cannot tell which one shipped."},
{"id": "soup_column_missing", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "The SOUP table has no column for a required record", "msg": "The SOUP table has no {field} column, so no item can carry that record ({clause})."},
{"id": "soup_no_items", "clause": "IEC 62304 8.1.2", "sev": "error", "title": "No SOUP items found in the file", "msg": "No SOUP items found. 8.1.2 asks for a list of SOUP configuration items; an empty list is only true if your device has no third-party code."}
]
}
;</script>
<script>/* SOUP List Lint — IEC 62304 software-of-unknown-provenance records.
Same file runs in Node (extension) and in the browser (free web tool). */
(function () {
'use strict';
var RULES = (typeof module !== 'undefined' && module.exports)
? require('./rules.json')
: window.SOUP_RULES;
var RULE_BY_ID = {};
for (var i = 0; i < RULES.rules.length; i++) RULE_BY_ID[RULES.rules[i].id] = RULES.rules[i];
var FIELD_LABEL = {
title: 'title',
manufacturer: 'manufacturer',
version: 'unique SOUP designator (version)',
functional: 'functional and performance requirements',
system: 'hardware and software requirements',
safety: 'software safety class',
anomaly: 'published anomaly list evaluation'
};
var REQUIRED = ['manufacturer', 'version', 'functional', 'system', 'safety', 'anomaly'];
var MISSING_RULE = {
manufacturer: 'soup_manufacturer_missing',
version: 'soup_version_missing',
functional: 'soup_functional_req_missing',
system: 'soup_system_req_missing',
safety: 'soup_safety_class_missing',
anomaly: 'soup_anomaly_review_missing'
};
function norm(s) {
return String(s == null ? '' : s)
.toLowerCase()
.replace(/[`*_]/g, '')
.replace(/\([^)]*\)/g, ' ')
.replace(/[^a-z0-9/&. ]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function plain(s) {
return String(s == null ? '' : s).replace(/[`*_]/g, '').replace(/\s+/g, ' ').trim();
}
function inList(list, v) {
var n = norm(v);
for (var i = 0; i < list.length; i++) if (norm(list[i]) === n) return true;
return false;
}
function isEmpty(v) {
return plain(v) === '' || inList(RULES.empty_values, v);
}
function isPlaceholder(v) {
var p = plain(v).toLowerCase();
if (p === '') return false;
for (var i = 0; i < RULES.placeholder_values.length; i++) {
if (p === String(RULES.placeholder_values[i]).toLowerCase()) return true;
}
return /^(tbd|todo|tba|tbc|fixme|xxx|pending)\b/i.test(p);
}
function isUnpinned(v) {
var p = plain(v);
if (inList(RULES.unpinned_markers, p)) return true;
var low = p.toLowerCase();
for (var i = 0; i < RULES.unpinned_markers.length; i++) {
if (low.indexOf(String(RULES.unpinned_markers[i]).toLowerCase()) === 0) return true;
}
if (/^[v]?\d+(\.\d+){0,3}([.\-+][0-9A-Za-z.\-]+)?$/.test(p)) return false;
if (/[\^~*]|>=|<=|>|<|\bor\b|,|\bto\b|\.x\b/i.test(p)) return true;
if (/^see\b/i.test(p)) return true;
return false;
}
function matchColumn(headers, aliases) {
var h, a;
for (a = 0; a < aliases.length; a++) {
for (h = 0; h < headers.length; h++) if (headers[h] === norm(aliases[a])) return h;
}
for (a = 0; a < aliases.length; a++) {
for (h = 0; h < headers.length; h++) {
if (headers[h] && headers[h].indexOf(norm(aliases[a])) !== -1) return h;
}
}
return -1;
}
function cells(line) {
var t = line.trim();
if (t.charAt(0) === '|') t = t.slice(1);
if (t.charAt(t.length - 1) === '|') t = t.slice(0, -1);
return t.split('|').map(function (c) { return c.trim(); });
}
function isSeparator(line) {
return /^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(line) && line.indexOf('|') !== -1;
}
function toDay(s) {
var m = /(\d{4})-(\d{2})-(\d{2})/.exec(String(s || ''));
if (!m) return null;
var d = Date.UTC(+m[1], +m[2] - 1, +m[3]);
if (isNaN(d)) return null;
return { ms: d, text: m[0] };
}
function parse(text) {
var lines = String(text || '').split(/\r?\n/);
var items = [];
var columns = null;
var headerLine = 0;
var i, j;
for (i = 0; i < lines.length - 1; i++) {
if (lines[i].trim().charAt(0) !== '|' || !isSeparator(lines[i + 1])) continue;
var headers = cells(lines[i]).map(norm);
var cols = {};
var hit = 0;
for (var key in RULES.columns) {
cols[key] = matchColumn(headers, RULES.columns[key]);
if (cols[key] !== -1) hit++;
}
if (cols.title === -1 || hit < 3) continue;
columns = cols;
headerLine = i + 1;
for (j = i + 2; j < lines.length && lines[j].trim().charAt(0) === '|'; j++) {
var c = cells(lines[j]);
var blank = true;
for (var k = 0; k < c.length; k++) if (plain(c[k]) !== '') blank = false;
if (blank) continue;
var item = { line: j + 1, fields: {}, title: plain(c[cols.title] || '') };
for (var f in cols) {
if (cols[f] === -1) continue;
item.fields[f] = { value: plain(c[cols[f]] || ''), line: j + 1 };
}
items.push(item);
}
break;
}
if (columns) return { mode: 'table', items: items, columns: columns, headerLine: headerLine, lines: lines };
// Section form: "### axtls 2.1.5" followed by "- Manufacturer: ..." lines.
var cur = null;
for (i = 0; i < lines.length; i++) {
var hm = /^#{2,5}\s+(.+?)\s*$/.exec(lines[i]);
if (hm) {
cur = { line: i + 1, fields: {}, title: plain(hm[1]), pairs: 0 };
items.push(cur);
continue;
}
if (!cur) continue;
var pm = /^\s*(?:[-*+]\s*)?([A-Za-z][A-Za-z0-9 /&.()-]{2,60}?)\s*:\s*(.*)$/.exec(lines[i]);
if (!pm) continue;
var kn = norm(pm[1]);
for (var key2 in RULES.columns) {
if (key2 === 'title') continue;
if (matchColumn([kn], RULES.columns[key2]) === 0) {
cur.fields[key2] = { value: plain(pm[2]), line: i + 1 };
cur.pairs++;
break;
}
}
}
items = items.filter(function (it) { return it.pairs > 0; });
return { mode: 'section', items: items, columns: null, headerLine: 0, lines: lines };
}
function check(text, opts) {
opts = opts || {};
var today = toDay(opts.today) || toDay(RULES.today_default);
var doc = parse(text);
var findings = [];
function add(id, line, vars) {
var rule = RULE_BY_ID[id];
var msg = rule.msg.replace(/\{(\w+)\}/g, function (_, name) {
if (name === 'clause') return rule.clause;
if (name === 'today') return today.text;
return vars && vars[name] !== undefined ? String(vars[name]) : '';
});
findings.push({ check: id, sev: rule.sev, msg: msg, line: line });
}
if (doc.items.length === 0) {
add('soup_no_items', 1, {});
return { findings: findings };
}
if (doc.mode === 'table') {
for (var r = 0; r < REQUIRED.length; r++) {
if (doc.columns[REQUIRED[r]] === -1) {
add('soup_column_missing', doc.headerLine, { field: FIELD_LABEL[REQUIRED[r]] });
}
}
}
var seen = {};
for (var i = 0; i < doc.items.length; i++) {
var it = doc.items[i];
var name = it.title;
if (isEmpty(name) || isPlaceholder(name)) {
add('soup_title_missing', it.line, {});
name = 'row ' + (i + 1);
}
for (var r2 = 0; r2 < REQUIRED.length; r2++) {
var f = REQUIRED[r2];
var cell = it.fields[f];
if (!cell) continue; // column/field absent: reported once at file level
var v = cell.value;
if (isEmpty(v)) { add(MISSING_RULE[f], cell.line, { item: name }); continue; }
if (isPlaceholder(v)) { add('soup_placeholder_value', cell.line, { item: name, field: FIELD_LABEL[f], value: v }); continue; }
if (f === 'version' && isUnpinned(v)) add('soup_version_not_pinned', cell.line, { item: name, value: v });
if (f === 'safety' && !/^(class\s*)?[abc]$/i.test(v)) add('soup_safety_class_invalid', cell.line, { item: name, value: v });
if (f === 'anomaly') {
if (inList(RULES.not_reviewed_values, v)) { add('soup_anomaly_review_missing', cell.line, { item: name }); continue; }
var d = toDay(v);
if (!d) { add('soup_anomaly_date_missing', cell.line, { item: name }); continue; }
var days = Math.round((today.ms - d.ms) / 86400000);
if (days < 0) add('soup_anomaly_date_future', cell.line, { item: name, value: d.text });
else if (days > RULES.stale_days) add('soup_anomaly_date_stale', cell.line, { item: name, value: d.text, days: days });
}
}
var slug = norm(name).replace(/\s+v?\d+(\.\d+)*$/, '');
if (slug) {
if (seen[slug]) {
var vs = [seen[slug].version, it.fields.version ? it.fields.version.value : ''].filter(Boolean).join(', ');
add('soup_duplicate_item', it.line, { item: name, value: vs || 'not recorded' });
} else {
seen[slug] = { version: it.fields.version ? it.fields.version.value : '' };
}
}
}
findings.sort(function (a, b) { return a.line - b.line; });
return { findings: findings };
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.rules.length };
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (typeof window !== 'undefined') window.SOUPENGINE = api;
})();
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
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 SOUPREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = SOUPREPORT;
else window.SOUPREPORT = SOUPREPORT;
</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.SOUPENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.SOUPREPORT.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://marketplace.visualstudio.com/items?itemName=ReadyStack.soup-list-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'soup-list-lint',src:'mp',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">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/soup-list-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'soup-list-lint',src:'ovsx',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">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/soup-list-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'soup-list-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/soup-list-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'soup-list-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/soup-list-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'soup-list-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">ext install ReadyStack.soup-list-lint
npx @readystack/soup-list-lint &lt;file&gt;
npx @readystack/soup-list-lint --mcp</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 one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind SOUP List Lint (IEC 62304) every day. When a rule changes, you get a single email with what changed and the updated check. 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">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</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:"soup-list-lint",lang:"en",source:"rule_alert"+((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">Reads your SOUP register the way an auditor does: 16 rules over clauses 8.1.2, 5.3.3, 5.3.4, 4.3 and 7.1.3.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A regulatory consultant reviewing SOUP documentation bills $150-$250 an hour; a 30-item register is a morning of that, at every version bump.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_LO01ePekkWKRaFbmyA8ZMKGiM4Oh215UEuHrO0mzlpP" 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 {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[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-16">Updated 2026-09-16</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-16","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/sku82012_result_card.jpg" alt="SOUP List Lint (IEC 62304)" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Fourteen IEC 62304 record gaps in an eight-item SOUP register: that is what the first run returns for a medical device software engineer getting an EU MDR technical file ready, and not one of the fourteen is a spelling mistake.</p>
<p>A SOUP list is the register of software of unknown provenance inside a device - the zlib, the SQLite, the OpenSSL, the Qt build that shipped with the firmware. IEC 62304:2006+AMD1:2015 does not ask for a tidy list of names. Clause 8.1.2 asks for the title, the manufacturer and the unique SOUP designator of each configuration item. 5.3.3 asks for the functional and performance requirements that item has to meet for your device, 5.3.4 for the hardware and software it needs to run, 4.3 for a software safety class of A, B or C, and 7.1.3 for an evaluation of the supplier&#x27;s published anomaly list against your hazard analysis. Five clauses, and an auditor walks the register row by row looking for all five.</p>
<p>The register shipped with this extension as a test fixture holds eight items from a portable radiography viewer. Running the sixteen rules over it returns fourteen findings - eleven errors and three warnings - and every one of the eight rows carries at least one:</p>
<ul><li>SQLite is recorded as <code>^3.45</code>. A caret range is not a unique SOUP designator; it names whatever resolved on the build machine that afternoon.</li><li>The libcurl row lists its manufacturer as <code>TBD</code>. A placeholder in a technical file is a missing record, not a promise.</li><li>OpenSSL&#x27;s evaluation says <code>No</code>; Qt Base has no safety class and an evaluation reading <code>checked by QA</code>, with no date.</li><li>libjpeg-turbo is pinned to <code>latest</code>, classified <code>Low</code>, and dated 2026-12-01, which is in the future.</li><li>zlib appears twice, at 1.2.11 and 1.3.1, and the second row has no 5.3.3 and no 5.3.4 record.</li><li>The oldest evaluation is 680 days old and a second is 462 days old. Both sit past the 365-day line the linter draws, so anomalies published since have never been judged.</li></ul>
<p>The clean version of the same register returns zero. That is the whole shape of the tool: a register that carries its records prints nothing, and one that does not prints the clause number next to the row.</p>
<p>Three of these are what an assistant gets wrong. Clause numbers first: ask a chatbot which clause wants the anomaly-list evaluation and it answers confidently, sometimes correctly. Dates second: a model reading a register has no opinion about whether 2026-12-01 is in the future, because it does not know what today is. Version ranges third - <code>^3.45</code> looks like a version, and only a rule that knows a range from a build stops on it. A markdown linter checks table syntax, and all fourteen findings sit in a syntactically perfect table.</p>
<p>Both shapes of register are read: a markdown table, and a section per item. Headings match by meaning, so <code>Supplier</code>, <code>Vendor</code> and <code>Manufacturer</code> all count.</p>
<p>It does not read CVE feeds, judge whether an anomaly is hazardous, or decide your safety class. This is a completeness check on the register - the mechanical part, and the part that gets written down at the audit.</p>
<p>Linting one register is free, in the editor and in the browser: every gap in that file, every run, no item cap and no trial window. The licensed tier does a different job - it scans every SOUP list in the workspace at once, across a family of device repositories, and exports the dated evidence record in Markdown and CSV that you file with the technical documentation. A regulatory consultant reviewing SOUP documentation bills in the $150 to $250 an hour range, and a thirty-item register is a morning of that, at every version bump.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku82012_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku82012_short.mp4" type="video/mp4"></video><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 SOUP List Lint actually check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads the markdown file holding your SOUP register and applies 16 rules from five IEC 62304 clauses. It flags a missing manufacturer or title, a version written as a range instead of the exact build, absent functional or hardware requirements, a missing or invalid safety class, and an anomaly-list evaluation that is absent, undated, future-dated or older than 365 days.</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">Medical device software engineers and regulatory affairs staff who maintain the SOUP register for an IEC 62304 technical file: firmware and application teams shipping under EU MDR or FDA premarket submissions, and the contract developers who hand a register over to a manufacturer at the end of a project.</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 an AI assistant or use a markdown linter?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A markdown linter checks table syntax, not records; it cannot tell a pinned version from a caret range. An AI assistant will answer confidently and sometimes cite the wrong clause number. This extension applies one fixed rule set, prints the clause next to every finding, and gives the same answer twice.</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?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Linting one SOUP list is free and complete: every gap in that file, every run, no item cap and no trial window. The same engine runs free in the browser. The licence adds workspace-wide scanning across several device repositories and the dated Markdown and CSV evidence record you file with the technical documentation.</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 hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A regulatory consultant reviewing SOUP documentation bills in the $150 to $250 per hour range, and a thirty-item register is a morning of that work, repeated at every version bump. The licence is $29 once, with a and it re-checks the register in seconds.</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. SOUP List Lint - IEC 62304 dependency inventory (medical sof checks the file you open against 16 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. 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 SOUP List Lint actually check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads the markdown file holding your SOUP register and applies 16 rules from five IEC 62304 clauses. It flags a missing manufacturer or title, a version written as a range instead of the exact build, absent functional or hardware requirements, a missing or invalid safety class, and an anomaly-list evaluation that is absent, undated, future-dated or older than 365 days."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Medical device software engineers and regulatory affairs staff who maintain the SOUP register for an IEC 62304 technical file: firmware and application teams shipping under EU MDR or FDA premarket submissions, and the contract developers who hand a register over to a manufacturer at the end of a project."}}, {"@type": "Question", "name": "Why not just ask an AI assistant or use a markdown linter?", "acceptedAnswer": {"@type": "Answer", "text": "A markdown linter checks table syntax, not records; it cannot tell a pinned version from a caret range. An AI assistant will answer confidently and sometimes cite the wrong clause number. This extension applies one fixed rule set, prints the clause next to every finding, and gives the same answer twice."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Linting one SOUP list is free and complete: every gap in that file, every run, no item cap and no trial window. The same engine runs free in the browser. The licence adds workspace-wide scanning across several device repositories and the dated Markdown and CSV evidence record you file with the technical documentation."}}, {"@type": "Question", "name": "What would this cost done by hand?", "acceptedAnswer": {"@type": "Answer", "text": "A regulatory consultant reviewing SOUP documentation bills in the $150 to $250 per hour range, and a thirty-item register is a morning of that work, repeated at every version bump. The licence is $29 once, with a and it re-checks the register in seconds."}}, {"@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. SOUP List Lint - IEC 62304 dependency inventory (medical sof checks the file you open against 16 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. 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:82012,slug:"soup-list-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/bem-class-extractor-html"><span class="yj-rel-t">BEM Class Auditor for HTML and SCSS</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/dora-register-lint"><span class="yj-rel-t">DORA Register of Information 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/en18031-psti-lint"><span class="yj-rel-t">EN 18031 / PSTI Device Config Lint</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>