Публічний реєстр
Звіт про здоров'я програмного забезпеченнясхема 0.27.0 · метрики 1.13.0 · 2026-07-26 21:17 UTC

canopen-python / canopen

CANopen for Python

PythonMIT★ 562 зірки⑂ 359 форківз вер. 2016 р.Переглянути на GitHub ↗

canopen-python/canopen має індекс здоров’я 69 зі 100, що відповідає смузі «Помірний». Найвищий показник — Engineering Quality (81/100), найнижчий — AI Readiness (51/100). Останнє оновлення було 28 днів тому. Більшість нещодавньої роботи виконують 2 учасники.

69
загалом / 100
Помірний

Індекс здоров'я програмного забезпечення

Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє середнє; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 49 («Під ризиком»). Готовність до ШІ не входить до індексу.

69
Відмінний85-100Зразковий; відповідає практично всім перевіреним критеріям
Добрий70-84Здоровий; незначні прогалини
Помірний50-69Прийнятний, але з помітними прогалинами; рекомендовано перевірку
У зоні ризику30-49Суттєві слабкі місця; впровадження потребує обережності
Критичний1-29Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
ЖиттєздатністьСпільнота тавпровадженняСталість таврядуванняІнженернаякістьБезпекаГотовність доШІ

Профіль оцінок

Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.

Власність

canopen-pythonОрганізація
2 підписники1 публічний репозиторійз лют. 2025 р.

За цим репозиторієм стоїть організація — спільна, підзвітна опіка, здатна пережити будь-якого окремого мейнтейнера.

Пакетні екосистеми

РеєстрПакетВерсіяЗавантажень / місВерсіїОстання публікація
PyPIcanopen2.4.1857 51942355 днів тому

Метрики за категоріями

Життєздатність

Чи живий проєкт — чи пишеться код і чи виходять релізи?

63Помірний · 22% загального індексу
Як обчислюється оцінка
28.8/36Свіжість push — останній push 28 дн. тому
10.4/36Ритм комітів — 15/52 тижнів із комітами
14.8/18Обсяг комітів — 43 комітів за останній рік
10/10OpenSSF Scorecard: Maintained — 26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10
Використані вхідні дані
commits_last_year43
human_commit_share1
days_since_last_push28
active_weeks_last_year15
Як обчислюється оцінка
27/27Випускає релізи — опубліковано 22 релізів
16.2/36Свіжість релізів — останній реліз 355 дн. тому
12.6/27Ритм релізів — реліз кожні ~237 дн.
0/10OpenSSF Scorecard: Signed-Releases — немає даних
Використані вхідні дані
releases_count22
latest_release_tagv2.4.1
releases_from_tagsні
days_since_latest_release355
mean_days_between_releases237
Виключено з оцінювання (немає даних або не застосовно): OpenSSF Scorecard: Signed-Releases. Залишкові ваги перенормовано.

Спільнота та впровадження

Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?

72Добрий · 18% загального індексу
Як обчислюється оцінка
44.6/60Зірки — 562 зірок
21.3/25Форки — 359 форків
7.8/15Спостерігачі — 26 спостерігачів
Використані вхідні дані
forks359
stars562
watchers26
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Як обчислюється оцінка
22.5/22.5README
22.5/22.5Ліцензія — визнана ліцензія (MIT)
0/18Настанови CONTRIBUTING
0/13.5Кодекс поведінки
0/7.2Шаблон issue
0/6.3Шаблон PR
Використані вхідні дані
has_readmeтак
has_licenseтак
has_contributingні
has_issue_templateні
has_code_of_conductні
has_pull_request_templateні
Як обчислюється оцінка
79.1/80Щомісячні завантаження — 857 519 завантажень/місяць у pypi
0/20Залежні пакети в реєстрі — ця екосистема цього не повідомляє
Використані вхідні дані
packagescanopen
dependents
ecosystemspypi
total_downloads
monthly_downloads857 519
Виключено з оцінювання (немає даних або не застосовно): Залежні пакети в реєстрі. Залишкові ваги перенормовано.

Сталість та врядування

Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?

66Помірний · 24% загального індексу
Як обчислюється оцінка
25.2/54Бас-фактор — на 2 контриб’ютор(ів) припадає половина всіх комітів
11.5/22.5Розподіл комітів — головний контриб’ютор — автор 49% комітів
13.5/13.5Широта контриб’юторів — 57 контриб’юторів
10/10OpenSSF Scorecard: Contributors — project has 11 contributing companies or organizations
Використані вхідні дані
bus_factor2
contributors_sampled57
top_contributor_share0,488
Як обчислюється оцінка
39.3/46.8Вирішення issue — закрито 84% issue
30.8/38.3Прийняття PR — злито 230/286 вирішених PR
10.5/15OpenSSF Scorecard: Code-Review — Found 19/25 approved changesets -- score normalized to 7
Використані вхідні дані
merged_prs230
open_issues53
closed_issues278
issue_closed_ratio0,84
closed_unmerged_prs56

Власність та опіка

38У зоні ризику
Як обчислюється оцінка
30/30Підтримка власника — у власності організації
0/20Верифікований домен
3.4/25Охоплення власника — 2 підписників у canopen-python
5/25Послужний список — 1 публічних репозиторіїв, вік облікового запису ~1 р.
Використані вхідні дані
followers2
owner_typeOrganization
is_verified
owner_logincanopen-python
public_repos1
account_age_days517
Як обчислюється оцінка
25/25Опубліковано й доступно — 1 пакет(ів) у pypi
26/35Свіжість публікацій — остання публікація 355 дн. тому
20/20Історія версій — 42 опублікованих версій
20/20Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані
packagescanopen
ecosystemspypi
any_deprecatedні
min_days_since_publish355

Інженерна якість

Чи наявні базові інженерні практики та документація?

81Добрий · 20% загального індексу
Як обчислюється оцінка
24/24Процеси CI — 3 процес(ів) CI
24/24Наявні тести
0/16Конфігурація лінтера
0/9.6Pre-commit-хуки
0/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 23 out of 23 merged PRs checked by a CI test -- score normalized to 10
Використані вхідні дані
has_ciтак
has_testsтак
has_editorconfigні
has_linter_configні
has_precommit_configні

Документація

100Відмінний
Як обчислюється оцінка
30/30README
25/25Каталог документації
15/15Сайт документації / домашня сторінка — http://canopen.readthedocs.io/
10/10Опис репозиторію
10/10Теми — 3 тем
10/10Wiki
Використані вхідні дані
topicscan, canopen, python
has_wikiтак
homepagehttp://canopen.readthedocs.io/
has_readmeтак
has_docs_dirтак
has_descriptionтак

Безпека

Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?

64Помірний · 16% загального індексу

Стан безпеки

55Помірний
Як обчислюється оцінка
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — немає даних
2.5/2.5CI-Tests — 23 out of 23 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
5.2/7.5Code-Review — Found 19/25 approved changesets -- score normalized to 7
2.5/2.5Contributors — project has 11 contributing companies or organizations
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Ліцензія — license file detected
7.5/7.5Maintained — 26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — немає даних
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
6.8/7.5Vulnerabilities — 1 existing vulnerabilities detected
Використані вхідні дані
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate5,5
Виключено з оцінювання (немає даних або не застосовно): branch_protection, signed_releases. Залишкові ваги перенормовано.
Як обчислюється оцінка
35/35Прямі залежності без відомих сповіщень — жодна пряма залежність не має відомих сповіщень
25/25Непрямі залежності без відомих сповіщень — жодна непряма залежність не має відомих сповіщень
0/40Немає задавнених сповіщень — жодне сповіщення не має дати публікації
Використані вхідні дані
sourceosv
advisories0
affected_packages0
assessed_packages4
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
Виключено з оцінювання (немає даних або не застосовно): Немає задавнених сповіщень. Залишкові ваги перенормовано. Звірено з runtime-замиканням залежностей pypi:canopen@2.4.1 — тим, що тягне за собою встановлення опублікованого пакета, — 4 пакетів. Досяжність не аналізується.

Готовність до ШІ

Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Незалежний, експериментальний бейдж — вага 0.0, тож він подається окремо і не впливає на загальний індекс здоров'я.

51Помірний · 0% загального індексу
Як обчислюється оцінка
0/45Інструкції для агентів — немає CLAUDE.md / AGENTS.md / правил редактора
0/15Машиночитана документація (llms.txt)
40/40Читабельна історія комітів — намір зазначено у 91 з 100 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
has_llms_txtні
legible_history_share0,91
agent_instruction_files
agent_instruction_max_bytes
Як обчислюється оцінка
18/18Розгортання однією командою — doc/Makefile
22/22Автоматизовані тести
0/11Конфігурація лінтера / форматера
11/11Статична перевірка типів — canopen/py.typed
0/10Відтворюване середовище
0/10Підтверджена практика роботи з агентами — серед останніх 100 комітів немає створених агентом
0/8Автоматизоване супроводження — автоматичних оновлень залежностей не виявлено
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Використані вхідні дані
has_nixні
has_testsтак
lockfiles
has_dockerfileні
typed_languageні
bootstrap_filesdoc/Makefile
has_devcontainerні
has_linter_configні
typecheck_configscanopen/py.typed
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Як обчислюється оцінка
27/45Типізований код — Python з конфігурацією перевірки типів (canopen/py.typed)
55/55Керовані розміри файлів — 0/49 файлів вихідного коду понад 60 КБ
Використані вхідні дані
primary_languagePython
largest_source_bytes40 222
source_files_sampled49
oversized_source_files0
Як обчислюється оцінка
0/40Схема API (OpenAPI/GraphQL/proto)
0/20Сервер MCP
40/40Придатні до запуску приклади — examples
Використані вхідні дані
example_dirsexamples
has_mcp_signalні
api_schema_files

Ключові факти

562зірок GitHub
57контриб'юторів
43комітів за останні 12 місяців
28днів від останнього пушу
22релізів
2бас-фактор
53відкритих issue
PyPIпакетних екосистем

Попередження щодо збору даних

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token

Докладніше

Історія зірок і форків 0 ★ / 359 ⇿
0Зірки
359Форки
20Релізи

Коли додано кожну зірку й форк — зібрано з GitHub і згруповано за днями. Кумулятивне зростання розміщено просто над денними додаваннями, з яких воно складається, тож їх видно одне проти одного: рівномірне органічне накопичення виглядає зовсім інакше, ніж різкий короткочасний сплеск. Там, де цю різницю можна виміряти, її подано як автентичність росту.

0125250375350272016-102021-082026-06
Мажорні 2Мінорні 13Патчі 5

Кожна точка охоплює 9 днів.

OpenSSF Scorecard 5.5 / 10
5.5сукупно

Незалежна, не прив'язана до інструментів оцінка безпеки від відкритого проєкту OpenSSF Scorecard. Кожна перевірка винагороджує практику безпеки, а не інструмент конкретного постачальника. Перевірки, які Scorecard не зміг визначити, позначено н/д і виключено з оцінки безпеки (вони ніколи не зараховуються як нуль).Scorecard v5.5.0 · 2026-07-26 21:17 UTC

10Binary-Artifactsno binaries found in the repo
н/дBranch-Protectioninternal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
10CI-Tests23 out of 23 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
7Code-ReviewFound 19/25 approved changesets -- score normalized to 7
10Contributorsproject has 11 contributing companies or organizations
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
н/дSigned-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
9Vulnerabilities1 existing vulnerabilities detected
Прямі залежності 1
РеєстрПакетОбмеження версіїМаніфест
PyPIpython-can>= 3.0.0pyproject.toml
Усі залежності 10

Повний розв'язаний набір залежностей із графа залежностей GitHub: 1 прямих і 9 непрямих (транзитивних) пакетів. Транзитивне замикання є повним, коли в репозиторії закомічено lockfile.

РеєстрПакетВерсіяЗв'язок
PyPIpython-canпряма
PyPIcanmatrixнепряма
PyPIfuroнепряма
PyPImypyнепряма
PyPIpytestнепряма
PyPIpytest-covнепряма
PyPIsetuptoolsнепряма
PyPIsetuptools-scmнепряма
PyPIsphinxнепряма
PyPIsphinx-autodoc-typehintsнепряма
Сповіщення про залежності 0

Встановлення pypi:canopen@2.4.1 тягне 4 пакетів, прямих і транзитивних: 0 мають відомі сповіщення, з них 0 — прямі залежності.

Жодне відоме сповіщення не стосується оцінених залежностей.

Сповіщення означає, що версія, записана в графі залежностей, потрапляє в уражений діапазон. Досяжність не аналізується, а граф містить піниї розробки й тестування — знахідка може стосуватися інструментів, а не поставленого коду.

Звіт у форматі JSON машиночитний
{
  "data": {
    "repo": {
      "topics": [
        "can",
        "canopen",
        "python"
      ],
      "is_fork": false,
      "size_kb": 854,
      "has_wiki": true,
      "homepage": "http://canopen.readthedocs.io/",
      "languages": {
        "Shell": 903,
        "Python": 393830
      },
      "pushed_at": "2026-06-28T16:01:52Z",
      "created_at": "2016-09-20T17:42:18Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-21T02:00:12Z",
      "description": "CANopen for Python",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "master",
      "license_spdx_raw": "MIT",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": null,
      "name": null,
      "type": "Organization",
      "login": "canopen-python",
      "company": null,
      "location": null,
      "followers": 2,
      "avatar_url": "https://avatars.githubusercontent.com/u/200581454?v=4",
      "created_at": "2025-02-24T08:54:04Z",
      "is_verified": null,
      "public_repos": 1,
      "account_age_days": 517
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v2.4.1",
          "kind": "patch",
          "published_at": "2025-08-05T11:11:01Z"
        },
        {
          "tag": "v2.4.0",
          "kind": "minor",
          "published_at": "2025-08-05T10:03:28Z"
        },
        {
          "tag": "v2.3.0",
          "kind": "minor",
          "published_at": "2024-06-13T20:35:27Z"
        },
        {
          "tag": "v2.2.0",
          "kind": "minor",
          "published_at": "2023-10-09T18:44:08Z"
        },
        {
          "tag": "v2.1.0",
          "kind": "minor",
          "published_at": "2023-01-03T14:27:22Z"
        },
        {
          "tag": "v2.0.0",
          "kind": "major",
          "published_at": "2022-02-12T11:40:27Z"
        },
        {
          "tag": "v1.2.1",
          "kind": "patch",
          "published_at": "2021-02-20T09:02:35Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2020-11-09T19:25:02Z"
        },
        {
          "tag": "v1.1.0",
          "kind": "minor",
          "published_at": "2020-03-05T19:45:23Z"
        },
        {
          "tag": "v1.0.0",
          "kind": "major",
          "published_at": "2019-10-03T18:33:06Z"
        },
        {
          "tag": "v0.9.0",
          "kind": "minor",
          "published_at": "2019-05-04T11:43:00Z"
        },
        {
          "tag": "v0.8.1",
          "kind": "patch",
          "published_at": "2019-03-06T12:53:29Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2018-11-21T21:06:14Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2018-09-03T17:51:44Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2018-08-05T18:32:13Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2018-03-12T18:41:13Z"
        },
        {
          "tag": "v0.5.1",
          "kind": "patch",
          "published_at": "2017-10-19T18:07:03Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2017-09-27T17:40:21Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2017-01-13T11:48:46Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2016-11-13T19:13:48Z"
        },
        {
          "tag": "v0.3.0.dev5",
          "kind": "other",
          "published_at": "2016-09-26T06:52:37Z"
        },
        {
          "tag": "v0.3.0.dev4",
          "kind": "other",
          "published_at": "2016-09-26T06:53:55Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "14b3336bc973023e82325426adf6d71046ebcf55",
          "body": "- Fix bug where an exception in one EMCY callback would abort all\n  subsequent callbacks; now logs the exception and continues\n- Add test for callback exception handling (verifies fix)\n- Add tests for EmcyConsumer initialization, multiple callback order,\n  error reset variants, wait timeout edge cas\n[…]\nhelper function, including the emcy_received condition\nlock to avoid race conditions.\n\nCo-authored-by: André Colomb <src@andre.colomb.de>\nCo-authored-by: Frieder Schüler <frieder.schueler@bizerba.com>",
          "is_bot": false,
          "headline": "emcy: Catch callback exceptions and expand test coverage (#659)",
          "author_name": "Frieder Schüler",
          "author_login": "friederschueler",
          "committed_at": "2026-06-28T15:28:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3b57fed8d3e5fd54b9fca2f6ebe344f346952cb3",
          "body": "When importing an EDS/DCF file, any key that is not part of the CiA 306\nstandard field set is now collected into a custom_options dict on the object.\nThis allows applications to round-trip vendor-specific metadata without\nlosing it.\n\n* Use getattr/setattr instead of __dict__ access for array entry attributes.\n\nCo-authored-by: FedericoSpada <FedericoSpada@users.noreply.github.com>\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "eds: Support custom_options for unknown EDS fields (#653)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-06-27T21:25:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2a4544aa4e960a373f35ffe5921bde931be2410e",
          "body": "…(#658)\n\n* eds: fix LowLimit/HighLimit parsing for all signed integer types\n\n- Reimplement _calc_bit_length() (only handled INTEGER8/16/32/64)\n  using the same structure types used in ODVariable.\n- Log a warning instead of silently ignoring invalid limit values\n- Add EDS test entries in sample.eds f\n[…]\n\n* Renamed _convert_variable → _decode_from_eds and _revert_variable → _encode_to_eds\n\nCo-authored-by: André Colomb <src@andre.colomb.de>\nCo-authored-by: Frieder Schüler <frieder.schueler@bizerba.com>",
          "is_bot": false,
          "headline": "eds: Fix signed integer limit parsing for all bit widths, log errors …",
          "author_name": "Frieder Schüler",
          "author_login": "friederschueler",
          "committed_at": "2026-06-27T20:44:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ea0efa4db5ddf8266d7acf1b64ce41d7534e944a",
          "body": "The NMT state transition from INITIALISING to PRE-OPERATIONAL causes\nthe heartbeat to start automatically if the parameter is non-zero in\nobject 0x1017:00.  This check however raises an exception if the\nobject does not even exist.\n\nCover this state in tests and explicitly ignore a KeyError when asking\nfor the object.",
          "is_bot": false,
          "headline": "nmt: Fix NmtSlave on a node without a heartbeat producer time. (#672)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-06-24T08:35:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9aab2d430034ba4fbc74325890bdf29db8cd68ae",
          "body": "* Correct type handling in decode_bits() and encode_bits().\n\nContrary to the existing type hints, the methods can handle strings\nfor lookup as well as an arbitrary collection of integer bit offsets.\nCorrect the hints to reflect that and check at runtime to only lookup\nfrom strings.\n\nCorrect return t\n[…]\ncted types for indexing the Bits.\n\n* Test non-string keys for bit access.\n\n* Fix slice-based access without step, useful exception without stop.\n\n* Fix slice-based access without start, cover in test.",
          "is_bot": false,
          "headline": "Correct type handling for bitwise variable access (#663)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-22T12:27:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6688d6399279609f2e4b4cee2b5034f0291b32b6",
          "body": "Use a simple integer zero as dummy value for \"unset\" in instances.\nThis fixes some type checker errors.  Add type hints for functions\ninvolving those values.\n\nFix test coverage for unset and unknown data type.",
          "is_bot": false,
          "headline": "Clarify ODVariable.data_type cannot be None (#666)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-22T11:19:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fc5621bf0bb4f2031c835bc60ec10579499a5b19",
          "body": "* variable: Value description in log message only for integers.\n\n* Raise TypeError when requesting value description of non-int data.\n\nAccording to the type hints, the mapping mechanism was only ever\nintended for integers (emulating an enumeration).  Avoids a later\nDictionaryError for the missing ke\n[…]\nStyle fix (black).\n\nBetter overview if not on one line.\n\n* Avoid else after raise.\n\nSimplify by using the walrus operator.\n\n* Extend test coverage for ODVariable.\n\n* Extend test coverage for Variable.",
          "is_bot": false,
          "headline": "variable: Fix type-related issues with desc property (#667)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-22T09:33:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1d6bd8d1f55593e16cc98dc3662c0e6f9a9df39a",
          "body": "* Fix too narrow type annotations in emcy and network.\n* Return None explicitly where return type expects it.\n* Annotate ODVariable and ODArray parent attribute as for ODRecord.\n\nCo-authored-by: Frieder Schüler <frieder.schueler@bizerba.com>",
          "is_bot": false,
          "headline": "Fix some wrong type hints and mypy errors (#664)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-12T23:06:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "62cf726b59d3dd22810921f367170897a64f7216",
          "body": "- State402 enum and state decoding from statusword\n- Command word generation for state transitions\n- Next-state calculation through the state machine\n- Operation mode switching and reading\n- TPDO callback handling for statusword updates\n- Lookup table consistency checks\n- Full state machine transitions simulating drive controlword /\n  statusword exchange\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Add unit tests for CiA 402 profile (#644)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-05-12T18:43:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7f8347f40e6794b3b3240436df3672733d691a79",
          "body": "* network: Avoid notifier re-creating in connect function\n\n* test_network: Add regression tests for notifier lifecycle\n\nAdd tests to verify that:\n- connect() does not recreate an existing notifier\n- disconnect() releases the notifier\n- disconnect() releases the notifier even when check() raises\n\nCo-authored-by: Frieder Schüler <frieder.schueler@bizerba.com>",
          "is_bot": false,
          "headline": "network: Avoid notifier re-creating in connect function (#621)",
          "author_name": "xndcn",
          "author_login": "xndcn",
          "committed_at": "2026-05-08T12:16:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a4e61d7715b571c186cbe3f2a5d9de5ec964f88a",
          "body": "* eds: Centralize object type logic in build_variable.\n\nPass object type to build_variable() instead of the boolean is_domain\nparameter.  Add type hints to aid in validating all call sites have\nbeen adjusted.\n\n* Remove unnecessary duplicate lookup from ConfigParser.\n\n* Fix mypy errors in build_varia\n[…]\nsumed\nonly None type) on the OD variable classes, to accept strings from\neds.\n\nSilence mypy in import_eds() for a custom attribute addition.\n\n* Respect line-length in build_variable + small style fix.",
          "is_bot": false,
          "headline": "eds: Style and type fixes around build_variable() (#661)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-07T12:34:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "929f4d40e2a85b3498bcc850ccc8eb86a547e19b",
          "body": "Co-authored-by: Christian Sandberg <christiansandberg@users.noreply.github.com>\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "doc: Should check unconfigured state before running fastscan (#290)",
          "author_name": "Youngmin Kim",
          "author_login": "ymkim92",
          "committed_at": "2026-05-07T10:39:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9dbb9fd909ac64fd2075cfb1b09412ae9420ce27",
          "body": "The BaseNode.id attribute can be initialized either from the given\nnode_id parameter, or from the Object Dictionary if zero was passed.\nThe None value as mentioned in the docstring actually violates the\ntype hint, so drop that suggestion.  Make sure the inferred type of\nthe self.id attribute does no\n[…]\n docstring for LocalNode as well, to document the expected\nnode_id \"invalid\" value.  And because there was none at all before,\ninclude a bit of functional description to set the expectations\nstraight.",
          "is_bot": false,
          "headline": "Make sure any BaseNode always has a valid Node ID (#657)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-07T09:36:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "35f4b7fbfdf3623931459203784ee0634026ac14",
          "body": "Line-length 96 is a compomise because 80 is problematic with existing\ncode, but we want a bit of headroom for the next common limit of 100.\n\nString \"normalization\" is complete noise in most cases, that could be\nenabled again after some general cleanup.\n\nMake sure that ruff agrees with the previously used import ordering\nstyle from isort.",
          "is_bot": false,
          "headline": "Add some basic rules for ruff linter and black formatting (#660)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-07T07:54:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5dd8e6f723932e4dcdb02a8d32412525cbfc3ad4",
          "body": "Fix encode_phys precision loss for int64 with factor=1",
          "is_bot": false,
          "headline": "Merge pull request #648 from bizfsc/fix/encode-phys-int64-precision",
          "author_name": "Frieder Schüler",
          "author_login": "friederschueler",
          "committed_at": "2026-05-06T20:08:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b73daa6f9efb0e9799ab48baba2869a16e122d66",
          "body": "Memorized DOMAIN ObjectType for a proper export",
          "is_bot": false,
          "headline": "Merge pull request #655 from FedericoSpada/export_domain_objects",
          "author_name": "Frieder Schüler",
          "author_login": "friederschueler",
          "committed_at": "2026-05-06T20:04:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "aaa495e70f23206aafcadd38b73643de6dbafff8",
          "body": null,
          "is_bot": false,
          "headline": "Small changes requested by the maintainers",
          "author_name": "Federico Spada",
          "author_login": "FedericoSpada",
          "committed_at": "2026-05-06T19:50:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1eba930eb9ba9b49d4aa5bfc8bf6b5b699a9aaf1",
          "body": null,
          "is_bot": false,
          "headline": "Add test for float 1.0 factor promoting to float type.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-06T09:26:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "43ae8926e48528ab3b44a66bb339d7a717514dd0",
          "body": null,
          "is_bot": false,
          "headline": "Reword docstrings to state facts, not changes.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-06T09:21:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c05b49d0cd76830df4a474645d7570db976340f1",
          "body": "* Add unit tests for Variable class.\n* Raise ValueError on invalid fmt argument.\n\nCo-authored-by: Frieder Schüler <frieder.schueler@bizerba.com>",
          "is_bot": false,
          "headline": "Add unit tests for Variable class (#656)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-05-05T20:29:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "473fbd7356a4cbf5f773d3c418413c1f6d76a66b",
          "body": "Change method signatures to accept 'object' as required by the\nLiskov substitution principle (Mapping/Container/object supertypes).\n\nFor __eq__, return NotImplemented for incompatible types instead of\nraising AttributeError. For __contains__ in SdoArray, add isinstance\ncheck to handle non-int arguments gracefully.",
          "is_bot": false,
          "headline": "Fix __contains__ and __eq__ override signatures (#649)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-05-05T19:32:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "67bed3c3ad1e31dd292fd3ebb25130adaf255e5c",
          "body": "- Replace List[int] with list[int] (PEP 585, supported since Python 3.9)\n- Add type annotations for untyped variables (var-annotated)\n- Ignore typing on import for 'canmatrix':\nThis should have the advantage of affecting various type checkers, not\nonly mypy.  When the upstream canmatrix package does support type\nhints (work is underway), this place is likely to get noticed and the\ncomment removed.\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Fix trivial mypy errors (#647)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-05-05T19:19:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f1c01e45ce4227aa9360096ad7fa9708d7802a7e",
          "body": "Changed to save whether a Variable has ObjectType==DOMAIN while importing an EDS, so that it is possible to export it unchanged.",
          "is_bot": false,
          "headline": "Implemented the management for DOMAIN Objects",
          "author_name": "Federico Spada",
          "author_login": "FedericoSpada",
          "committed_at": "2026-05-04T20:44:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2133067bf03691cefc6b9cc41a90218424edd70c",
          "body": "Skip the float division in encode_phys when factor is 1 (the default).\nPreviously, `value /= self.factor` always performed float division,\ncausing up to 10 bits of precision loss for large int64 values\n(e.g. 0x55554444AAAABBBB became 0x55554444AAAABC00).\n\nUse `!= 1` for the comparison since we are c\n[…]\nthe original bug)\n- int type preservation with factor=1\n- rounding with integer factor != 1 (existing behavior preserved)\n- float factor (existing behavior preserved)\n\nBased on the discussion in #611.",
          "is_bot": false,
          "headline": "Fix encode_phys precision loss for int64 with factor=1",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-04-29T11:59:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4e789fe6627b2bd5e4f0133bdff883ea84e60200",
          "body": "- Switch state global/selective commands\n- Node ID and bit timing configuration\n- Activate bit timing and store configuration\n- LSS address inquiry (vendor/product/revision/serial)\n- Timeout and error handling\n- Fast scan protocol\n- Obsolete method aliases\n- LssError exception\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Add unit tests for LSS master (#643)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-04-29T08:26:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "80c6da32e5d79a636a18475d1bfe49d23c0cb827",
          "body": "Also use comparison instead of range() for a minor performance\nimprovement.",
          "is_bot": false,
          "headline": "eds: Fix typo in manufacturer_idices → manufacturer_indices (#642)",
          "author_name": "Frieder Schüler",
          "author_login": "bizfsc",
          "committed_at": "2026-04-29T06:04:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2be54f3e2827de6488ef2c642aac35cfe068ff93",
          "body": null,
          "is_bot": false,
          "headline": "Mention ValueError cause in transmit() docstring.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-04-23T21:09:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "baba4a94482fbd71c26c7a9ee4afd2aa2b4c2607",
          "body": null,
          "is_bot": false,
          "headline": "Mention ValueError cause in docstring.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-04-23T21:05:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fe494bba8f2365179308e839192f9fb28ad04ef2",
          "body": null,
          "is_bot": false,
          "headline": "pdo: Handle None cob_id correctly in __repr__ (#640)",
          "author_name": "Yash Chaudhary",
          "author_login": "stoicfeats",
          "committed_at": "2026-04-23T21:05:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cb1622a931f391fcf6bf3f68718fc48be8efcfc8",
          "body": "The fallback behavior in PdoMaps.__getitem__() tried looking up by\nparameter record index even when no com_offset or map_offset was\ndefined. This caused a possibly wrong PdoMap object to be\nreturned. For example with 0x15FF, the lookup for 0x1600 would\nsucceed.\n\nAdd a test to make sure that an nonexistant parameter record index for\n0x15FF actually does raise a KeyError. Skip the fallback lookup by\nparameter index if no record offsets are defined.",
          "is_bot": false,
          "headline": "pdo: Fix wrongfully returned object for missing index (#636)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-04-07T11:00:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9685880e19f348c0d282ff33296b6902104ef96c",
          "body": "The extended access patterns by communication parameter record index\nonly work for TPDOs. The lower bound check on the number range\nexcludes anything below 0x1600, thus RPDO communication parameters do\nnot work.\n\nExtend test to cover access via 0x1400 and 0x1600 yielding the same\nobject.",
          "is_bot": false,
          "headline": "pdo: Fix lower bound check in access by index (#628)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2026-03-12T19:07:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "db9110e9cd0545b2aa7426243f169d0f863a7640",
          "body": "Move the ARR, DOMAIN, RECORD, VAR constant definitions to their own\n\"objectcodes\" module and add missing ones from the standard.  Rename\nARR to ARRAY.  Add a reverse lookup function code2str() for\nconvenience.\n\nAdjust all uses within the eds module.",
          "is_bot": false,
          "headline": "eds: Factor out object code constants.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-10-26T20:41:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7c6dee5a1b040a3de8ea18263f7d8dad9120e076",
          "body": "Import the OD* classes directly, as the repeatedly used\nobjectdictionary.OD* naming is redundant.",
          "is_bot": false,
          "headline": "eds: Refactor OD classes imports.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-10-26T20:22:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e58a65318eee4b0e3befb1a9f244be7173a0960d",
          "body": "Fix mixed up mapping parameter record index numbers in class PDO. The\naccess by this index number mixed up the two number ranges.  The CiA\n301 standard defines:\n\n* Object 1600h to 17FFh: RPDO mapping parameter\n* Object 1A00h to 1BFFh: TPDO mapping parameter\n\nThe test was also wrong, because it added\n[…]\nwell,\ninstead of overwriting it with a custom dictionary.  To allow this\ndummy usage, adjust its constructor to skip generating entries.  Add\nsome explanation why relative indices cannot be used here.",
          "is_bot": false,
          "headline": "Fix and extend PDO selection by record IDs (fixes #607) (#613)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-09-22T19:39:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "593f0621f41ce3f94c09322ca8414c7e649b128e",
          "body": "* Raise KeyError for zero index.\n* Properly test different access type return values.\n\nAugment test_pdo_getitem() to not only check the mapped object values.\nWhat's more important is the type of object returned, and whether it\nis the correct object (identical to other access method results).",
          "is_bot": false,
          "headline": "PdoBase: Forbid getting item with zero index (fixes #581) (#609)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-09-22T19:18:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "54ac5c2d5ca0324c528f2b5356be06be2b3def1f",
          "body": "Reduce the amount of code within the critical section holding the\nlock.  Unpack and log first.  Update timestamp and state while locked\nand notify condition waiters.  Invoke callbacks after releasing the\nlock.",
          "is_bot": false,
          "headline": "NmtMaster: Delay heartbeat callbacks after state update (#601)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-09-08T21:13:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "829f4628634f1a6f169edfe14254b6aa54a398f5",
          "body": "…) (#600)\n\nRaise a RuntimeError when a SYNC transmission task has previously been\nstarted.  Make sure to clear the internal _task attribute when\nstopping, to make the check work.",
          "is_bot": false,
          "headline": "SyncProducer: Prevent from starting another periodic task (fixes #582…",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-09-08T21:07:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "be9c56d4fe696bb3d2cd63834b3a1245773976b0",
          "body": "* Disregard OD variable description in SdoClient.upload().\n\nThe upload method should behave as a raw producer of byte data.\nInterpretation of the returned data based on the Object Dictionary is\nthe responsibility of the SdoVariable class, which delegates to the\ngeneric ODVariable decoding functions.\n[…]\nonly incompatible change.\n\n* Test expedited upload with 8 byte padded frame.\n\nThe existing test response is actually wrong, since the standard\nmandates four bytes \"data\" regardless of the other flags.",
          "is_bot": false,
          "headline": "Disregard OD variable description in SdoClient.upload() (#592)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-08-18T20:51:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1582ba5b0d5f8f7365d86432ceaca5137b4f4670",
          "body": "* Update license metadata.\n\nAvoid deprecation warnings with modern setuptools, which will stop\nsupporting a table as the project.license option in pyproject.toml\nsoon.  Require a modern version >= 77 to make use of the alternative\noptions.\n\n* Modernize Dict and List type annotations.\n\nSince Python 3.9 we can use the built-in types directly.\n\n* Simplify type import.\n\n* Modernize Iterator type annotations.\n\nSince Python 3.9 the collections.abc module can be used directly for\ntyping.",
          "is_bot": false,
          "headline": "build: Bump minimum Python version to 3.9 (fixes #585) (#610)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-08-18T20:42:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "317fd3d61513b33ddb1aaf8a3ea2c23384a275ee",
          "body": "Commit 1fc63b5f4835eba80462c7892a2cd00f786ec9ce tried to limit the\nbuilt packages to ignore other arbitrary top-level directories.  But\nin turn, it disabled the auto-discovery in setuptools completely.\nThis results in build warnings for the missing sub-packages that would\nhave been auto-discovered.\n\nSwitch to an explicit setuptools.packages.find configuration which\nstill limits to the canopen top-level package.",
          "is_bot": false,
          "headline": "build: Fix package auto-discovery (ref #468) (#608)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-08-05T11:06:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b50616b57afc9ef048469b2064b1b8e9bc0434c3",
          "body": "Partially reverts commit 62e9c1f851cb8721355d22c8571f2b41599c1b36.\nThese RST roles are not supported in the basic renderer used on PyPI.\nThus the package is rejected for upload.",
          "is_bot": false,
          "headline": "Remove Sphinx roles from README.rst",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-08-05T10:47:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "49905727b26fd273eecdb534946e67021d4666ff",
          "body": "* Define constants for all standardized SDO abort codes.\n* Type annotations for constants (Final).\n* Use constants in SDO client and server.\n* Keep literals in tests, but comment and format.",
          "is_bot": false,
          "headline": "sdo: Define constants for all standardized abort codes (#596)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-08-05T09:38:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d35ce74e197bbc88e5a3b796ef5963154421e968",
          "body": "…(#606)",
          "is_bot": false,
          "headline": "eds: Avoid float formatting in BaudRate_xxx export keys (fixes #605) …",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-07-27T20:47:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0130eb88b0c137bdd41cdaa25463d020ae3df6c9",
          "body": "SDO abort messages were sent in some cases when a response times out\nor has an unexpected server command specifier. But not consistently,\nthus the following cases are now added:\n\n    Mismatched scs:\n        ReadableStream, after upload segment request\n        WritableStream, after download initiate \n[…]\ne).\n\n* Avoid duplicate SDO abort messages on timeout in _block_ack()\n   and _end_upload().\n\n* Add docstring to read_response() and detail exceptions.\n\nCo-authored-by: samsam <samfire.pilote@gmail.com>",
          "is_bot": false,
          "headline": "sdo.client: Add missing abort messages (#594)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-07-13T12:40:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "09f6e9e8e9497108790328aed9839f2552acdbae",
          "body": "* Skip saving and log read-only PDO parameters.\n\nFor PDO communication or mapping parameters which are read-only on a\nnode, the PdoMap.save() method currently aborts with an SDO exception.\nHowever, individual parameters may simply not support configuration\nchanges.  Rely on the Object Dictionary to \n[…]\nries, log\nwhat is not being saved.\n\n* Switch to a LocalNode in TestPDO to not require a network.\n\n* Test PdoMap.save() skipping readonly entries.\n\nCo-authored-by: Erlend E. Aasland <erlend@python.org>",
          "is_bot": false,
          "headline": "Gracefully handle readonly entries in PdoMap.save() (fixes #541) (#593)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-06-22T20:17:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8d9aa6c67c07bfe39baae1d02b058e644de23bdc",
          "body": "Co-authored-by: André Colomb <github.com@andre.colomb.de>",
          "is_bot": false,
          "headline": "Prevent warnings if SDO setup fails (#590)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2025-06-18T15:14:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "80349e1326787fe4793858e0477c798489352029",
          "body": "A data size of zero bytes cannot be encoded in the \"number of bytes\nthat do not contain data\" semantics, as that would require n=4, which\nis however limited to two bits (maximum value 3).  So the SDO\nexpedited upload protocol simply cannot convey that condition.  The\ncurrent implementation however s\n[…]\notocol violation by responding with an SDO abort code of\n0800 0024h, No data available.\n\nAdd a test case for this condition, verifying that the client raises\nthe appropriate SdoAbortedError exception.",
          "is_bot": false,
          "headline": "Abort SDO server upload when the object has zero length (#587)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-06-17T16:37:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "de0e644b7d2c6ce11294be40c1da4ab33a15206b",
          "body": "If the lookup by name returns an object (not None), the boolean\nexpression still checks whether that object is equivalent to boolean\nTrue.  This needlessly calls the object's __len__() method, while what\nwe're interested in is just whether an object was found.",
          "is_bot": false,
          "headline": "Fix OD item lookup evaluating subobject's length (fixes #588) (#589)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2025-06-15T18:53:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7fb914a12e239afbb8acc4134e9eb5a6afdbcb9d",
          "body": "When an OD entry is found in SdoClient.upload(), the truncation is\ncurrently skipped only for those types explicitly listed as variable\nlength data (domain, strings).  That causes unknown types to be\ntruncated to whatever their length indicates, which is usually one\nbyte.\n\nInvert the condition to ch\n[…]\nknown size,\na.k.a. those listed in STRUCT_TYPES where the required length can be\ndeduced from the structure format.\n\nRe-enable the unit tests which were skipped based on the previously\nbuggy behavior.",
          "is_bot": false,
          "headline": "Avoid truncating unknown types in SDO upload (fixes #436) (#591)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-06-15T18:20:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7ddb19b2ca7b7a85b9cef3090249b00293dd4b4a",
          "body": "We are waiting for the sequence number to be the same as the last good\nknown sequence number, to start considering the messages.  However,\nthis is wrong because the SDO server will start sending the discarded\nsegments at the start of a new block, which means the sequence number\nrestarts from 1.\n\nRes\n[…]\nssion.\n\nAdd a block retransmit test. This test passes with this implementation\nbut will fail with an invalid CRC without fix for discarded segments.\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Fix check for retransmission of discarded block segments (#546)",
          "author_name": "Samuel Lee",
          "author_login": "samsamfire",
          "committed_at": "2025-06-11T07:55:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c0a4ae50b0487d5d683e6eeff1fd3c3069747a02",
          "body": "Co-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Correctly apply offset to custom timestamp value (fixes #563) (#576)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2025-06-11T07:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "09c1ddd4094358081dd4a79803dacf744ab72ecf",
          "body": "Co-authored-by: Erlend E. Aasland <erlend@python.org>",
          "is_bot": false,
          "headline": "Add some type annotations for the nmt module (#586)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-06-09T20:09:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc917f057500cfa66cbe9b24fa80e268a40f7907",
          "body": "Co-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Type annotate everywhere Network is used (#575)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2025-06-09T12:01:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cf9f33ea88cbade2ee08037926c5a653396e6fab",
          "body": "Estalish a minimal mypy config that ensures annotations are compatible\nwith Python 3.8 (the oldest supported Python version).\n\nAdd a forgiving CI job that does not affect the status of the GitHub\nworkflow run.\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Add mypy CI and config (ref #358) (#518)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2025-06-09T11:50:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e840449fbf9ddf810d14899279cb72e61a2447f0",
          "body": "* Fix multiple assosciations and removals of networks\n* Simplify cleanup logic in Network.unsubscribe().\n\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Fix multiple associations and removals of networks (#573)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2025-04-28T20:36:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f1a71da20b918760ace20c62297f41132cd6618c",
          "body": null,
          "is_bot": false,
          "headline": "Bump Codecov action to version 5.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2025-04-27T18:37:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "48cca7c655f8c932b1f761a8edc11d0702397fab",
          "body": null,
          "is_bot": false,
          "headline": "Update GitHub URLs to new organization",
          "author_name": "Christian Sandberg",
          "author_login": "christiansandberg",
          "committed_at": "2025-04-06T10:25:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5382f245d8ede61b113c6a648cb99b199667f0cd",
          "body": null,
          "is_bot": false,
          "headline": "Updated debian packaging for reprepro (#557)",
          "author_name": "Tony Baltovski",
          "author_login": "tonybaltovski",
          "committed_at": "2025-02-20T20:20:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ae71853be7fb42870bb5763066b0c3c50d015669",
          "body": "Previously, when attempting a write of \"\" to a VISIBLE_STRING, the\r\nexpedited SDO request would not be sent and the transaction would\r\ntimeout.  To resolve this, if the transaction size is known to be 0, a\r\nsegmented transfer is used.",
          "is_bot": false,
          "headline": "Fix SDO writes of empty strings (#551)",
          "author_name": "Thomas Willson",
          "author_login": "willson556",
          "committed_at": "2025-01-05T22:01:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ffbd10f44da89e407622c82b8587fcc64e3f2f49",
          "body": "Follow recommendations from flake8 regarding import order and\ngrouping.  Put two blank lines after the top import block in each\nfile.\n\nImport CanError directly from can package in canopen.sdo.client\ninstead of from canopen.network, where it was not even used.",
          "is_bot": false,
          "headline": "Clean up imports ordering.",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-10-18T07:20:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c781a225f60b1b5d39cfdad713d3d5bb4799c844",
          "body": "… (#525)\n\nAdd an _UnitializedNetwork class and a singleton\r\n_UNINITIALIZED_NETWORK instance.  It can replace the dummy \"None\"\r\nvalue for attribute initializations, which can then be properly typed\r\nas Network to avoid static type checking errors.\r\n\r\nThis has the benefit of not needing `self.network \n[…]\nge (accessing a network without associating it\r\nfirst), which a static checker cannot detect reliably.  The dummy\r\nclass provides a descriptive exception message when any attribute is\r\naccessed on it.",
          "is_bot": false,
          "headline": "Add a dummy object to designate \"uninitialized\" networks (fixes #511)…",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-10-17T19:36:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6bc90a817f049b52fbeb1201c699e90ca36a6136",
          "body": "The count entry itself is not part of the data (according to CiA 301),\r\nthus should not be yielded from an iterator.  That matches the\r\nbehavior of SdoArray, which also yields only the array contents.\r\n\r\nNote that the basis of returned record sub-objects is still the subset\r\ndescribed by the OD, whi\n[…]\nicated by the record's\r\nsubindex 0.  Thus the count (and iteration set) is reduced by one\r\nelement only if the subindex 0 was actually part of this subset.\r\n\r\n* No need to expect test failure anymore.",
          "is_bot": false,
          "headline": "Skip the \"highest subindex\" entry when iterating over SdoRecord (#538)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-08-17T21:05:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "273bab17400f0eb14c5e9e7714fa88de3fce2e7f",
          "body": "* Remove one of the sub-objects in a record for testing.\r\n\r\nAdjust the EDS test accordingly, since the record length only counts\r\nsub-objects that have an actual description.\r\n\r\n* Flesh out the Pre-defined error field object in sample.eds.\r\n\r\nSwitch from CompactSubObj to actual sub-entries.  Leave o\n[…]\n SdoArray dynamically generated member variables.\r\n\r\n* Add test for SdoArray length and iteration count.\r\n\r\n* Add test for SdoRecord length and iteration count.\r\n\r\n* Expect failure on last added test.",
          "is_bot": false,
          "headline": "Tests: Verify some basic SDO record and array assumptions (#539)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-08-17T20:59:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "210374a857b8afbb9fb2bf2a93d374f4e0f70563",
          "body": "* Speed up NMT tests\r\n\r\n* Make test_emcy, test_eds, and test_od executable scripts\r\n\r\n* Harden and speed up test_network_send_periodic()\r\n\r\n* Shorten heartbeat period in test_local\r\n\r\n* Join as cleanup action\r\n\r\n* Fix test_network_send_periodic() periodicity test",
          "is_bot": false,
          "headline": "Tests: speed up test suite (#537)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-15T06:46:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1b7c06f57ba8a46afbddfa2b4ca29196e63a465d",
          "body": "* Annotate PeriodicMessageTask in NmtMaster and NmtSlave.\r\n\r\n* Type annotate Network.notifier.\r\n\r\n* Remove duplicate initialization of PeriodicMessageTask._task.\r\n\r\nThe attribute is set within the method directly following.  So there\r\nis no need to initialize it to None before, only to confuse type\r\ncheckers.",
          "is_bot": false,
          "headline": "Fix some type checker errors in network module (#534)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-08-14T21:17:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6ef4f5e35d84a7d776d1758842bc8138215e9715",
          "body": "* Add configurable timeouts for Network.notifier.\r\n\r\nMove the hard-coded 1 second maximum cycle duration passed during\r\ncan.Notifier construction to a class variable NOTIFIER_CYCLE.\r\nExplicitly specify the timeout to stop the notifier in another class\r\nvariable NOTIFIER_SHUTDOWN_TIMEOUT, matching th\n[…]\nuring tests.\r\n\r\nExplicitly set the NOTIFIER_SHUTDOWN_TIMEOUT to zero in all Network\r\ninstances created for unit tests.\r\n\r\nThis reduces total test running time from ~30 to ~9 seconds when run\r\nlocally.",
          "is_bot": false,
          "headline": "Make Network.notifier timeouts customizable (fixes #530) (#536)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-08-14T10:47:13Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "932c6212bf077596dbfec91fd6ca66308142b972",
          "body": "Also:\r\n- bump GitHub checkout action to v4\r\n- bump GitHub setup-python action to v5\r\n- ignore README and LICENSE files\r\n- invoke pip according to best practise\r\n\r\nFollow-up of #498",
          "is_bot": false,
          "headline": "CI: Add requirements-dev.txt and add pip cache (#533)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-12T10:59:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4301c4bfed020565bfdb939deaf30b8d49de5727",
          "body": "* Tests: harden test_emcy_consumer_wait\r\n\r\nWait a little bit longer before dispatching the EMCY packet, and start\r\nthe one-shot timer just before invoking the wait() call.\r\n\r\n* Better resource management",
          "is_bot": false,
          "headline": "Tests: harden test_emcy_consumer_wait (#532)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-12T10:01:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "83594ac89ee8f07d3bf346f3ceeab3b52d954625",
          "body": null,
          "is_bot": false,
          "headline": "Tests: improve SYNC coverage (#531)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-12T08:22:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "78f6dded0b714e542dd0c5bac7af73b8012b499c",
          "body": null,
          "is_bot": false,
          "headline": "Docs: remove unused Sphinx config 'html_static_path' (#521)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-12T07:45:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5004363e011b5f22b5e16825328189c81889a205",
          "body": null,
          "is_bot": false,
          "headline": "Tests: improve EMCY coverage (#529)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-12T07:27:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "28ee18c14a628671ebeb6105f3cd1ed02af02cc4",
          "body": "The .active functionality was purged on June 9, 2017\r\nwith commit a28ef2762.",
          "is_bot": false,
          "headline": "Remove unused NodeScanner.active member (#527)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-11T20:50:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d75c5f09b11b1ffee200bd8813efac5c8f0127e8",
          "body": "This fixes up the tests added with #517 (commit\r\nd1c28e51b93c7d45fce40187672e7ab7a0616cac).",
          "is_bot": false,
          "headline": "Make node scanner tests more deterministic (#526)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-11T20:40:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d1c28e51b93c7d45fce40187672e7ab7a0616cac",
          "body": null,
          "is_bot": false,
          "headline": "Tests: improve coverage of NodeScanner (#517)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-06T20:08:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ecf216a760c8f0016826bb0c4f8e1f7944951d21",
          "body": "- take into account that payloads with DATA1 may be in flight when we\r\n  update the task\r\n- test task.stop()\r\n- use more lenient timeout\r\n- use threading.Condition for improved readability\r\n- wait for periodicity to be established before validation; some\r\n  platforms are a little bit flaky\r\n\r\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Tests: harden TestNetwork and TestNmt even more (#508)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-06T20:03:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d7e2f17c6aeef6aea2e6970f3f80064a3b8b2cc",
          "body": null,
          "is_bot": false,
          "headline": "Tests: improve coverage of Network (#515)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-08-06T19:43:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a196e1e3b62bddcfd62cea9fc5293efe8a8cc0a4",
          "body": "Always require dependencies to be installed.\r\n\r\nResolved #509",
          "is_bot": false,
          "headline": "Purge import hack from network.py (#510)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-10T14:47:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "55d870e2d4fd1f36141712ebad318e6b611ea50c",
          "body": "Windows fails with a permission error if the temporary file is already\r\nopened.\r\n\r\nFixes #501",
          "is_bot": false,
          "headline": "Tests: make sure temporary files are pre-closed (#507)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-10T14:44:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3aa509db9fea8094516e5eb717be3df182f0751a",
          "body": "- make tests deterministic\r\n- use cleanup hooks\r\n- check the periodicity of heartbeats, by number of triggers\r\n- roughly check the interval",
          "is_bot": false,
          "headline": "Tests: harden TestNetwork (#505)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-09T11:06:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5207b322907b9d3fa2a4343ed17dc4d89fe26109",
          "body": "Fixes #500",
          "is_bot": false,
          "headline": "Ensure NmtBase.state always returns a string (#506)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-09T10:58:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fa74236b3f3312c03d09e72de3fa6d3cf077fbec",
          "body": null,
          "is_bot": false,
          "headline": "Add tests for NMT base and NMT master (#504)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-09T10:15:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c4560daa37bec67a663046834a42b8058b5ceedc",
          "body": null,
          "is_bot": false,
          "headline": "Build docs in CI (fixes #497) (#498)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-09T06:54:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6bd39ba8f1fef7e32732fcc61ae548aa2392b7e0",
          "body": "The canmatrix optional dependency was removed on Oct 10, 2021 with commit\r\nc46228f. It is now added back as an optional dependency, using the same\r\nname as previously: db_export.\r\n\r\nTo install the dependency:\r\n\r\n$ python3 -m pip install 'canopen[db_export]'\r\n\r\nResolves #488",
          "is_bot": false,
          "headline": "Add back PdoBase.export() dependency (canmatrix) (#493)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-09T06:51:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "08eba81f519e637e0c20d58560ca9d6637e0adfe",
          "body": "Give NMT tests their own file, and establish a util.py for the test\r\nsuite for common stuff like the location of sample EDS files.\r\n\r\nConsistently use SAMPLE_EDS instead of EDS_PATH.",
          "is_bot": false,
          "headline": "Tests: establish test/util.py and test/test_nmt.py (#503)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-08T13:42:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c413eaffd0e13389512cfdf43dd51694842a427b",
          "body": null,
          "is_bot": false,
          "headline": "Git: correctly ignore docs build directory (#499)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-08T10:41:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "62e9c1f851cb8721355d22c8571f2b41599c1b36",
          "body": "- add formatting to Git branch names\r\n- add formatting to Python module names\r\n- spell out Python version support",
          "is_bot": false,
          "headline": "README: Add formatting and amend doc build instructions (#494)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-06T17:44:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "36c8488febc20e1d351135c90dd8966ab1d07dfb",
          "body": "Handle PDO-related objects through the associated methods in the PdoBase class when initializing a RemoteNode from an ObjectDictionary with provided values (DCF file usually).  This avoids the locally cached mapping information to get out of sync with the PDO configuration parameters, and thus elimi\n[…]\n.read() method must honor DCF-provided values for this purpose.  It previously only respected the OD default values when called with from_od=True.\r\n\r\nCo-authored-by: André Colomb <src@andre.colomb.de>",
          "is_bot": false,
          "headline": "Add support for loading DCF configuration to remote node (#427)",
          "author_name": "meifonglow",
          "author_login": "meifonglow",
          "committed_at": "2024-07-04T20:45:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "02ebc0c44fac791ca44f232c6d6ac1950617bfab",
          "body": null,
          "is_bot": false,
          "headline": "Add tests for bitrate parsing in import_eds() (#495)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-04T10:40:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f1315d3df4354cf02ac2acc70b53b91761c2f242",
          "body": "The CiA306 spec is not quite clear on which fields are mandatory\r\nwithin the DeviceComissioning section.  Thus it should be assumed that\r\nthe two options handled here could be missing.  Instead of raising an\r\nexception, ignore the absence of the Baudrate setting.",
          "is_bot": false,
          "headline": "Gracefully handle missing Baudrate setting in DCF (#492)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-07-03T22:28:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6a4ca1184e219c64768fbb0eb8434b04320d4803",
          "body": "Only for DCF-files (which include a DeviceComissioning section), the Node-ID from the file is stored in the ObjectDictionary instance.  When given an override value via the `import_od(..., node_id=...)` argument, that was however not applied to the OD attribute, but only used for $NODEID interpolati\n[…]\nn.\r\n\r\nMake sure the interpolated values match the stored node_id in this case.\r\n\r\nAlso handle a missing NodeID option within the DeviceComissioning section gracefully, instead of raising an exception.",
          "is_bot": false,
          "headline": "Save the passed node ID to the resulting OD in import_od() (#484)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-03T22:08:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2938a908fb3cb93a65db5d30a7d889a08f66c956",
          "body": "The bustype keyword has long been replaced by \"interface\" and the\r\nformer is deprecated in python-can.  The Network class passes it\r\nthrough without caring for the name, but documentation and tests\r\nreferred to the older name regularly.",
          "is_bot": false,
          "headline": "Adapt to renamed \"bustype\" parameter on can.Bus (#489)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-07-03T21:20:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc37aee4e172a5a83f9f9dc956cc66d82bd1a8b7",
          "body": "Fixes #485",
          "is_bot": false,
          "headline": "Bail early if an unsupported doctype is passed to export_od() (#486)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-03T09:40:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "92a6e11168f5072403a8ec03a7de0195854674cb",
          "body": null,
          "is_bot": false,
          "headline": "Remove temporary files created in EDS tests (#482)",
          "author_name": "Svein Seldal",
          "author_login": "sveinse",
          "committed_at": "2024-07-02T15:25:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "65bfaab0fd4d826be125dc0045834b179e47fb22",
          "body": null,
          "is_bot": false,
          "headline": "Reduce diff",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T11:56:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "71da1bc38f56e602d95686adec22abd3ee0d3939",
          "body": "Clean-up post #474",
          "is_bot": false,
          "headline": "Remove temporary files created in EDS tests",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T11:54:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e3af0eb31bdfde2203d56f1dd953dbd23b4b6609",
          "body": "* Test export_eds() to stdout\r\n* Use StringIO as a context manager iso. addCleanup\r\n* Expand the test to cover most paths\r\n* Expand test for unknown doctypes\r\n* Use subTest for better debugging",
          "is_bot": false,
          "headline": "Increase export_eds() test coverage (#474)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T11:26:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "028a57f49510803eeef4965b95f3aa76ce18e648",
          "body": "Resolves #472\r\n\r\n* Expose and improve the docstrings of export_od and import_id\r\n* export_od(od, None, doctype) does not return a string; it writes to stdout\r\n* Clarify 'doc_type' details\r\n* Add exception info",
          "is_bot": false,
          "headline": "Expose and improve the docstrings of `export_od` and `import_id` (#473)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T11:21:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d141e13970ce5bb5e696a97b92131356d34092bc",
          "body": "Spell out how it works and link to PdoBase.read()\r\n\r\nMoreover, add return annotation to make sure that's included in the\r\nrendered docs.",
          "is_bot": false,
          "headline": "Improve the RemoteNode.load_configuration() docs (#477)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T11:19:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "503918c9bd9ff0499a604c7805e7627ca38fb332",
          "body": "Fixes #475\r\n\r\n* Test the offending suffix is part of the error message\r\n* Make the exception message more helpful",
          "is_bot": false,
          "headline": "Raise ValueError for unknown formats in the import/export OD APIs (#476)",
          "author_name": "Erlend E. Aasland",
          "author_login": "erlend-aasland",
          "committed_at": "2024-07-02T10:32:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5734f37ee758e1d025701b4d40e0b89a57db2317",
          "body": "* Reintroduce PdoMap and PdoVariable to public API of sub-package pdo.\r\n\r\nWith commit 0285f582beb344de708ce8308274a99bc04988a1, the Map class\r\nwas inadvertently removed from the imports directly available within\r\nthe canopen.pdo package.  However, it is referenced as\r\ncanopen.pdo.PdoMap in the p402 \n[…]\nc API.\r\n\r\n* Use type hints for PdoMap in BaseNode402.\r\n\r\nReplace the sphinx parameter type documentation with a proper type\r\nhint annotation.  Same for the tpdo_pointers and rpdo_pointers\r\nattributes.",
          "is_bot": false,
          "headline": "Export PdoMap and PdoVariable classes in pdo module (#464)",
          "author_name": "André Colomb",
          "author_login": "acolomb",
          "committed_at": "2024-07-02T10:04:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 22,
      "commits_last_year": 43,
      "latest_release_at": "2025-08-05T11:11:01Z",
      "latest_release_tag": "v2.4.1",
      "releases_from_tags": false,
      "days_since_last_push": 28,
      "active_weeks_last_year": 15,
      "days_since_latest_release": 355,
      "mean_days_between_releases": 237
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 37,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "canopen",
          "exists": true,
          "license": null,
          "keywords": [
            "Development Status :: 5 - Production/Stable",
            "Intended Audience :: Developers",
            "License :: OSI Approved :: MIT License",
            "Operating System :: OS Independent",
            "Programming Language :: Python :: 3 :: Only",
            "Topic :: Scientific/Engineering"
          ],
          "ecosystem": "pypi",
          "matches_repo": true,
          "registry_url": "https://pypi.org/project/canopen/",
          "is_deprecated": false,
          "latest_version": "2.4.1",
          "repository_url": "https://github.com/canopen-python/canopen",
          "versions_count": 42,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 857519,
          "first_published_at": "2016-09-20T18:01:29.141719Z",
          "latest_published_at": "2025-08-05T11:11:39.476697Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 355
        }
      ]
    },
    "popularity": {
      "forks": 359,
      "stars": 562,
      "watchers": 26,
      "fork_history": {
        "days": [
          {
            "date": "2016-10-22",
            "count": 1
          },
          {
            "date": "2016-11-01",
            "count": 1
          },
          {
            "date": "2016-11-03",
            "count": 1
          },
          {
            "date": "2016-11-21",
            "count": 1
          },
          {
            "date": "2016-12-13",
            "count": 1
          },
          {
            "date": "2016-12-22",
            "count": 1
          },
          {
            "date": "2017-01-09",
            "count": 1
          },
          {
            "date": "2017-04-13",
            "count": 1
          },
          {
            "date": "2017-05-08",
            "count": 1
          },
          {
            "date": "2017-07-06",
            "count": 1
          },
          {
            "date": "2017-07-27",
            "count": 1
          },
          {
            "date": "2017-08-09",
            "count": 1
          },
          {
            "date": "2017-09-01",
            "count": 2
          },
          {
            "date": "2017-09-15",
            "count": 1
          },
          {
            "date": "2017-10-09",
            "count": 1
          },
          {
            "date": "2017-11-04",
            "count": 1
          },
          {
            "date": "2017-11-06",
            "count": 1
          },
          {
            "date": "2018-01-03",
            "count": 1
          },
          {
            "date": "2018-01-09",
            "count": 1
          },
          {
            "date": "2018-01-11",
            "count": 1
          },
          {
            "date": "2018-02-01",
            "count": 1
          },
          {
            "date": "2018-03-22",
            "count": 1
          },
          {
            "date": "2018-03-30",
            "count": 1
          },
          {
            "date": "2018-04-03",
            "count": 1
          },
          {
            "date": "2018-04-18",
            "count": 1
          },
          {
            "date": "2018-06-19",
            "count": 1
          },
          {
            "date": "2018-07-17",
            "count": 1
          },
          {
            "date": "2018-07-24",
            "count": 1
          },
          {
            "date": "2018-07-26",
            "count": 1
          },
          {
            "date": "2018-08-07",
            "count": 2
          },
          {
            "date": "2018-08-17",
            "count": 1
          },
          {
            "date": "2018-09-05",
            "count": 1
          },
          {
            "date": "2018-10-26",
            "count": 1
          },
          {
            "date": "2018-10-28",
            "count": 1
          },
          {
            "date": "2018-11-06",
            "count": 1
          },
          {
            "date": "2018-11-27",
            "count": 1
          },
          {
            "date": "2018-12-26",
            "count": 1
          },
          {
            "date": "2019-01-08",
            "count": 1
          },
          {
            "date": "2019-01-14",
            "count": 1
          },
          {
            "date": "2019-02-16",
            "count": 1
          },
          {
            "date": "2019-03-07",
            "count": 1
          },
          {
            "date": "2019-03-14",
            "count": 2
          },
          {
            "date": "2019-03-22",
            "count": 1
          },
          {
            "date": "2019-03-28",
            "count": 1
          },
          {
            "date": "2019-04-02",
            "count": 1
          },
          {
            "date": "2019-05-17",
            "count": 1
          },
          {
            "date": "2019-05-21",
            "count": 1
          },
          {
            "date": "2019-06-06",
            "count": 1
          },
          {
            "date": "2019-06-12",
            "count": 1
          },
          {
            "date": "2019-06-13",
            "count": 1
          },
          {
            "date": "2019-06-23",
            "count": 1
          },
          {
            "date": "2019-07-24",
            "count": 1
          },
          {
            "date": "2019-08-23",
            "count": 1
          },
          {
            "date": "2019-08-24",
            "count": 1
          },
          {
            "date": "2019-08-27",
            "count": 1
          },
          {
            "date": "2019-09-04",
            "count": 1
          },
          {
            "date": "2019-09-09",
            "count": 1
          },
          {
            "date": "2019-09-14",
            "count": 1
          },
          {
            "date": "2019-10-20",
            "count": 1
          },
          {
            "date": "2019-10-21",
            "count": 1
          },
          {
            "date": "2019-11-08",
            "count": 1
          },
          {
            "date": "2019-11-14",
            "count": 2
          },
          {
            "date": "2019-11-19",
            "count": 1
          },
          {
            "date": "2020-01-20",
            "count": 1
          },
          {
            "date": "2020-01-28",
            "count": 1
          },
          {
            "date": "2020-01-30",
            "count": 1
          },
          {
            "date": "2020-03-19",
            "count": 1
          },
          {
            "date": "2020-04-07",
            "count": 1
          },
          {
            "date": "2020-04-15",
            "count": 1
          },
          {
            "date": "2020-04-24",
            "count": 1
          },
          {
            "date": "2020-04-29",
            "count": 1
          },
          {
            "date": "2020-05-13",
            "count": 1
          },
          {
            "date": "2020-05-18",
            "count": 1
          },
          {
            "date": "2020-06-02",
            "count": 1
          },
          {
            "date": "2020-08-11",
            "count": 1
          },
          {
            "date": "2020-09-22",
            "count": 1
          },
          {
            "date": "2020-09-30",
            "count": 1
          },
          {
            "date": "2020-10-11",
            "count": 1
          },
          {
            "date": "2020-10-31",
            "count": 1
          },
          {
            "date": "2020-11-02",
            "count": 1
          },
          {
            "date": "2020-11-20",
            "count": 1
          },
          {
            "date": "2021-01-13",
            "count": 1
          },
          {
            "date": "2021-01-17",
            "count": 1
          },
          {
            "date": "2021-01-20",
            "count": 1
          },
          {
            "date": "2021-02-08",
            "count": 1
          },
          {
            "date": "2021-02-17",
            "count": 1
          },
          {
            "date": "2021-02-18",
            "count": 1
          },
          {
            "date": "2021-02-24",
            "count": 1
          },
          {
            "date": "2021-03-03",
            "count": 1
          },
          {
            "date": "2021-03-09",
            "count": 1
          },
          {
            "date": "2021-03-22",
            "count": 1
          },
          {
            "date": "2021-04-05",
            "count": 1
          },
          {
            "date": "2021-04-07",
            "count": 1
          },
          {
            "date": "2021-04-14",
            "count": 1
          },
          {
            "date": "2021-04-29",
            "count": 1
          },
          {
            "date": "2021-05-02",
            "count": 1
          },
          {
            "date": "2021-06-09",
            "count": 1
          },
          {
            "date": "2021-07-27",
            "count": 1
          },
          {
            "date": "2021-08-05",
            "count": 1
          },
          {
            "date": "2021-09-02",
            "count": 1
          },
          {
            "date": "2021-09-06",
            "count": 1
          },
          {
            "date": "2021-10-11",
            "count": 1
          },
          {
            "date": "2021-10-23",
            "count": 1
          },
          {
            "date": "2021-10-28",
            "count": 1
          },
          {
            "date": "2021-11-03",
            "count": 1
          },
          {
            "date": "2021-11-09",
            "count": 1
          },
          {
            "date": "2021-11-11",
            "count": 1
          },
          {
            "date": "2021-11-12",
            "count": 1
          },
          {
            "date": "2021-11-27",
            "count": 1
          },
          {
            "date": "2021-12-09",
            "count": 1
          },
          {
            "date": "2021-12-10",
            "count": 1
          },
          {
            "date": "2021-12-31",
            "count": 1
          },
          {
            "date": "2022-01-06",
            "count": 1
          },
          {
            "date": "2022-02-11",
            "count": 2
          },
          {
            "date": "2022-03-08",
            "count": 2
          },
          {
            "date": "2022-03-15",
            "count": 1
          },
          {
            "date": "2022-03-22",
            "count": 1
          },
          {
            "date": "2022-04-03",
            "count": 1
          },
          {
            "date": "2022-05-04",
            "count": 1
          },
          {
            "date": "2022-05-07",
            "count": 1
          },
          {
            "date": "2022-05-11",
            "count": 1
          },
          {
            "date": "2022-05-12",
            "count": 1
          },
          {
            "date": "2022-05-13",
            "count": 1
          },
          {
            "date": "2022-05-17",
            "count": 1
          },
          {
            "date": "2022-05-20",
            "count": 1
          },
          {
            "date": "2022-06-14",
            "count": 1
          },
          {
            "date": "2022-07-08",
            "count": 1
          },
          {
            "date": "2022-08-06",
            "count": 1
          },
          {
            "date": "2022-08-25",
            "count": 1
          },
          {
            "date": "2022-08-26",
            "count": 1
          },
          {
            "date": "2022-08-30",
            "count": 1
          },
          {
            "date": "2022-09-01",
            "count": 1
          },
          {
            "date": "2022-09-14",
            "count": 1
          },
          {
            "date": "2022-09-22",
            "count": 1
          },
          {
            "date": "2022-11-03",
            "count": 1
          },
          {
            "date": "2023-01-23",
            "count": 1
          },
          {
            "date": "2023-01-29",
            "count": 1
          },
          {
            "date": "2023-01-30",
            "count": 1
          },
          {
            "date": "2023-02-02",
            "count": 1
          },
          {
            "date": "2023-02-09",
            "count": 1
          },
          {
            "date": "2023-02-22",
            "count": 1
          },
          {
            "date": "2023-02-27",
            "count": 1
          },
          {
            "date": "2023-03-06",
            "count": 1
          },
          {
            "date": "2023-03-14",
            "count": 1
          },
          {
            "date": "2023-03-16",
            "count": 1
          },
          {
            "date": "2023-04-28",
            "count": 1
          },
          {
            "date": "2023-05-09",
            "count": 1
          },
          {
            "date": "2023-05-18",
            "count": 1
          },
          {
            "date": "2023-05-24",
            "count": 1
          },
          {
            "date": "2023-05-26",
            "count": 1
          },
          {
            "date": "2023-06-06",
            "count": 1
          },
          {
            "date": "2023-06-19",
            "count": 1
          },
          {
            "date": "2023-07-06",
            "count": 1
          },
          {
            "date": "2023-07-07",
            "count": 1
          },
          {
            "date": "2023-07-19",
            "count": 1
          },
          {
            "date": "2023-08-24",
            "count": 2
          },
          {
            "date": "2023-09-01",
            "count": 1
          },
          {
            "date": "2023-10-18",
            "count": 1
          },
          {
            "date": "2023-10-24",
            "count": 1
          },
          {
            "date": "2023-10-28",
            "count": 1
          },
          {
            "date": "2023-11-14",
            "count": 1
          },
          {
            "date": "2023-11-28",
            "count": 1
          },
          {
            "date": "2023-11-29",
            "count": 1
          },
          {
            "date": "2023-12-02",
            "count": 1
          },
          {
            "date": "2023-12-19",
            "count": 1
          },
          {
            "date": "2024-01-08",
            "count": 1
          },
          {
            "date": "2024-02-28",
            "count": 1
          },
          {
            "date": "2024-03-10",
            "count": 1
          },
          {
            "date": "2024-03-20",
            "count": 1
          },
          {
            "date": "2024-03-28",
            "count": 1
          },
          {
            "date": "2024-04-24",
            "count": 1
          },
          {
            "date": "2024-05-20",
            "count": 1
          },
          {
            "date": "2024-06-04",
            "count": 1
          },
          {
            "date": "2024-06-29",
            "count": 1
          },
          {
            "date": "2024-07-10",
            "count": 1
          },
          {
            "date": "2024-07-15",
            "count": 2
          },
          {
            "date": "2024-07-29",
            "count": 1
          },
          {
            "date": "2024-10-05",
            "count": 1
          },
          {
            "date": "2024-10-28",
            "count": 1
          },
          {
            "date": "2024-11-25",
            "count": 1
          },
          {
            "date": "2025-02-12",
            "count": 1
          },
          {
            "date": "2025-02-25",
            "count": 1
          },
          {
            "date": "2025-03-02",
            "count": 1
          },
          {
            "date": "2025-04-04",
            "count": 1
          },
          {
            "date": "2025-04-23",
            "count": 1
          },
          {
            "date": "2025-06-17",
            "count": 1
          },
          {
            "date": "2025-07-21",
            "count": 2
          },
          {
            "date": "2025-08-05",
            "count": 1
          },
          {
            "date": "2025-08-06",
            "count": 1
          },
          {
            "date": "2025-08-20",
            "count": 1
          },
          {
            "date": "2025-09-01",
            "count": 1
          },
          {
            "date": "2025-09-02",
            "count": 1
          },
          {
            "date": "2025-09-10",
            "count": 1
          },
          {
            "date": "2025-09-14",
            "count": 1
          },
          {
            "date": "2025-09-21",
            "count": 1
          },
          {
            "date": "2025-09-27",
            "count": 1
          },
          {
            "date": "2025-11-07",
            "count": 1
          },
          {
            "date": "2025-12-01",
            "count": 1
          },
          {
            "date": "2025-12-09",
            "count": 1
          },
          {
            "date": "2026-01-05",
            "count": 3
          },
          {
            "date": "2026-01-30",
            "count": 1
          },
          {
            "date": "2026-02-27",
            "count": 2
          },
          {
            "date": "2026-03-05",
            "count": 2
          },
          {
            "date": "2026-03-06",
            "count": 2
          },
          {
            "date": "2026-03-07",
            "count": 5
          },
          {
            "date": "2026-03-08",
            "count": 3
          },
          {
            "date": "2026-03-09",
            "count": 6
          },
          {
            "date": "2026-03-10",
            "count": 5
          },
          {
            "date": "2026-03-12",
            "count": 3
          },
          {
            "date": "2026-03-13",
            "count": 2
          },
          {
            "date": "2026-03-14",
            "count": 1
          },
          {
            "date": "2026-03-15",
            "count": 3
          },
          {
            "date": "2026-03-16",
            "count": 1
          },
          {
            "date": "2026-03-17",
            "count": 2
          },
          {
            "date": "2026-03-18",
            "count": 5
          },
          {
            "date": "2026-03-19",
            "count": 4
          },
          {
            "date": "2026-03-20",
            "count": 2
          },
          {
            "date": "2026-03-21",
            "count": 7
          },
          {
            "date": "2026-03-22",
            "count": 2
          },
          {
            "date": "2026-03-23",
            "count": 2
          },
          {
            "date": "2026-03-24",
            "count": 4
          },
          {
            "date": "2026-03-25",
            "count": 1
          },
          {
            "date": "2026-03-26",
            "count": 6
          },
          {
            "date": "2026-03-27",
            "count": 3
          },
          {
            "date": "2026-03-28",
            "count": 1
          },
          {
            "date": "2026-03-29",
            "count": 2
          },
          {
            "date": "2026-03-30",
            "count": 1
          },
          {
            "date": "2026-03-31",
            "count": 2
          },
          {
            "date": "2026-04-01",
            "count": 3
          },
          {
            "date": "2026-04-02",
            "count": 4
          },
          {
            "date": "2026-04-03",
            "count": 2
          },
          {
            "date": "2026-04-04",
            "count": 1
          },
          {
            "date": "2026-04-06",
            "count": 2
          },
          {
            "date": "2026-04-07",
            "count": 5
          },
          {
            "date": "2026-04-08",
            "count": 1
          },
          {
            "date": "2026-04-09",
            "count": 1
          },
          {
            "date": "2026-04-10",
            "count": 2
          },
          {
            "date": "2026-04-11",
            "count": 1
          },
          {
            "date": "2026-04-12",
            "count": 1
          },
          {
            "date": "2026-04-13",
            "count": 1
          },
          {
            "date": "2026-04-14",
            "count": 2
          },
          {
            "date": "2026-04-15",
            "count": 2
          },
          {
            "date": "2026-04-16",
            "count": 3
          },
          {
            "date": "2026-04-17",
            "count": 3
          },
          {
            "date": "2026-04-18",
            "count": 2
          },
          {
            "date": "2026-04-20",
            "count": 1
          },
          {
            "date": "2026-04-21",
            "count": 3
          },
          {
            "date": "2026-04-22",
            "count": 1
          },
          {
            "date": "2026-04-23",
            "count": 2
          },
          {
            "date": "2026-04-24",
            "count": 1
          },
          {
            "date": "2026-04-27",
            "count": 1
          },
          {
            "date": "2026-04-28",
            "count": 1
          },
          {
            "date": "2026-04-29",
            "count": 2
          },
          {
            "date": "2026-04-30",
            "count": 3
          },
          {
            "date": "2026-05-06",
            "count": 2
          },
          {
            "date": "2026-05-07",
            "count": 1
          },
          {
            "date": "2026-05-11",
            "count": 1
          },
          {
            "date": "2026-05-12",
            "count": 1
          },
          {
            "date": "2026-05-20",
            "count": 1
          },
          {
            "date": "2026-05-21",
            "count": 1
          },
          {
            "date": "2026-06-23",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 350,
        "total_forks": 359
      },
      "star_history": null,
      "open_issues_and_prs": 73
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [
        "doc/Makefile"
      ],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "canopen/py.typed"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 40222,
      "source_files_sampled": 49,
      "oversized_source_files": 0,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "doc/requirements.txt",
        "pyproject.toml",
        "requirements-dev.txt",
        "setup.py"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 4,
        "malicious_count": 0,
        "assessed_package": "pypi:canopen@2.4.1",
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "pypi"
      ],
      "dependencies": [
        {
          "name": "python-can",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">= 3.0.0"
        }
      ],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "python-can",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "canmatrix",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "furo",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "mypy",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-cov",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "setuptools",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "setuptools-scm",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "sphinx",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "sphinx-autodoc-typehints",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 10,
        "direct_count": 1,
        "indirect_count": 9
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 20,
        "merged_prs": 230,
        "open_issues": 53,
        "closed_ratio": 0.84,
        "closed_issues": 278,
        "closed_unmerged_prs": 56
      },
      "bus_factor": 2,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "christiansandberg",
          "commits": 317,
          "avatar_url": "https://avatars.githubusercontent.com/u/3647996?v=4"
        },
        {
          "type": "User",
          "login": "acolomb",
          "commits": 113,
          "avatar_url": "https://avatars.githubusercontent.com/u/6996887?v=4"
        },
        {
          "type": "User",
          "login": "af-silva",
          "commits": 38,
          "avatar_url": "https://avatars.githubusercontent.com/u/11874980?v=4"
        },
        {
          "type": "User",
          "login": "erlend-aasland",
          "commits": 31,
          "avatar_url": "https://avatars.githubusercontent.com/u/13780613?v=4"
        },
        {
          "type": "User",
          "login": "fredrikhoyer",
          "commits": 29,
          "avatar_url": "https://avatars.githubusercontent.com/u/35274532?v=4"
        },
        {
          "type": "User",
          "login": "sveinse",
          "commits": 25,
          "avatar_url": "https://avatars.githubusercontent.com/u/13770135?v=4"
        },
        {
          "type": "User",
          "login": "friederschueler",
          "commits": 8,
          "avatar_url": "https://avatars.githubusercontent.com/u/4674229?v=4"
        },
        {
          "type": "User",
          "login": "bizfsc",
          "commits": 7,
          "avatar_url": "https://avatars.githubusercontent.com/u/202658520?v=4"
        },
        {
          "type": "User",
          "login": "tuwuhs",
          "commits": 5,
          "avatar_url": "https://avatars.githubusercontent.com/u/2083418?v=4"
        },
        {
          "type": "User",
          "login": "luminize",
          "commits": 5,
          "avatar_url": "https://avatars.githubusercontent.com/u/2206842?v=4"
        }
      ],
      "contributors_sampled": 57,
      "top_contributor_share": 0.488
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "pr-linters.yaml",
        "pythonpackage.yml",
        "pythonpublish.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": null,
            "reason": "internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "23 out of 23 merged PRs checked by a CI test -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 7,
            "reason": "Found 19/25 approved changesets -- score normalized to 7",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 10,
            "reason": "project has 11 contributing companies or organizations",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 9,
            "reason": "1 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "14b3336bc973023e82325426adf6d71046ebcf55",
        "ran_at": "2026-07-26T21:17:15Z",
        "aggregate_score": 5.5,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-06-28T15:28:58Z",
      "oldest_open_prs": [
        {
          "number": 267,
          "created_at": "2021-09-06T23:22:48Z",
          "last_comment_at": "2024-06-04T21:02:54Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 298,
          "created_at": "2022-03-08T07:29:23Z",
          "last_comment_at": "2026-05-04T09:33:30Z",
          "last_comment_author": "bizfsc"
        },
        {
          "number": 343,
          "created_at": "2023-01-15T21:18:27Z",
          "last_comment_at": "2023-03-15T21:25:47Z",
          "last_comment_author": "samsamfire"
        },
        {
          "number": 352,
          "created_at": "2023-03-15T21:22:02Z",
          "last_comment_at": "2026-05-06T08:44:45Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 359,
          "created_at": "2023-03-27T13:58:16Z",
          "last_comment_at": "2026-06-10T21:43:55Z",
          "last_comment_author": "sveinse"
        },
        {
          "number": 386,
          "created_at": "2023-07-07T21:08:14Z",
          "last_comment_at": "2023-08-02T13:00:22Z",
          "last_comment_author": "Obsnold"
        },
        {
          "number": 419,
          "created_at": "2024-04-05T13:09:41Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 454,
          "created_at": "2024-06-04T13:30:49Z",
          "last_comment_at": "2026-04-16T21:28:01Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 524,
          "created_at": "2024-07-29T20:47:57Z",
          "last_comment_at": "2024-08-12T19:53:05Z",
          "last_comment_author": "codecov-commenter"
        },
        {
          "number": 547,
          "created_at": "2024-11-07T20:55:02Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 559,
          "created_at": "2025-02-18T08:06:00Z",
          "last_comment_at": "2026-05-22T11:59:41Z",
          "last_comment_author": "codecov"
        },
        {
          "number": 603,
          "created_at": "2025-07-21T09:29:33Z",
          "last_comment_at": "2025-07-21T09:41:08Z",
          "last_comment_author": "codecov"
        },
        {
          "number": 614,
          "created_at": "2025-09-11T16:58:48Z",
          "last_comment_at": "2026-03-24T21:02:15Z",
          "last_comment_author": "zaporozhets"
        },
        {
          "number": 651,
          "created_at": "2026-04-29T19:01:25Z",
          "last_comment_at": "2026-05-12T07:43:02Z",
          "last_comment_author": "bizfsc"
        },
        {
          "number": 654,
          "created_at": "2026-05-04T08:44:40Z",
          "last_comment_at": "2026-06-27T21:38:16Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 668,
          "created_at": "2026-05-26T08:32:51Z",
          "last_comment_at": "2026-05-26T08:34:28Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 669,
          "created_at": "2026-05-26T14:48:30Z",
          "last_comment_at": "2026-06-28T21:24:55Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 673,
          "created_at": "2026-06-04T21:09:21Z",
          "last_comment_at": "2026-06-04T21:10:10Z",
          "last_comment_author": "codecov"
        },
        {
          "number": 674,
          "created_at": "2026-06-24T14:27:21Z",
          "last_comment_at": "2026-06-28T16:04:51Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 675,
          "created_at": "2026-06-27T22:05:43Z",
          "last_comment_at": "2026-06-27T22:13:59Z",
          "last_comment_author": "acolomb"
        }
      ],
      "last_merged_pr_at": "2026-06-28T15:28:19Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 47,
          "created_at": "2017-10-29T12:33:07Z",
          "last_comment_at": "2021-10-12T02:30:58Z",
          "last_comment_author": "sveinse"
        },
        {
          "number": 143,
          "created_at": "2019-02-25T19:05:14Z",
          "last_comment_at": "2020-06-04T14:19:44Z",
          "last_comment_author": "arne123"
        },
        {
          "number": 145,
          "created_at": "2019-03-11T12:17:17Z",
          "last_comment_at": "2019-03-14T09:10:11Z",
          "last_comment_author": "grw1983"
        },
        {
          "number": 179,
          "created_at": "2020-03-05T09:54:02Z",
          "last_comment_at": "2025-01-25T02:27:38Z",
          "last_comment_author": "ZeroErrControl"
        },
        {
          "number": 203,
          "created_at": "2020-10-13T12:14:05Z",
          "last_comment_at": "2020-10-20T07:15:45Z",
          "last_comment_author": "illuusio"
        },
        {
          "number": 261,
          "created_at": "2021-08-12T12:16:12Z",
          "last_comment_at": "2024-06-14T08:01:47Z",
          "last_comment_author": "friederschueler"
        },
        {
          "number": 272,
          "created_at": "2021-10-12T02:29:21Z",
          "last_comment_at": "2026-06-05T21:03:07Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 286,
          "created_at": "2021-11-29T13:51:47Z",
          "last_comment_at": "2022-02-18T20:03:56Z",
          "last_comment_author": "af-silva"
        },
        {
          "number": 321,
          "created_at": "2022-06-29T14:51:48Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 325,
          "created_at": "2022-08-12T06:36:59Z",
          "last_comment_at": "2022-08-15T05:13:54Z",
          "last_comment_author": "gardenvariety42"
        },
        {
          "number": 329,
          "created_at": "2022-09-27T11:58:17Z",
          "last_comment_at": "2022-09-29T08:48:37Z",
          "last_comment_author": "TudorMorosanu"
        },
        {
          "number": 358,
          "created_at": "2023-03-26T22:54:44Z",
          "last_comment_at": "2025-06-22T20:08:15Z",
          "last_comment_author": "acolomb"
        },
        {
          "number": 372,
          "created_at": "2023-04-12T16:32:52Z",
          "last_comment_at": "2023-04-14T17:36:28Z",
          "last_comment_author": "superlogan72"
        },
        {
          "number": 374,
          "created_at": "2023-04-21T09:52:42Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 377,
          "created_at": "2023-05-12T09:51:33Z",
          "last_comment_at": "2024-06-13T08:26:20Z",
          "last_comment_author": "friederschueler"
        },
        {
          "number": 383,
          "created_at": "2023-06-23T14:14:31Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 385,
          "created_at": "2023-07-07T20:56:52Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 391,
          "created_at": "2023-08-19T03:57:31Z",
          "last_comment_at": "2024-06-13T09:30:42Z",
          "last_comment_author": "friederschueler"
        },
        {
          "number": 392,
          "created_at": "2023-08-28T16:07:51Z",
          "last_comment_at": "2023-09-13T14:13:48Z",
          "last_comment_author": "sam-tj"
        },
        {
          "number": 396,
          "created_at": "2023-09-05T10:53:25Z",
          "last_comment_at": "2024-01-07T12:30:59Z",
          "last_comment_author": "acolomb"
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/canopen-python/canopen",
    "host": "github.com",
    "name": "canopen",
    "owner": "canopen-python"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 69,
      "inputs": {
        "security": 64,
        "vitality": 63,
        "community": 72,
        "governance": 66,
        "engineering": 81
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "moderate",
        "name": "Vitality",
        "value": 63,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 64,
            "inputs": {
              "commits_last_year": 43,
              "human_commit_share": 1,
              "days_since_last_push": 28,
              "active_weeks_last_year": 15
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 28 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 28
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "15/52 weeks with commits",
                "points": 10.4,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 15
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "43 commits in the last year",
                "points": 14.8,
                "status": "partial",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 43
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "moderate",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 62,
            "inputs": {
              "releases_count": 22,
              "latest_release_tag": "v2.4.1",
              "releases_from_tags": false,
              "days_since_latest_release": 355,
              "mean_days_between_releases": 237
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "22 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 22
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 355 days ago",
                "points": 16.2,
                "status": "partial",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 355
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~237 days",
                "points": 12.6,
                "status": "partial",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 237
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "maintained",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": null,
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": 28,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "last human commit 28 days ago",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_maintained",
                    "params": {
                      "days": 28
                    }
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "good",
        "name": "Community & Adoption",
        "value": 72,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "good",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 74,
            "inputs": {
              "forks": 359,
              "stars": 562,
              "watchers": 26,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "562 stars",
                "points": 44.6,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 562
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "359 forks",
                "points": 21.3,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 359
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "26 watchers",
                "points": 7.8,
                "status": "partial",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 26
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "excellent",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 99,
            "inputs": {
              "packages": [
                "canopen"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 857519
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "857,519 downloads/month across pypi",
                "points": 79.1,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 857519,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 66,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "moderate",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 60,
            "inputs": {
              "bus_factor": 2,
              "contributors_sampled": 57,
              "top_contributor_share": 0.488
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "2 contributor(s) cover half of all commits",
                "points": 25.2,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 49% of commits",
                "points": 11.5,
                "status": "partial",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 49
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "57 contributors",
                "points": 13.5,
                "status": "met",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 57
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 11 contributing companies or organizations",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 81,
            "inputs": {
              "merged_prs": 230,
              "open_issues": 53,
              "closed_issues": 278,
              "issue_closed_ratio": 0.84,
              "closed_unmerged_prs": 56
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "84% of issues closed",
                "points": 39.3,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 84
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "230/286 decided PRs merged",
                "points": 30.8,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 230,
                      "decided": 286
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 19/25 approved changesets -- score normalized to 7",
                "points": 10.5,
                "status": "partial",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 38,
            "inputs": {
              "followers": 2,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "canopen-python",
              "public_repos": 1,
              "account_age_days": 517
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "organization-owned",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "owner_organization",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "2 followers of canopen-python",
                "points": 3.4,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 2,
                      "login": "canopen-python"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "1 public repos, account ~1 yr old",
                "points": 5,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 1
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 1
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 91,
            "inputs": {
              "packages": [
                "canopen"
              ],
              "ecosystems": "pypi",
              "any_deprecated": false,
              "min_days_since_publish": 355
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on pypi",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 355 days ago",
                "points": 26,
                "status": "partial",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 355
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "42 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 42
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "good",
        "name": "Engineering Quality",
        "value": 81,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 68,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "23 out of 23 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "topics": [
                "can",
                "canopen",
                "python"
              ],
              "has_wiki": true,
              "homepage": "http://canopen.readthedocs.io/",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "http://canopen.readthedocs.io/",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "3 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "moderate",
        "name": "Security",
        "value": 64,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Branch-Protection, Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "branch_protection",
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 55,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "scorecard_aggregate": 5.5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "23 out of 23 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 19/25 approved changesets -- score normalized to 7",
                "points": 5.2,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 11 contributing companies or organizations",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "26 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "1 existing vulnerabilities detected",
                "points": 6.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the pypi:canopen@2.4.1 runtime dependency closure — what installing the published package pulls in — 4 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "pypi:canopen@2.4.1",
                  "assessed": 4
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 0,
              "affected_packages": 0,
              "assessed_packages": 4,
              "unassessed_packages": 0,
              "affected_by_severity": "none",
              "direct_affected_packages": 0
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "no direct dependency carries a known advisory",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "no_direct_advisories",
                    "params": {}
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "no indirect dependency carries a known advisory",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "no_indirect_advisories",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "no advisory carries a publication date",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_no_publication_date",
                    "params": {}
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 4,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 10
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 51,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.91,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "91 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 91,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 51,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [
                "doc/Makefile"
              ],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [
                "canopen/py.typed"
              ],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "doc/Makefile",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "doc/Makefile"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "canopen/py.typed",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "canopen/py.typed"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "good",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 82,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 40222,
              "source_files_sampled": 49,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python with type-check config (canopen/py.typed)",
                "points": 27,
                "status": "partial",
                "details": [
                  {
                    "code": "typecheck_config_language",
                    "params": {
                      "files": "canopen/py.typed",
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/49 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 49,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "at_risk",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": false,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-26T21:17:41.882771Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/c/canopen-python/canopen.svg",
  "full_name": "canopen-python/canopen",
  "license_state": "standard",
  "license_spdx": "MIT"
}

Оцінки — це сигнали, а не гарантії. Вони відображають публічно видимі практики на GitHub — це не аудит коду й не гарантія безпеки.

Відсутні дані виключаються, а ваги перенормовуються — нуль за відсутність ніколи не ставиться. Методологія версіонована й відкрита: метрики v1.13.0, схема v0.27.0 — повна методологія · вікі метрик.

Як окремий результат виглядає на тлі всього реєстру: сукупна статистикаPyPI.