Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
76a65ca
Refresh repository statistics incl. ArtiFinder-matched GitHub repos
vahldiek Jul 3, 2026
f6bb4eb
ArtiFinder page: add conference timeline heatmap; clearer labels
vahldiek Jul 3, 2026
6155ced
Regenerate artifinder intermediates with source field
vahldiek Jul 3, 2026
c87f0ff
Regenerate ArtiFinder data with fuzzy-matched links (+21 AE matches)
vahldiek Jul 3, 2026
f7fd5b9
ArtiFinder page: unify chart colour scheme
vahldiek Jul 3, 2026
ec710c1
docs(artifinder): AE-vs-ArtiFinder comparison table on methodology, c…
vahldiek Jul 6, 2026
bf2c312
Add ArtiFinder discovered-artifact integration (website)
vahldiek Jul 3, 2026
2ce7e59
Drop redundant ArtiFinder JSON outputs
vahldiek Jul 3, 2026
b00af1d
Include non-AE ArtiFinder papers in search index
vahldiek Jul 3, 2026
4e373f9
Show ArtiFinder-discovered papers on author/institution profiles
vahldiek Jul 3, 2026
3414a33
Profile tables: link title to AE URL, ArtiFinder as secondary link
vahldiek Jul 3, 2026
eebed8f
Rename afKey -> afAuthorKey to mirror pipeline _af_author_key
vahldiek Jul 3, 2026
fafd664
search_data.json: add source field (ae|artifinder)
vahldiek Jul 3, 2026
5c22677
docs(methodology): add paper citation section
vahldiek Jul 6, 2026
8681f69
docs(methodology): link intro to citation section
vahldiek Jul 7, 2026
e03843d
chore: drop generated data artifacts from ArtiFinder website PR
vahldiek Jul 8, 2026
efd13e7
Merge remote-tracking branch 'upstream/main' into feature/artifinder-…
vahldiek Jul 8, 2026
d43e0f7
ci: validate only schema-compatible institution ranking files
vahldiek Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ jobs:
for data in "$DATA_DIR"/*_conf_authors.json; do
[ -f "$data" ] && validate "$data" "$SCHEMA_DIR/conf_authors.schema.json"
done
for data in "$DATA_DIR"/*_institution_rankings.json; do
# Only validate files that match the full InstitutionRanking schema.
# Per-conference *_institution_rankings.json variants intentionally
# omit fields such as top_authors and should not be validated here.
for data in "$DATA_DIR"/systems_institution_rankings.json "$DATA_DIR"/security_institution_rankings.json; do
[ -f "$data" ] && validate "$data" "$SCHEMA_DIR/institution_rankings.schema.json"
done

Expand Down
2 changes: 2 additions & 0 deletions src/_data/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ main:
url: institution_rankings.html
- title: Systems vs. Security
url: overview.html
- title: ArtiFinder Discovery
url: artifinder.html
- title: Geographic Statistics
url: statistics/
- title: Profiles
Expand Down
1 change: 1 addition & 0 deletions src/_includes/head/custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<link rel="stylesheet" href="{{ '/assets/css/reprodb-table.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/reprodb-controls.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/reprodb-search.css' | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/reprodb-artifinder.css' | relative_url }}">
<script src="{{ '/assets/js/reprodb-utils.js' | relative_url }}"></script>

<!-- Tabulator 6 — sortable / paginated / filterable tables -->
Expand Down
1 change: 1 addition & 0 deletions src/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ AE results and committee lists live in the upstream community sites. Contribute

- [sysartifacts.github.io](https://github.com/sysartifacts/sysartifacts.github.io) — systems conferences (OSDI, SOSP, ATC, EuroSys, FAST, …)
- [secartifacts.github.io](https://github.com/secartifacts/secartifacts.github.io) — security conferences (USENIX Security, CCS, NDSS, S&P, …)
- [ArtiFinder-Data](https://github.com/DistriNet/ArtiFinder-Data) — automatically discovered artifact links (open to manual corrections via pull requests). These links are unverified, carry no badges, and never affect ReproDB scores; see the [ArtiFinder discovery page](/artifinder.html) and [methodology](/methodology.html#artifinder-discovered-artifacts).

### Suggest New Analyses

Expand Down
97 changes: 97 additions & 0 deletions src/artifinder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: "ArtiFinder — Automatically Discovered Artifacts"
permalink: /artifinder.html
---

[ArtiFinder](https://github.com/DistriNet/ArtiFinder) scrapes conference papers directly and identifies links to their artifacts, independently of any artifact-evaluation (AE) process. This page summarises the ArtiFinder links that ReproDB ingests from the [ArtiFinder-Data](https://github.com/DistriNet/ArtiFinder-Data) repository and how they have grown over time.

ArtiFinder and its underlying longitudinal study are described by Vansteenhuyse et al., *[Not All Those Who Share Are Lost: Analyzing 25 Years of Cybersecurity Artifact Sharing Practices Through Automated Discovery](https://github.com/DistriNet/ArtiFinder)* (USENIX Security 2026).

{: .artifinder-note}
**These links are _not_ manually verified.** ArtiFinder-discovered artifacts carry **no badges** and are **excluded from every ReproDB score** (artifact rate, reproducibility rate, combined score, institution and author rankings). They are shown for discovery purposes only, marked with an <img src="{{ '/assets/images/artifinder-logo.svg' | relative_url }}" alt="" style="height:1em;vertical-align:-0.15em"> **Artifinder** sign wherever they appear in search and profiles. The one exception is repository statistics: when ArtiFinder finds a GitHub repository for a paper that _did_ go through AE, that repository may be counted in the repository stats.

{% if site.data.artifinder_summary %}

## High-Level Summary

<div class="rdb-cards">
<div class="rdb-card">
<div class="rdb-card-value">{{ site.data.artifinder_summary.total_discovered }}</div>
<div class="rdb-card-label">Discovered Artifacts</div>
</div>
<div class="rdb-card">
<div class="rdb-card-value">{{ site.data.artifinder_summary.total_matched_ae }}</div>
<div class="rdb-card-label">Matched to AE Papers</div>
</div>
<div class="rdb-card">
<div class="rdb-card-value">{{ site.data.artifinder_summary.github_count }}</div>
<div class="rdb-card-label">GitHub Links</div>
</div>
<div class="rdb-card">
<div class="rdb-card-value">{{ site.data.artifinder_summary.discovery_pct }}%</div>
<div class="rdb-card-label">Discovery Rate</div>
</div>
<div class="rdb-card">
<div class="rdb-card-value">{{ site.data.artifinder_summary.year_range }}</div>
<div class="rdb-card-label">Years Included</div>
</div>
</div>

<p class="rdb-scatter-caption">Across {{ site.data.artifinder_summary.total_papers }} scanned papers in {{ site.data.artifinder_summary.conferences | join: ", " }}, ArtiFinder discovered {{ site.data.artifinder_summary.total_discovered }} artifact links. ArtiFinder data last updated {{ site.data.artifinder_summary.data_updated }} (only editions from {{ site.data.artifinder_summary.year_range | split: "\u2013" | first }} onward are included).</p>

---

## Discovered Artifacts Over Time

Number of ArtiFinder-discovered artifact links per year, split by whether the paper also went through artifact evaluation (AE) in ReproDB. Papers that never went through AE make up the bulk of ArtiFinder's coverage.

<div class="rdb-chart-wide rdb-chart-wrap--lg">
<div id="afDiscoveredChart" style="width:100%;height:100%"></div>
</div>

## Discovery Rate Over Time

Share of scanned papers for which ArtiFinder found an artifact link, per year. This is a coverage measure of ArtiFinder itself, not an artifact-evaluation rate.

<div class="rdb-chart-wide rdb-chart-wrap--lg">
<div id="afRateChart" style="width:100%;height:100%"></div>
</div>

## By Conference

Discovered artifact links per conference, and how many were matched to an AE paper tracked by ReproDB.

<div class="rdb-chart-wide rdb-chart-wrap--lg">
<div id="afConfChart" style="width:100%;height:100%"></div>
</div>

## Conference Timeline Heatmap

Discovered artifact links by conference and year. Darker cells indicate more discovered artifacts that year.

<div class="rdb-chart-wide">
<div id="afHeatmap" style="height:360px"></div>
</div>

{% else %}

*ArtiFinder statistics are being generated. Please check back soon.*

{% endif %}

<!-- Data blob: Liquid injects site data, JS reads it -->
<script id="artifinder-data" type="application/json">
{
"years": [{% for y in site.data.artifinder_by_year %}{{ y.year }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"discovered": [{% for y in site.data.artifinder_by_year %}{{ y.discovered }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"matched": [{% for y in site.data.artifinder_by_year %}{{ y.matched_ae }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"totalPapers": [{% for y in site.data.artifinder_by_year %}{{ y.total_papers }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"conferences": [
{% for c in site.data.artifinder_by_conference %}
{ "name": "{{ c.name }}", "category": "{{ c.category }}", "total_papers": {{ c.total_papers }}, "discovered": {{ c.discovered }}, "matched_ae": {{ c.matched_ae }},
"years_data": [{% for yd in c.years %}{ "year": {{ yd.year }}, "discovered": {{ yd.discovered }} }{% unless forloop.last %},{% endunless %}{% endfor %}] }{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}
</script>
<script src="{{ '/assets/js/reprodb-artifinder.js' | relative_url }}"></script>
105 changes: 105 additions & 0 deletions src/assets/css/reprodb-artifinder.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* reprodb-artifinder.css — styling for the ArtiFinder provenance marker
* (shown in search results and profile tables) and the ArtiFinder statistics
* page. Mirrors the ".avail-warn" tooltip pattern so the two markers feel
* consistent.
*/

/* ── Provenance marker (search + profile) ─────────────────────────── */
.artifinder-tag {
position: relative;
cursor: help;
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 0.8em;
color: #4a5aa8;
background: #eef1fb;
padding: 1px 6px;
border-radius: 3px;
border: 1px solid #c9d2f0;
vertical-align: middle;
}
.artifinder-tag .artifinder-logo {
width: 0.95em;
height: 0.95em;
flex: 0 0 auto;
}
.artifinder-tag .avail-tip {
display: none;
position: absolute;
bottom: 130%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: #fff;
font-size: 0.9em;
font-weight: normal;
line-height: 1.3;
padding: 5px 9px;
border-radius: 4px;
width: max-content;
max-width: 240px;
white-space: normal;
text-align: left;
z-index: 100;
pointer-events: none;
}
.artifinder-tag:hover .avail-tip {
display: block;
}

/* An ArtiFinder-only artifact link (no AE artifact_urls alongside it). */
.rdb-result-links .artifinder-link {
color: #4a5aa8;
}

/* ArtiFinder-discovered paper rows in profile tables (author + institution). */
.af-title,
a.af-title {
color: #4a5aa8;
font-style: italic;
}

/* ── Statistics page ──────────────────────────────────────────────── */
.artifinder-note {
font-size: 0.92em;
color: var(--ov-muted, #666);
border-left: 3px solid #c9d2f0;
padding: 0.4em 0.9em;
margin: 1em 0;
background: rgba(200, 210, 240, 0.12);
border-radius: 0 4px 4px 0;
}

/* ── Dark mode ────────────────────────────────────────────────────── */
html[data-theme="dark"] .artifinder-tag {
color: #aeb8ea;
background: #262a3d;
border-color: #3d445f;
}
html[data-theme="dark"] .rdb-result-links .artifinder-link {
color: #aeb8ea;
}
html[data-theme="dark"] .af-title,
html[data-theme="dark"] a.af-title {
color: #aeb8ea;
}
html[data-theme="dark"] .artifinder-note {
border-left-color: #3d445f;
background: rgba(60, 68, 95, 0.25);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) .artifinder-tag {
color: #aeb8ea;
background: #262a3d;
border-color: #3d445f;
}
html:not([data-theme="light"]) .rdb-result-links .artifinder-link {
color: #aeb8ea;
}
html:not([data-theme="light"]) .artifinder-note {
border-left-color: #3d445f;
background: rgba(60, 68, 95, 0.25);
}
}
11 changes: 11 additions & 0 deletions src/assets/images/artifinder-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
146 changes: 146 additions & 0 deletions src/assets/js/reprodb-artifinder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* reprodb-artifinder.js — Chart logic for the ArtiFinder discovery page.
*
* Depends on: ECharts 5 (loaded globally via head/custom.html).
* Reads inline JSON from <script id="artifinder-data" type="application/json">.
*/
(function() {
'use strict';

// Consistent 2-colour scheme across every chart on this page:
// AF_COLOR (indigo) = the ArtiFinder measure (discovered / rate / heatmap)
// MATCHED_COLOR (orange) = the subset also matched to an AE-evaluated paper
var AF_COLOR = '#4a5aa8'; // ArtiFinder indigo (primary)
var MATCHED_COLOR = '#e08a2b'; // matched-to-AE overlap accent
var AF_AREA = 'rgba(74,90,168,0.12)'; // translucent AF_COLOR for area fills

document.addEventListener('DOMContentLoaded', function() {
var dataEl = document.getElementById('artifinder-data');
if (!dataEl) return;
var D;
try { D = JSON.parse(dataEl.textContent); } catch (e) { return; }

var years = D.years || [];

/* ===== 1. Discovered artifacts over time (matched vs. unmatched) ===== */
var discEl = document.getElementById('afDiscoveredChart');
if (discEl && years.length) {
var matched = D.matched || [];
var unmatched = (D.discovered || []).map(function(d, i) {
return Math.max(0, (d || 0) - (matched[i] || 0));
});
var chart = ReproDB.initEChart(discEl);
chart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { bottom: 0, data: ['Not evaluated (no AE)', 'Matched to AE paper'] },
grid: { containLabel: true, left: 40, right: 20, bottom: 50, top: 30 },
xAxis: { type: 'category', data: years },
yAxis: { type: 'value', name: 'Discovered links', min: 0 },
series: [
{ name: 'Not evaluated (no AE)', type: 'bar', stack: 'af', data: unmatched, itemStyle: { color: AF_COLOR } },
{ name: 'Matched to AE paper', type: 'bar', stack: 'af', data: matched, itemStyle: { color: MATCHED_COLOR } }
]
});
ReproDB.registerEChart(chart);
}

/* ===== 2. Discovery rate over time ===== */
var rateEl = document.getElementById('afRateChart');
if (rateEl && years.length) {
var rate = (D.discovered || []).map(function(d, i) {
var tot = (D.totalPapers || [])[i] || 0;
return tot ? Math.round((d / tot) * 1000) / 10 : 0;
});
var chart2 = ReproDB.initEChart(rateEl);
chart2.setOption({
tooltip: {
trigger: 'axis',
valueFormatter: function(v) { return v + '%'; }
},
grid: { containLabel: true, left: 40, right: 20, bottom: 30, top: 30 },
xAxis: { type: 'category', data: years },
yAxis: { type: 'value', name: 'Discovery rate (%)', min: 0, max: 100 },
series: [
{ name: 'Discovery rate', type: 'line', data: rate, smooth: true,
itemStyle: { color: AF_COLOR }, lineStyle: { width: 2, color: AF_COLOR }, symbolSize: 6,
areaStyle: { color: AF_AREA } }
]
});
ReproDB.registerEChart(chart2);
}

/* ===== 3. Per-conference discovered vs. matched ===== */
var confEl = document.getElementById('afConfChart');
var confs = D.conferences || [];
if (confEl && confs.length) {
var names = confs.map(function(c) { return c.name; });
var disc = confs.map(function(c) { return c.discovered; });
var mAE = confs.map(function(c) { return c.matched_ae; });
var chart3 = ReproDB.initEChart(confEl);
chart3.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { bottom: 0, data: ['Discovered', 'Matched to AE'] },
grid: { containLabel: true, left: 40, right: 20, bottom: 50, top: 30 },
xAxis: { type: 'category', data: names },
yAxis: { type: 'value', name: 'Links', min: 0 },
series: [
{ name: 'Discovered', type: 'bar', data: disc, itemStyle: { color: AF_COLOR } },
{ name: 'Matched to AE', type: 'bar', data: mAE, itemStyle: { color: MATCHED_COLOR } }
]
});
ReproDB.registerEChart(chart3);
}

/* ===== 4. Conference timeline heatmap (year x conference = discovered) ===== */
var hmEl = document.getElementById('afHeatmap');
if (hmEl && confs.length && years.length) {
var hmConfs = confs.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });
var confNames = hmConfs.map(function(c) { return c.name; });
var rawHeat = [];
var maxVal = 0;
hmConfs.forEach(function(c, ci) {
var yrs = {};
(c.years_data || []).forEach(function(yd) { yrs[yd.year] = yd.discovered; });
years.forEach(function(y, yi) {
var v = yrs[y] || 0;
rawHeat.push([yi, ci, v]);
if (v > maxVal) maxVal = v;
});
});

function afCellColor(v) {
var dark = ReproDB.isDark();
if (v === 0) return dark ? 'rgba(50,55,65,0.5)' : 'rgba(220,220,220,0.3)';
var t = maxVal > 0 ? v / maxVal : 0;
return dark
? 'rgb(' + Math.round(40 + 60 * t) + ',' + Math.round(50 + 60 * t) + ',' + Math.round(90 + 120 * t) + ')'
: 'rgba(74,90,168,' + (0.15 + t * 0.75) + ')';
}
function afLabelColor(v) {
var dark = ReproDB.isDark();
var t = maxVal > 0 ? v / maxVal : 0;
return (v > 0 && t > (dark ? 0.25 : 0.6)) ? '#fff' : ReproDB.themeColors().text;
}

var hmChart = ReproDB.initEChart(hmEl);
function setAfHeatmap() {
var dark = ReproDB.isDark();
hmChart.setOption({
tooltip: { formatter: function(p) { return confNames[p.value[1]] + ' (' + years[p.value[0]] + '): ' + p.value[2] + ' discovered'; } },
grid: { containLabel: true, left: 20, right: 20, bottom: 20, top: 30 },
xAxis: { type: 'category', data: years, splitArea: { show: false } },
yAxis: { type: 'category', data: confNames, splitArea: { show: false }, inverse: true },
series: [{
type: 'heatmap',
data: rawHeat.map(function(d) { return { value: d, itemStyle: { color: afCellColor(d[2]) }, label: { color: afLabelColor(d[2]) } }; }),
label: { show: true, fontSize: 10, formatter: function(p) { return p.value[2] > 0 ? p.value[2] : ''; } },
itemStyle: { borderColor: dark ? '#333' : '#fff', borderWidth: 1 }
}]
});
}
setAfHeatmap();
ReproDB.registerEChart(hmChart);
ReproDB.onThemeChange(setAfHeatmap);
}
});
})();
Loading