Начни здесь

package-story-page: страница пакета, где доказано каждое число

Практический курс по навыку @dzhechkov/skills-package-story-page для того, кто впервые собирает страницу-историю о чужом или своём пакете. Вместе с Ниной ты разберёшь порядок восьми блоков истории, форму доказательства из пути, хеша и узкого диапазона строк, три статуса утверждения, отдельную заставу для чисел, обязательный ярлык синтетического примера, конвейер из четырёх шагов, закрытое подмножество HTML у проверяющего, отрицательный контроль в браузерной заставе, честные границы проверок — и научишься выбирать между страницей-историей и полным курсом.

Знакомься: Нина

Нина сопровождает небольшой пакет и устала пересказывать README вслух каждому, кто спрашивает «а что он делает?». Она проходит курс на шаг впереди тебя: сначала совершает ошибку сама, потом показывает, как правильно.

Содержание курса

1. Зачем нужна страница-история

Что делает package-story-page и чем он отличается от ещё одного README.

Ключевая мысль: страница-история для неспециалиста

Нина сопровождает небольшой пакет. Раз в неделю кто-нибудь из соседней команды спрашивает: «а он вообще что делает?» — и она в сотый раз пересказывает README вслух.

README написан для того, кто уже решил ставить пакет. Человеку со стороны нужно другое: страница-история — короткая страница, где сначала показан живой пример работы, и только потом устройство, установка и границы.

Ровно это собирает навык package-story-page: берёт один существующий пакет и делает из него самодостаточный HTML-файл — без внешних шрифтов, картинок, скриптов и сетевых запросов при показе.

Отличие от обычного лендинга жёсткое: здесь нельзя написать «самый быстрый в мире». Каждое утверждение с фактом обязано указывать на конкретные строки в файлах пакета — иначе страница не пройдёт собственную проверку и не будет собрана.

Нина сначала расстроилась: «мне же нужно продать инструмент». Через день она поняла обратное — доказанная страница убеждает сильнее, потому что читатель может сам открыть указанный файл и увидеть ту же строку.

2. Пример раньше архитектуры

Восемь блоков истории и жёсткое правило их порядка.

Ключевая мысль: пример раньше архитектуры

Первая версия страницы у Нины начиналась так: «Архитектура решения основана на модульном подходе…». Читатель со стороны закрыл вкладку на второй строке.

Контракт истории (references/story-contract.md) задаёт восемь блоков и требует авторовать их именно в этом порядке:

  1. hero — одна проблема аудитории, одна фраза о пользе, одно основное действие;
  2. example — конкретный вход, от двух до пяти шагов обработки, предпросмотр результата;
  3. why — зачем пакет существует и что меняется до и после;
  4. mechanism — от трёх до шести названных стадий с пояснением и ограничителем у каждой;
  5. install — точные команды, подтверждённые данными о пакете;
  6. reuse — где ещё это запускается; неизвестная поддержка так и остаётся неизвестной;
  7. limits — чего пакет не делает: безопасность, стоимость, свежесть;
  8. cta — следующий безопасный шаг.

Правило, которое навык называет непререкаемым: пример появляется раньше архитектуры, установки, стоимости, безопасности и раздела вопросов. Не «желательно раньше» — раньше.

Причина простая и проверяемая на себе: человек решает, читать ли дальше, по первому экрану. Если там абстракция, второго экрана не будет. Нина переставила блоки — и тот же коллега дочитал до конца.

3. Из чего состоит доказательство

Путь, SHA-256 и диапазон строк — форма локальной ссылки на источник.

Ключевая мысль: узкий диапазон строк с SHA-256

Нина написала первое утверждение и сослалась на весь README целиком: «там же это написано».

Контракт доказательств (references/evidence-contract.md) такую ссылку не принимает. Источник бывает ровно двух видов:

  • локальный: { id, path, sha256, lineRange } — относительный путь, хеш файла и диапазон строк;
  • внешний: { id, url, checkedAt, receiptPath, sha256, lineRange } — адрес по HTTPS, дата проверки и локальная квитанция, которая сама называет тот же адрес и ту же дату.

Диапазон строк ограничен сверху сорока строками (MAX_LOCAL_RANGE_LINES в scripts/story-schema.mjs). Это и есть лечение от ссылки «весь README»: нельзя процитировать файл целиком и назвать это доказательством — прочитай файл и сузь диапазон до тех строк, на которых утверждение действительно держится.

Проверяющий не верит брифу на слово: он заново читает файл под --pkg, сверяет путь и хеш с извлечёнными данными, отказывается идти по символическим ссылкам и наружу из каталога пакета.

И сразу честная оговорка, которую контракт делает сам: указатель на источник доказывает, что пакет ЭТО СКАЗАЛ, а не что сказанное истинно. Документация пакета может врать; SHA-256 покажет, какие именно байты сделали заявление. Нина держит эту фразу в голове всё время — она отделяет провенанс от истины.

4. Три статуса утверждения

evidenced, external, unknown — и почему пустой список источников допустим только у одного из них.

Ключевая мысль: статус утверждения evidenced, external или unknown

У Нины на столе три фразы, и все три хочется поставить на страницу.

Каждое утверждение в брифе выглядит так: { id, text, status, sourceIds }. Статус — ровно один из трёх:

  • evidenced — подтверждено текущими локальными строками пакета;
  • external — опирается на внешнюю запись с датой и локальной квитанцией;
  • unknown — доказательства нет, и страница честно показывает это читателю.

Ловушка, на которую Нина попалась: пустой список источников разрешён ТОЛЬКО при статусе unknown. Написать красивую фразу, не сослаться никуда и оставить статус evidenced не выйдет — схема отклонит бриф с сообщением, что утверждение должно либо цитировать доказательство, либо быть явно неизвестным.

И вторая ловушка, поважнее: unknown — это не позор и не заглушка. Это единственный честный способ сказать «мы не знаем», и интерфейс показывает такой пункт с видимой пометкой отсутствующего доказательства. Страница, где всё подряд помечено evidenced, вызывает больше подозрений, чем страница с двумя честными unknown.

Ещё одно ограничение, которое стоит запомнить сразу: числовое утверждение обязано быть evidenced. Внешняя запись с числом детерминированную проверку не проходит — либо приносите число как текущее локальное доказательство, либо помечайте unknown.

5. Числа проходят через отдельную заставу

Почему число в подзаголовке отклоняется, а то же число в утверждении — нет.

Ключевая мысль: числовой токен с контекстом на той же строке

Нина написала подзаголовок: «74 живых мутанта охраняют этот верификатор». Красиво, правда — и проверка отклонила бриф целиком.

Сюрприз в том, что дело было не в самом числе, а в том, ГДЕ оно стоит. Схема делит текст на две категории:

  1. утверждения (claims) — туда числа можно;
  2. прочая авторская проза — заголовки, подписи, пояснения стадий, направления визуалов: числовой токен там запрещён вовсе.

Правило звучит так: фактические числа выражай утверждением, а не украшением. Исключение сделано ровно для отображаемых артефактов — синтетического ввода, предпросмотра результата и буквальных команд установки: там цифры это часть показываемого объекта, а не обещание.

А внутри утверждения число охраняется отдельно. Каждому числовому токену нужна своя запись доказательства с тремя полями:

  • token — точно тот же токен, и записи идут в том же порядке, что и числа в тексте;
  • context — поле или единица измерения рядом, длиной от трёх до восьмидесяти знаков, обязательно с буквами и без цифр (контекст не может быть самим числом);
  • sourceId — источник, который уже назван в sourceIds этого утверждения.

И главное: токен и его контекст должны встретиться на ОДНОЙ строке внутри процитированного диапазона. Не в одном файле, не по соседству — на одной строке. Так число «74» получает смысл «74 чего именно», и его нельзя подставить из случайного места.

Нина переписала фразу как утверждение со ссылкой на реестр мутаций — и застава пропустила её без единого возражения. Кстати, десятичные цифры распознаются в любой системе письма, а слова «семьдесят четыре» токеном не считаются: это уже вопрос содержательного обзора, а не грамматики.

6. Синтетический пример и его видимый ярлык

Почему демонстрация обязана называть себя демонстрацией.

Ключевая мысль: синтетический пример с обязательным видимым ярлыком

Нина сделала красивый пример: вход, три шага, готовый результат. Всё придумано ею за пять минут — и выглядит как настоящий рабочий прогон.

Именно поэтому навык требует у каждого примера поле synthetic: true, и это не служебный флаг: он превращается в видимый ярлык на странице. Читатель узнаёт, что перед ним показательный пример, а не запись реальной работы у реального клиента.

Отказаться от ярлыка нельзя — схема требует synthetic: true буквально, любое другое значение отклоняет весь пример.

Второе требование того же уровня: показывай проверяемый результат, а не фразу «дальше происходит магия». Предпросмотр результата и его формат — обязательные поля примера; синтетический пример без результата схема не принимает.

Что запрещено прямо и без оговорок: выдуманные отзывы, клиенты, метрики, награды и искусственная срочность. Популярность, цены, результаты тестов производительности и совместимость нельзя придумывать — только приносить доказательства.

Нина проверила себя одним вопросом: «если читатель откроет исходники, он почувствует себя обманутым?» Ярлык synthetic — это способ ответить «нет» заранее, а не оправдываться потом.

7. Конвейер: извлечь, написать, отрисовать, проверить

Четыре обязательных шага и пятый — перед заявлением о выпуске.

Ключевая мысль: конвейер из четырёх шагов и браузерная застава

Нина хочет один раз увидеть весь путь целиком, а не собирать его из обрывков.

Конвейер навыка состоит из четырёх шагов, и каждый — обычная команда node:

  1. извлечь доказательства: extract-package-evidence.mjs --pkg <корень пакета> --json evidence.json — обходит файлы пакета, считает хеши, собирает примеры из README и честно перечисляет то, что доказать не смог;
  2. написать бриф — единственный шаг, который делает человек или модель: скопировать в бриф идентификатор, путь и хеш каждого источника из шага 1 и сузить диапазон строк;
  3. отрисовать страницу: render-story-page.mjs --brief brief.json --out site/index.html;
  4. проверить: verify-story-page.mjs --brief … --site … --evidence … --pkg … — при любом красном результате конвейер останавливается.

Пятый шаг отделён намеренно: verify-story-page-browser.mjs --site … запускает настоящий Firefox и измеряет вёрстку. Он требуется перед заявлением о выпуске — и его нельзя заменить ни свойством overflow-x: hidden, ни утверждением о тексте стилей.

Обрати внимание на форму команд: всё запускается как node "$SKILL_ROOT/scripts/<файл>.mjs". SKILL_ROOT — абсолютный путь к каталогу, в котором лежит установленный SKILL.md. Искать соседние файлы в родительских каталогах репозитория навыку запрещено: он должен работать одинаково и внутри монорепозитория, и в одиночной установке.

Нина попробовала пропустить шаг 4 — «и так же видно, что страница нормальная». На следующей правке брифа выяснилось, что один идентификатор источника потерялся, и страница ссылалась в пустоту. Проверка ловит это за секунды; глаз не поймал за день.

8. Закрытое подмножество HTML

Что именно проверяет разбор страницы — и чем он принципиально не является.

Ключевая мысль: закрытое подмножество HTML вместо сканера чужих страниц

Нина спросила прямо: «Ваш проверяющий — это сканер безопасности? Можно натравить его на чужой сайт?»

Ответ: нет, и это записано в README пакета отдельным абзацем. Проверяющий предназначен для страниц, которые собрал ЭТОТ ЖЕ конвейер. Он разбирает намеренно закрытое подмножество HTML привезённой с собой сборкой parse5, не исполняет ни строчки скриптов страницы и не делает сетевых запросов.

Что он отклоняет внутри своего подмножества:

  • ошибки разбора и любые починки, которые браузер сделал бы молча;
  • синтезированные и не имеющие позиции в исходнике узлы;
  • чужие пространства имён и комментарии;
  • всякий элемент и атрибут за пределами выпускаемого набора.

Отдельная деталь про стили: единственный элемент <style> принимается только тогда, когда SHA-256 его точных байтов совпадает с отпечатком, который хранит сам проверяющий. Ссылки на источники обязаны быть объявленными в брифе абсолютными адресами по HTTPS с атрибутом rel="noreferrer" и находиться внутри раскрывающегося блока источников.

Перед тем как выполнить хоть один локальный модуль, обёртка читает и закрепляет по SHA все пять загружаемых локальных входов: семантический проверяющий, сборку parse5, извлечение доказательств, отрисовку и общую схему. Отдельная застава графа импортов спрашивает у разборщика Node, какие модули запрашиваются, и пропускает только относительные рёбра .mjs внутри каталога плюс ровно node:crypto.

Если изолированный разборщик не удалось запустить или он вернул пустой, несовместимый или нетипизированный результат — застава закрывается с названной причиной, а не пропускает молча. Это и есть разница между «проверка прошла» и «проверка не смогла сказать»; смешивать их нельзя. И честная граница напоследок: ни один из этих механизмов не обещает удержать произвольный или намеренно заново разрешённый код — окончательной властью остаются проверенные побайтовые хеши.

9. Ноль запросов — ещё не доказательство

Отрицательный контроль: прибор сначала обязан доказать, что он вообще видит.

Ключевая мысль: отрицательный контроль перед выводом о нуле запросов

Нина запустила браузерную заставу и получила строку: сетевых запросов на другое происхождение — ноль. Победа?

Вопрос, который стоит задать первым: а прибор вообще способен увидеть такой запрос? Ноль бывает двух совершенно разных видов — «страница ничего не запросила» и «измеритель сломан и молчит». Отличить их по самому нулю невозможно.

Поэтому застава устроена так: перед измерением она поднимает вторую точку и заставляет страницу-приманку сходить туда. Записывающий посредник обязан этот запрос увидеть и отклонить. Только после этого ноль запросов с настоящей страницы засчитывается как доказательство.

Такой шаг называется отрицательный контроль — намеренно вызванное событие, которое прибор должен зарегистрировать, чтобы его молчание в остальное время что-то значило.

Дальше идёт ещё один слой, и он про подлог со стороны вызывающего кода:

  • измерения принимают только непрозрачную квитанцию, выданную самим модулем наблюдения; структурно похожий объект её не подделывает;
  • посредник сам читает адрес контрольной цели и живой счётчик попаданий, поэтому место вызова не может подставить готовую пару «да, ноль».

Нина сформулировала для себя правило шире, чем этот пакет: отсутствие квитанции — не успех. Если проверка выводит «прошло» из тишины, она тихо ломается на каждом новом виде отказа — и никто об этом не узнает.

10. Установка и запуск из SKILL_ROOT

Как поставить пакет, проверить подпись и запустить первый шаг.

Ключевая мысль: запуск скриптов только из SKILL_ROOT

Хватит теории — Нина уже открыла терминал, открывай и ты.

Пакет опубликован: @dzhechkov/skills-package-story-page на npmjs.com, исходники — в репозитории. Путь до первого запуска:

  1. поставь пакет навыков в проект: npm i -D @dzhechkov/skills-package-story-page;
  2. если тебе нужна подтверждённая подлинность, до любого запуска выполни dz verify-pack на распакованном артефакте — README называет это предварительным условием со стороны вызывающего, и одна лишь установка навыков его не заменяет;
  3. назначь корень навыка: SKILL_ROOT=./node_modules/@dzhechkov/skills-package-story-page/package-story-page;
  4. сделай первый шаг конвейера — извлечение доказательств.

Почему SKILL_ROOT, а не относительные пути. SKILL_ROOT — абсолютный каталог, в котором лежит установленный SKILL.md. Навыку прямо запрещено искать свои вспомогательные файлы в родительских каталогах монорепозитория: иначе он работал бы у автора и ломался у любого, кто поставил пакет обычным способом.

Грабли, на которые наступила Нина: она запустила скрипт из каталога репозитория, где рядом лежал знакомый ей путь, и получила совсем другую сборку. Правило простое — каждый запуск идёт через node "$SKILL_ROOT/scripts/<файл>.mjs".

Отдельно про подпись: прямая проверка dz verify-pack в авторском каталоге по замыслу падает на package.json, потому что при публикации из него удаляется хук жизненного цикла prepublishOnly. Проверять нужно распакованный опубликованный артефакт — именно он является подписанным объектом для потребителя.

11. Честные границы: что страница НЕ доказывает

Раздел, который читают последним и жалеют, что не первым.

Ключевая мысль: честные границы того, что проверка не доказывает

В README пакета есть раздел «Honest scope», и Нина советует читать его раньше всего остального.

Что проверки доказывают машинно: текущий SHA файла и ограниченный диапазон строк, точный числовой токен с контекстом на одной строке, явную пометку синтетического примера, совпадение имени пакета, точное расположение авторских полей, замкнутость идентификаторов источников, владение доказательством и статусом для каждого пункта — без скрытых, осиротевших и лишних владельцев.

Что они не доказывают — и это записано там же:

  • что утверждение истинно за пределами пакета;
  • что страница красива;
  • что она приводит к целевому действию;
  • что она полностью соответствует стандарту доступности.

Всё это остаётся содержательным и визуальным обзором, то есть работой человека.

Самая важная фраза раздела звучит так: документация самого пакета может содержать ложные утверждения; хеш доказывает, какие байты сделали заявление, а не то, что заявление независимо истинно.

Границы есть и у ресурсов: файлы доказательств читаются как строгий UTF-8 через дескрипторы без переходов по символическим ссылкам, с потолком в один мебибайт на файл и восемь мебибайт суммарно; графы объектов глубже шестидесяти четырёх уровней отклоняются до разбора схемы. Это границы ресурсов и декодирования — не гарантии истины.

И бытовая, но важная деталь: браузерная застава требует системных бинарников браузера. Команда npm test запускает её по умолчанию, а PACKAGE_STORY_SKIP_BROWSER=1 npm test — это явно ослабленная полоса только с модульными тестами, и называть её полной проверкой нельзя.

12. Выбор между страницей и курсом

Твоя очередь решать: story-page или tutorial-factory.

Ключевая мысль: выбор между страницей-историей и полным курсом

Последняя секция — твоя. Нина уже сделала свой выбор, теперь очередь за тобой.

У навыка есть родственник: package-tutorial-factory. Оба берут на вход один существующий пакет, и разница между ними — не в качестве, а в жанре:

  • страница-история нужна, когда человеку надо ПОКАЗАТЬ: один живой пример, устройство, установка, границы. Читают за пять минут, экзамена нет;
  • полный курс нужен, когда человека надо НАУЧИТЬ: секции по порядку, упражнения, достижения, финальный тест.

Этот выбор не оставлен на удачу. В пакете лежит оценка маршрутизации: файл evals/routing.yaml с положительными и отрицательными примерами запросов на двух языках, запускаемая как evals/run-pair-routing.mjs с параметром --out. Отдельная деталь честности эксперимента: ожидаемые владельцы запроса не попадают в приглашение судьи — иначе судья читал бы ответ прямо в вопросе.

Устанавливаемая отдельно копия навыка может передать соседний SKILL.md параметром --sibling-skill, чтобы сравнение шло с настоящим текстом соседа, а не с представлением о нём.

Твоя задача в упражнении ниже: три запроса, и по каждому надо решить, чей это случай. Готового правила «если в запросе есть слово „курс“» не существует — читай, чего человек хочет на выходе: показать или научить.

Частые вопросы

Можно ли натравить проверяющий на чужую страницу, чтобы найти в ней дыры?

Нет. Он рассчитан на выход собственного конвейера: разбирает намеренно закрытое подмножество HTML, не исполняет скрипты страницы и не является сканером безопасности для произвольного стороннего HTML. Для чужих страниц нужен другой инструмент.

У меня нет Firefox и geckodriver. Что я теряю?

Живую проверку вёрстки на четырёх ширинах, измерение фокуса и контраста и наблюдение за сетевыми запросами с отрицательным контролем. Запуск npm test с переменной PACKAGE_STORY_SKIP_BROWSER даёт только модульную полосу — README называет её явно ослабленной, и выдавать её за полный прогон нельзя.

Я хочу написать «пакет ставят тысячи команд». Как это провести через проверку?

Никак, если доказательства нет. Числовое утверждение обязано иметь статус evidenced с локальными строками; внешняя запись с числом детерминированную проверку не проходит. Честный выход — убрать фразу или заменить её тем, что действительно доказано.

Почему dz verify-pack падает прямо в каталоге пакета?

Так и задумано. При публикации из package.json удаляется хук жизненного цикла prepublishOnly, поэтому авторский каталог и опубликованный артефакт отличаются ровно на эту строку. Подписанным объектом для потребителя является распакованный опубликованный артефакт — проверять надо его.

Чем страница-история отличается от соседнего навыка package-tutorial-factory?

Жанром результата. Страница-история показывает работу пакета: один живой пример, механизм, установка, границы, пять минут чтения. Полный курс учит пользоваться: секции по порядку, упражнения, достижения и финальный тест. Выбор проверяется запускаемой оценкой маршрутизации внутри самого пакета.

Что делать, если извлечение доказательств не смогло что-то прочитать?

Ничего не скрывать. Извлечение само перечисляет непрочитанное в списке unknowns и отдельно сообщает, сколько источников и примеров оно включило из найденных — ограниченный обход никогда не выдаётся за полный. Оставь такие пункты со статусом unknown, и страница честно покажет отсутствие доказательства.

Обязательно ли начинать страницу с примера, если пакет сложный?

Да, это правило контракта, а не стилистический совет: пример появляется раньше архитектуры, установки, стоимости, безопасности и раздела вопросов. Если пакет сложный, сузь пример до одного входа и одного проверяемого результата — но не меняй порядок.

Бесплатный интерактивный курс · aicoding.space

Лицензии шрифтов

Onest

Copyright 2021 The Onest Project Authors (https://github.com/googlefonts/onest)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

JetBrainsMono

Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.