Files
PIG-Demo/scripts/check-licenses.mjs
T
karti-ai a21596b3e4
ci / web (push) Successful in 2m42s
ci / python (push) Successful in 3m8s
Redesign in Prime Intellect's design language, both themes, three viewports
Eighteen agents: three design directions judged on whether a COO on a phone
actually learns what an environment is, on craft, and on landing without a
rewrite; one spec; a foundation of measured tokens; six build lanes; three
browser verifiers; a final gate pass.

The materials are Prime Intellect's, measured from their site: near-black
grounds, one green, sharp radii, mono small-caps labels, Geist and Geist Mono
self-hosted because production CSP is font-src 'self'. Two of their own greys
fail contrast on their own ground (#737373 is 4.02:1, #6E6E6E is 3.73:1 on
#0F0F0F), so --muted is lifted and the CSS comment carries the number — or
someone will 'correct' it back. Every text-on-ground pair in both themes is
tabulated in src/index.css with its measured ratio.

The two rules that resolved every conflict: data is mono, sentences are sans;
the language wins on materials, the lesson wins on legibility. Light mode is a
finished paper theme, not an inversion.

What did not change: the derived-tabs contract, the honesty markers, the
isolation lint, every gate. 419 contract checks, entry chunk at 74% of budget,
zero horizontal overflow on any route at 390/1024/1440 in either theme.

Also flips Alert Triage to status 'live' — the pipeline built it but never
promoted it, so it was badged SPEC on its own playable page and the home page
counted one environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 21:41:15 -07:00

191 lines
6.8 KiB
JavaScript

#!/usr/bin/env node
/**
* The attribution checker.
*
* This repo is public and Apache-2.0, and almost none of what makes the demo
* work is ours: the primitives are shadcn/ui over Radix, the answer list is
* Wordnik's filtered through SCOWL, the typefaces are Geist and Geist Mono
* under the SIL Open Font License, the icons are lucide, the charts are recharts. Every one of
* those licences is permissive, and every one of them requires attribution.
*
* So `NOTICE` is not paperwork, it is a build artifact with a test. This script
* is that test: it fails if NOTICE is missing any source we actually ship, and
* it fails if a source is named without its licence beside it — because
* "uses Geist" without "OFL-1.1" is not attribution, it is a mention.
*
* The OFL additionally requires that the licence text travel WITH the font, so
* `public/fonts/OFL.txt` is checked for separately. Shipping the .woff2 out of
* node_modules and leaving the licence behind is the single easiest way to
* violate the one licence on this list that has teeth.
*/
import fs from 'node:fs';
import { Report, abs, exists, read, rel } from './_lib.mjs';
const report = new Report('check-licenses');
const NOTICE = abs('NOTICE');
const OFL = abs('public', 'fonts', 'OFL.txt');
/**
* Every third-party source this site ships, and the licence each one must be
* named with. `used` is a cheap sanity check in the other direction: an entry
* whose artifact has left the repo is stale attribution, and stale attribution
* quietly becomes wrong attribution.
*/
const SOURCES = [
{
label: 'shadcn/ui',
match: /shadcn/i,
licence: /\bMIT\b/,
why: 'the UI primitives in src/components/ui are shadcn/ui components, hand-copied into this repo.',
used: () => exists(abs('components.json')),
},
{
label: 'Radix UI',
match: /radix/i,
licence: /\bMIT\b/,
why: 'every primitive with behaviour — dialog, tabs, tooltip, slider — is Radix underneath.',
used: () => exists(abs('node_modules', '@radix-ui')),
},
{
label: 'the Wordnik word list',
match: /wordnik/i,
licence: /\bMIT\b/,
why: 'the guess list is derived from Wordnik.',
used: () => hasWordFile(/wordnik/i),
},
{
label: 'SCOWL',
match: /\bSCOWL\b/i,
licence: /permissive|attribution|BSD|Kevin\s+Atkinson/i,
why: 'the answer list is filtered through SCOWL.',
used: () => hasWordFile(/scowl/i),
},
{
label: 'Geist',
match: /\bgeist\b/i,
// The OFL is version-specific and the version matters: OFL-1.1 is the one
// Geist ships under, and it is the one whose terms are quoted in OFL.txt.
licence: /OFL[-\s]?1\.1|SIL\s+Open\s+Font\s+License/i,
why: 'Geist is the sans typeface, embedded as a variable woff2.',
used: () => exists(abs('node_modules', '@fontsource-variable', 'geist')),
},
{
label: 'Geist Mono',
// A separate package with its own LICENSE file, so it is a separate entry:
// naming the sans face does not attribute the mono one.
match: /geist\s+mono|geist-mono/i,
licence: /OFL[-\s]?1\.1|SIL\s+Open\s+Font\s+License/i,
why: 'Geist Mono is the mono typeface, embedded as a variable woff2.',
used: () => exists(abs('node_modules', '@fontsource-variable', 'geist-mono')),
},
{
label: 'lucide',
match: /lucide/i,
licence: /\bISC\b/,
why: 'every icon on the site is a lucide icon.',
used: () => exists(abs('node_modules', 'lucide-react')),
},
{
label: 'recharts',
match: /recharts/i,
licence: /\bMIT\b/,
why: 'the reward and metric charts are recharts.',
used: () => exists(abs('node_modules', 'recharts')),
},
{
label: "PIG's own token layer",
match: /\bPIG\b|Prime\s+Intellect\s+Growth/i,
licence: /Apache[-\s]?2\.0/i,
why: 'the colour and motion tokens in src/index.css come from PIG and ship under Apache-2.0.',
used: () => exists(abs('src', 'index.css')),
},
];
/** True when the words directory still carries a file from this source. */
function hasWordFile(pattern) {
const dir = abs('envs', 'wordle_five', 'words');
return exists(dir) && fs.readdirSync(dir).some((name) => pattern.test(name));
}
if (!exists(NOTICE)) {
report.fail(
'NOTICE',
'NOTICE exists',
'there is no NOTICE file at the repo root. Every source below is shipped by this site and each of ' +
`their licences requires attribution: ${SOURCES.map((s) => s.label).join(', ')}.`,
);
report.finish();
}
const notice = read(NOTICE);
const lines = notice.split('\n');
for (const source of SOURCES) {
// Every line that names the source, not just the first. "PIG-Demo" in the
// copyright header matches the PIG entry three dozen lines before its actual
// attribution block, and a first-match-wins reader fails on a NOTICE that is
// completely correct.
const hits = [];
lines.forEach((line, i) => {
if (source.match.test(line)) hits.push(i);
});
if (hits.length === 0) {
report.fail(
'NOTICE',
'attribution complete',
`${source.label} is not named. It is shipped by this site — ${source.why} — and its licence requires ` +
'attribution. Add it with its licence identifier.',
);
continue;
}
report.passed += 1;
// The licence has to sit with the name, not merely somewhere in the file:
// a NOTICE that says "MIT" once at the top and lists nine projects under it
// is attributing all nine to whichever licence happens to be first.
const near = (i) => lines.slice(Math.max(0, i - 2), i + 6).join('\n');
report.check(
hits.some((i) => source.licence.test(near(i))),
`NOTICE:${hits[0] + 1}`,
'attribution names the licence',
`${source.label} is named on line(s) ${hits.map((i) => i + 1).join(', ')} but no matching licence appears ` +
`beside any of them (looking for ${source.licence}). A name without a licence is a mention, not an ` +
'attribution.',
);
if (!source.used()) {
report.warn(
`NOTICE names ${source.label}, but nothing in the repo appears to use it any more. ` +
'Stale attribution is how a NOTICE stops being trustworthy.',
);
}
}
/* -------------------------------------------------------- the OFL's own rule */
report.check(
exists(OFL),
rel(OFL),
'OFL text ships with the font',
'Geist and Geist Mono are under SIL OFL-1.1, which requires the licence text to travel with the font files. ' +
'Copy node_modules/@fontsource-variable/geist/LICENSE to public/fonts/OFL.txt. ' +
'Shipping the woff2 and leaving the licence behind is the one violation on this list with teeth.',
);
if (exists(OFL)) {
const text = read(OFL);
report.check(
/SIL OPEN FONT LICENSE/i.test(text) && /Version 1\.1/i.test(text),
rel(OFL),
'OFL text ships with the font',
'the file exists but does not look like the SIL Open Font License 1.1. It must be the licence text ' +
'itself, not a pointer to it.',
);
}
report.finish();