公开记录
软件健康报告模式 0.27.0 · 指标 1.13.0 · 2026-07-31 09:06 UTC

Djelibeybi / lifx-async

A modern, type-safe, async Python library for controlling LIFX lights

PythonUPL-1.0★ 5 星标⑂ 1 复刻始于 2025年11月在 GitHub 上查看 ↗

Djelibeybi/lifx-async 的健康指数为 100 分中的 71 分,处于「良好」区间。 其得分最高的类别是Engineering Quality(92/100),最低的是Community & Adoption(36/100)。 最近一次更新在今天。 近期的大部分工作由 1 位贡献者完成。

71
总分 / 100
良好

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 49(有风险)的上限。AI 就绪度不计入总体分。

71
优秀85-100堪称典范;基本满足所有检验标准
良好70-84健康;仅有轻微不足
中等50-69可接受,但存在明显不足;建议进行审查
存在风险30-49存在重大薄弱环节;采用时应保持审慎
危急1-29问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

所有权

Avi Miller个人账户
187 关注者46 个公开仓库始于 2009年7月

该仓库由个人账户拥有。相较于组织支持的项目,单一所有者项目的延续性风险更高。

软件包生态系统

注册表软件包版本月下载量版本数最近发布
PyPIlifx-async6.1.13,963750 天前

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

90优秀 · 占总体的 22%
评分方式
36/36推送新近度 — 最近一次推送于 0 天前
25.6/36提交节奏 — 52 周中有 37 周有提交
18/18提交量 — 最近一年 499 次提交
10/10OpenSSF Scorecard:Maintained — 30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
所用输入
commits_last_year499
human_commit_share0.89
days_since_last_push0
active_weeks_last_year37

发布纪律

90优秀
评分方式
27/27有发布版本 — 已发布 75 个发布版本
36/36发布时效 — 最近一次发布版本于 0 天前
27/27发布节奏 — 约每 5.3 天发布一次
0/10OpenSSF Scorecard:Signed-Releases — Project has not signed or included provenance with any releases.
所用输入
releases_count75
latest_release_tagv6.1.1
releases_from_tags
days_since_latest_release0
mean_days_between_releases5.3

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

36存在风险 · 占总体的 18%
评分方式
9.8/60星标 — 5 个星标
0/25复刻 — 1 个复刻
0/15关注者 — 1 位关注者
所用输入
forks1
stars5
watchers1
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

50中等
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(UPL-1.0)
0/18CONTRIBUTING 指南
0/13.5行为准则
0/7.2议题模板
0/6.3PR 模板
所用输入
has_readme
has_license
has_contributing
has_issue_template
has_code_of_conduct
has_pull_request_template
评分方式
48/80月度下载量 — pypi 合计每月 3,963 次下载
0/20注册表被依赖数 — 该生态系统不报告此项
所用输入
packageslifx-async
dependents
ecosystemspypi
total_downloads
monthly_downloads3,963
已排除计分(无数据或不适用):注册表被依赖数。 其余权重已重新归一化。

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

56中等 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0/22.5提交分布 — 头号贡献者编写了 100% 的提交
1.4/13.5贡献者广度 — 1 位贡献者
0/10OpenSSF Scorecard:Contributors — project has 0 contributing companies or organizations -- score normalized to 0
所用输入
bus_factor1
contributors_sampled1
top_contributor_share1
评分方式
31.2/46.8议题解决 — 67% 的议题已关闭
37.2/38.3PR 接受 — 已裁定的 PR 中 177/182 已合并
0/15OpenSSF Scorecard:Code-Review — Found 0/9 approved changesets -- score normalized to 0
所用输入
merged_prs177
open_issues1
closed_issues2
issue_closed_ratio0.667
closed_unmerged_prs5
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
16.4/25所有者影响力 — Djelibeybi 有 187 位关注者
24.2/25既往记录 — 46 个公开仓库,账户约 17 年
所用输入
followers187
owner_typeUser
is_verified
owner_loginDjelibeybi
public_repos46
account_age_days6,230
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — pypi 上有 1 个软件包
35/35发布时效 — 最近一次发布于 0 天前
20/20版本历史 — 75 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packageslifx-async
ecosystemspypi
any_deprecated
min_days_since_publish0

工程质量

基础的工程与文档实践是否到位?

92优秀 · 占总体的 20%

工程实践

94优秀
评分方式
24/24CI 工作流 — 3 个工作流
24/24存在测试
16/16Linter 配置
9.6/9.6Pre-commit 钩子
0/6.4.editorconfig
20/20OpenSSF Scorecard:CI-Tests — 5 out of 5 merged PRs checked by a CI test -- score normalized to 10
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config

文档

90优秀
评分方式
30/30README
25/25文档目录
15/15文档 / 主页站点 — https://djelibeybi.github.io/lifx-async/
10/10仓库描述
10/10主题标签 — 7 个主题标签
0/10Wiki
所用输入
topicsasyncio, asyncio-python, lifx, lifx-bulbs, lifx-lan-protocol, lifx-lights, llms-txt
has_wiki
homepagehttps://djelibeybi.github.io/lifx-async/
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

78良好 · 占总体的 16%

安全态势

72良好
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
3/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 5 out of 5 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
0/7.5Code-Review — Found 0/9 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
7.5/7.5Maintained — 30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
5/5Pinned-Dependencies — all dependencies are pinned
5/5SAST — SAST tool is run on all commits
5/5Security-Policy — security policy file detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
7.5/7.5Token-Permissions — GitHub workflow tokens follow principle of least privilege
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate7.2
评分方式
35/35直接依赖不含已知公告 — 没有直接依赖携带已知公告
0/25间接依赖不含已知公告 — 在此范围内,传递依赖集合无法与开发和测试依赖区分
0/40没有长期未处理的公告 — 没有公告带有发布日期
所用输入
sourceosv
advisories0
affected_packages0
assessed_packages67
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
已排除计分(无数据或不适用):间接依赖不含已知公告, 没有长期未处理的公告。 其余权重已重新归一化。 已将 67 个已解析依赖与 OSV 比对。 该仓库未发布任何索引可解析的软件包,因此改为评估仓库依赖图。该图将开发与测试版本固定同交付的依赖混在一起,因此仅对声明的运行时依赖计分;传递性发现仅作为背景信息列出,不计入评分。 未对可达性进行分析。

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?这是一枚独立的实验性徽章——权重为 0.0,因此单独呈现,不影响总体健康评分。

76良好 · 占总体的 0%
评分方式
45/45代理指令 — AGENTS.md, CLAUDE.md
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 89 次人类提交中有 78 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.876
agent_instruction_filesAGENTS.md, CLAUDE.md
agent_instruction_max_bytes22,347
评分方式
0/18一条命令的引导启动
22/22自动化测试
11/11Lint / 格式化配置
11/11静态类型检查 — src/lifx/py.typed
10/10可复现环境 — lockfile
10/10已体现的代理实践 — 最近 100 次提交中有 21 次由代理编写或署名代理
8/8自动化维护 — 最近 100 次提交中有 11 次为自动依赖更新
10/10OpenSSF Scorecard:Pinned-Dependencies — all dependencies are pinned
所用输入
has_nix
has_tests
lockfilesuv.lock
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configssrc/lifx/py.typed
agent_commit_share0.21
toolchain_manifests
dependency_bot_commit_share0.11
评分方式
27/45可类型检查的代码 — Python,已配置类型检查(src/lifx/py.typed)
54.1/55可控的文件大小 — 采样的 237 个源文件中有 4 个超过 60KB
所用输入
primary_languagePython
largest_source_bytes119,553
source_files_sampled237
oversized_source_files4

机器可读接口

40存在风险
评分方式
0/40API 模式(OpenAPI/GraphQL/proto)
0/20MCP 服务器
40/40可运行示例 — examples
所用输入
example_dirsexamples
has_mcp_signal
api_schema_files

关键数据

5GitHub 星标
1贡献者
499最近 12 个月提交数
0距最近推送天数
75发布版本数
1巴士系数(bus factor)
1开放议题
PyPI软件包生态系统数

数据采集警告

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • deps.dev does not index pypi:lifx-async@6.1.1; advisories assessed against the repository dependency graph instead

更多细节

OpenSSF Scorecard 7.2 / 10
7.2综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。Scorecard v5.5.0 · 2026-07-31 09:06 UTC

10Binary-Artifactsno binaries found in the repo
4Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests5 out of 5 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/9 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
10Dependency-Update-Toolupdate tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
10Pinned-Dependenciesall dependencies are pinned
10SASTSAST tool is run on all commits
10Security-Policysecurity policy file detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
10Token-PermissionsGitHub workflow tokens follow principle of least privilege
10Vulnerabilities0 existing vulnerabilities detected
全部依赖 67

来自 GitHub 依赖图的完整解析依赖集合:0 个直接依赖与 67 个间接(传递)软件包。仓库提交锁文件时,传递闭包才是完整的。

注册表软件包版本关系
PyPIannotated-doc0.0.4间接
PyPIannotated-types0.7.0间接
PyPIbackports-asyncio-runner1.2.0间接
PyPIbeautifulsoup44.15.0间接
PyPIclick8.4.2间接
PyPIcolorama0.4.6间接
PyPIcoverage7.15.0间接
PyPIdeepmerge2.1.0间接
PyPIexceptiongroup1.3.1间接
PyPIghp-import2.1.0间接
PyPIgriffelib2.1.0间接
PyPIhatchling1.30.1间接
PyPIiniconfig2.3.0间接
PyPIjinja23.1.6间接
PyPIlifx-async6.1.1间接
PyPIlifx-emulator-core3.7.0间接
PyPIllmstxt-standalone0.2.0间接
PyPImarkdown3.10.2间接
PyPImarkdown-it-py3.0.0间接
PyPImarkdownify1.2.3间接
PyPImarkupsafe3.0.3间接
PyPImdformat0.7.22间接
PyPImdformat-tables1.0.0间接
PyPImdurl0.1.2间接
PyPImergedeep1.3.4间接
PyPImkdocs1.6.1间接
PyPImkdocs-autorefs1.4.4间接
PyPImkdocs-get-deps0.2.2间接
PyPImkdocstrings1.0.4间接
PyPImkdocstrings-python2.0.5间接
PyPInodeenv1.10.0间接
PyPIpackaging26.2间接
PyPIpathspec1.1.1间接
PyPIplatformdirs4.10.0间接
PyPIpluggy1.6.0间接
PyPIprek0.4.8间接
PyPIpy-cpuinfo9.0.0间接
PyPIpydantic2.13.4间接
PyPIpydantic-core2.46.4间接
PyPIpygments2.20.0间接
PyPIpymdown-extensions11.0.1间接
PyPIpyright1.1.411间接
PyPIpytest9.1.1间接
PyPIpytest-asyncio1.4.0间接
PyPIpytest-benchmark5.2.3间接
PyPIpytest-cov7.1.0间接
PyPIpytest-retry1.7.0间接
PyPIpytest-sugar1.1.1间接
PyPIpytest-timeout2.4.0间接
PyPIpython-dateutil2.9.0.post0间接
PyPIpyyaml6.0.3间接
PyPIpyyaml-env-tag1.1间接
PyPIrich15.0.0间接
PyPIruamel-yaml0.19.1间接
PyPIruff0.15.20间接
PyPIshellingham1.5.4间接
PyPIsix1.17.0间接
PyPIsoupsieve2.8.4间接
PyPItermcolor3.3.0间接
PyPItomli2.4.1间接
PyPItrove-classifiers2026.6.1.19间接
PyPItyper0.26.8间接
PyPItyping-extensions4.16.0间接
PyPItyping-inspection0.4.2间接
PyPIwatchdog6.0.0间接
PyPIwcwidth0.8.2间接
PyPIzensical0.0.47间接
依赖安全公告 0

该仓库未发布可被索引解析的包,因此评估的是其自身的依赖图——共 67 个包,其中也包含从不交付的开发与测试版本固定:0 个存在已知公告,0 个为直接依赖。

没有已知公告影响已评估的依赖。

公告表示依赖图中记录的版本落入某条公告的受影响范围。可达性未经分析,且依赖图包含开发与测试的版本固定——某项发现可能只涉及工具链而非交付的软件。

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [
        "asyncio",
        "asyncio-python",
        "lifx",
        "lifx-bulbs",
        "lifx-lan-protocol",
        "lifx-lights",
        "llms-txt"
      ],
      "is_fork": false,
      "size_kb": 23839,
      "has_wiki": false,
      "homepage": "https://djelibeybi.github.io/lifx-async/",
      "languages": {
        "HTML": 44167,
        "Python": 3449461
      },
      "pushed_at": "2026-07-31T09:02:03Z",
      "created_at": "2025-11-03T21:11:30Z",
      "owner_type": "User",
      "updated_at": "2026-07-31T09:02:41Z",
      "description": "A modern, type-safe, async Python library for controlling LIFX lights",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "UPL-1.0",
      "default_branch": "main",
      "license_spdx_raw": "UPL-1.0",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": "https://omg.dje.li",
      "name": "Avi Miller",
      "type": "User",
      "login": "Djelibeybi",
      "company": null,
      "location": "Melbourne, Australia",
      "followers": 187,
      "avatar_url": "https://avatars.githubusercontent.com/u/103232?v=4",
      "created_at": "2009-07-09T10:53:07Z",
      "is_verified": null,
      "public_repos": 46,
      "account_age_days": 6230
    },
    "license": {
      "state": "standard",
      "spdx_id": "UPL-1.0",
      "raw_spdx": "UPL-1.0",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v6.1.1",
          "kind": "patch",
          "published_at": "2026-07-31T09:02:04Z"
        },
        {
          "tag": "v6.1.0",
          "kind": "minor",
          "published_at": "2026-07-31T04:40:19Z"
        },
        {
          "tag": "v6.0.1",
          "kind": "patch",
          "published_at": "2026-07-29T15:25:26Z"
        },
        {
          "tag": "v6.0.0",
          "kind": "major",
          "published_at": "2026-07-28T15:37:55Z"
        },
        {
          "tag": "v5.6.4",
          "kind": "patch",
          "published_at": "2026-07-26T12:23:55Z"
        },
        {
          "tag": "v5.6.3",
          "kind": "patch",
          "published_at": "2026-07-26T00:30:07Z"
        },
        {
          "tag": "v5.6.2",
          "kind": "patch",
          "published_at": "2026-07-25T20:55:13Z"
        },
        {
          "tag": "v5.6.1",
          "kind": "patch",
          "published_at": "2026-07-25T19:37:34Z"
        },
        {
          "tag": "v5.6.0",
          "kind": "minor",
          "published_at": "2026-07-18T01:18:16Z"
        },
        {
          "tag": "v5.5.1",
          "kind": "patch",
          "published_at": "2026-06-14T00:47:27Z"
        },
        {
          "tag": "v5.5.0",
          "kind": "minor",
          "published_at": "2026-06-13T17:59:47Z"
        },
        {
          "tag": "v5.4.9",
          "kind": "patch",
          "published_at": "2026-05-21T22:17:57Z"
        },
        {
          "tag": "v5.4.8",
          "kind": "patch",
          "published_at": "2026-04-17T11:33:21Z"
        },
        {
          "tag": "v5.4.7",
          "kind": "patch",
          "published_at": "2026-04-17T03:22:44Z"
        },
        {
          "tag": "v5.4.6",
          "kind": "patch",
          "published_at": "2026-03-22T13:08:28Z"
        },
        {
          "tag": "v5.4.5",
          "kind": "patch",
          "published_at": "2026-03-22T08:25:46Z"
        },
        {
          "tag": "v5.4.4",
          "kind": "patch",
          "published_at": "2026-03-21T19:40:00Z"
        },
        {
          "tag": "v5.4.3",
          "kind": "patch",
          "published_at": "2026-03-21T19:06:51Z"
        },
        {
          "tag": "v5.4.2",
          "kind": "patch",
          "published_at": "2026-03-21T07:05:12Z"
        },
        {
          "tag": "v5.4.1",
          "kind": "patch",
          "published_at": "2026-03-19T04:30:28Z"
        },
        {
          "tag": "v5.4.0",
          "kind": "minor",
          "published_at": "2026-03-18T06:32:40Z"
        },
        {
          "tag": "v5.3.0",
          "kind": "minor",
          "published_at": "2026-03-18T02:52:58Z"
        },
        {
          "tag": "v5.2.1",
          "kind": "patch",
          "published_at": "2026-03-18T01:59:53Z"
        },
        {
          "tag": "v5.2.0",
          "kind": "minor",
          "published_at": "2026-02-07T11:43:09Z"
        },
        {
          "tag": "v5.1.1",
          "kind": "patch",
          "published_at": "2026-02-06T04:15:18Z"
        },
        {
          "tag": "v5.1.0",
          "kind": "minor",
          "published_at": "2026-01-24T04:06:14Z"
        },
        {
          "tag": "v5.0.1",
          "kind": "patch",
          "published_at": "2026-01-14T09:20:46Z"
        },
        {
          "tag": "v5.0.0",
          "kind": "major",
          "published_at": "2026-01-12T06:11:04Z"
        },
        {
          "tag": "v4.9.0",
          "kind": "minor",
          "published_at": "2025-12-30T03:29:55Z"
        },
        {
          "tag": "v4.8.1",
          "kind": "patch",
          "published_at": "2025-12-24T22:16:47Z"
        },
        {
          "tag": "v4.8.0",
          "kind": "minor",
          "published_at": "2025-12-20T02:12:21Z"
        },
        {
          "tag": "v4.7.5",
          "kind": "patch",
          "published_at": "2025-12-16T12:28:50Z"
        },
        {
          "tag": "v4.7.4",
          "kind": "patch",
          "published_at": "2025-12-16T11:51:46Z"
        },
        {
          "tag": "v4.7.3",
          "kind": "patch",
          "published_at": "2025-12-16T06:49:49Z"
        },
        {
          "tag": "v4.7.2",
          "kind": "patch",
          "published_at": "2025-12-16T02:32:31Z"
        },
        {
          "tag": "v4.7.1",
          "kind": "patch",
          "published_at": "2025-12-13T10:54:04Z"
        },
        {
          "tag": "v4.7.0",
          "kind": "minor",
          "published_at": "2025-12-13T09:26:38Z"
        },
        {
          "tag": "v4.6.1",
          "kind": "patch",
          "published_at": "2025-12-12T08:23:38Z"
        },
        {
          "tag": "v4.6.0",
          "kind": "minor",
          "published_at": "2025-12-11T17:32:19Z"
        },
        {
          "tag": "v4.5.1",
          "kind": "patch",
          "published_at": "2025-12-11T14:37:14Z"
        },
        {
          "tag": "v4.5.0",
          "kind": "minor",
          "published_at": "2025-12-08T03:38:52Z"
        },
        {
          "tag": "v4.4.1",
          "kind": "patch",
          "published_at": "2025-12-03T18:19:53Z"
        },
        {
          "tag": "v4.4.0",
          "kind": "minor",
          "published_at": "2025-11-29T06:57:02Z"
        },
        {
          "tag": "v4.3.9",
          "kind": "patch",
          "published_at": "2025-11-27T04:46:19Z"
        },
        {
          "tag": "v4.3.8",
          "kind": "patch",
          "published_at": "2025-11-25T03:31:25Z"
        },
        {
          "tag": "v4.3.7",
          "kind": "patch",
          "published_at": "2025-11-25T01:24:51Z"
        },
        {
          "tag": "v4.3.6",
          "kind": "patch",
          "published_at": "2025-11-25T00:33:05Z"
        },
        {
          "tag": "v4.3.5",
          "kind": "patch",
          "published_at": "2025-11-22T17:52:03Z"
        },
        {
          "tag": "v4.3.4",
          "kind": "patch",
          "published_at": "2025-11-22T17:03:50Z"
        },
        {
          "tag": "v4.3.3",
          "kind": "patch",
          "published_at": "2025-11-22T15:28:05Z"
        },
        {
          "tag": "v4.3.2",
          "kind": "patch",
          "published_at": "2025-11-22T13:05:39Z"
        },
        {
          "tag": "v4.3.1",
          "kind": "patch",
          "published_at": "2025-11-22T11:06:05Z"
        },
        {
          "tag": "v4.3.0",
          "kind": "minor",
          "published_at": "2025-11-22T10:39:49Z"
        },
        {
          "tag": "v4.2.1",
          "kind": "patch",
          "published_at": "2025-11-21T02:34:29Z"
        },
        {
          "tag": "v4.2.0",
          "kind": "minor",
          "published_at": "2025-11-21T00:21:18Z"
        },
        {
          "tag": "v4.1.0",
          "kind": "minor",
          "published_at": "2025-11-20T11:49:03Z"
        },
        {
          "tag": "v4.0.2",
          "kind": "patch",
          "published_at": "2025-11-19T03:16:44Z"
        },
        {
          "tag": "v4.0.1",
          "kind": "patch",
          "published_at": "2025-11-18T18:37:32Z"
        },
        {
          "tag": "v4.0.0",
          "kind": "major",
          "published_at": "2025-11-18T11:52:42Z"
        },
        {
          "tag": "v3.1.0",
          "kind": "minor",
          "published_at": "2025-11-17T13:31:10Z"
        },
        {
          "tag": "v3.0.1",
          "kind": "patch",
          "published_at": "2025-11-17T03:48:14Z"
        },
        {
          "tag": "v3.0.0",
          "kind": "major",
          "published_at": "2025-11-16T23:55:04Z"
        },
        {
          "tag": "v2.2.2",
          "kind": "patch",
          "published_at": "2025-11-14T21:12:38Z"
        },
        {
          "tag": "v2.2.1",
          "kind": "patch",
          "published_at": "2025-11-14T18:55:39Z"
        },
        {
          "tag": "v2.2.0",
          "kind": "minor",
          "published_at": "2025-11-14T14:55:08Z"
        },
        {
          "tag": "v2.1.0",
          "kind": "minor",
          "published_at": "2025-11-14T05:02:39Z"
        },
        {
          "tag": "v2.0.0",
          "kind": "major",
          "published_at": "2025-11-14T03:04:49Z"
        },
        {
          "tag": "v1.3.1",
          "kind": "patch",
          "published_at": "2025-11-12T15:30:09Z"
        },
        {
          "tag": "v1.3.0",
          "kind": "minor",
          "published_at": "2025-11-10T14:57:22Z"
        },
        {
          "tag": "v1.2.1",
          "kind": "patch",
          "published_at": "2025-11-08T07:17:21Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2025-11-07T12:46:55Z"
        },
        {
          "tag": "v1.1.3",
          "kind": "patch",
          "published_at": "2025-11-06T09:41:34Z"
        },
        {
          "tag": "v1.1.2",
          "kind": "patch",
          "published_at": "2025-11-05T12:26:26Z"
        },
        {
          "tag": "v1.1.1",
          "kind": "patch",
          "published_at": "2025-11-05T12:06:39Z"
        },
        {
          "tag": "v1.0.0",
          "kind": "major",
          "published_at": "2025-11-04T04:01:23Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "e4f8c95f6500172276da2f45dc9b8a2e7e896ab0",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "6.1.1",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-31T09:01:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0251e08a21c1d6f20410e3166c7e3fbfe0f4dd0f",
          "body": "Nothing propagated transport death up to the layers that decide whether to\nreopen. _UdpProtocol.connection_lost() cleared the protocol's own transport\nreference, but UdpTransport._transport/._protocol stayed populated, so\nis_open kept returning True, open() early-returned \"already_open\", and\nsend() \n[…]\nket itself is invalid, and asyncio never\ncalls connection_lost for them.\n\nRequest timeouts deliberately signal nothing. Only genuine endpoint death\nresets state.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(network): recover from UDP endpoint death instead of hanging forever",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T08:57:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e335439b90a4ea7296098d2d93d3bc195d59cd3",
          "body": "HSBK.__init__ applied user-input validation to values the firmware sent.\nvalidate_kelvin() rejected kelvin 0, which is what a fully saturated zone\ncarries on the wire, so get_all_tile_colors() crashed on a real Tile:\n\n    ValueError: Kelvin must be 1500-9000, got 0\n\nMIN_KELVIN-MAX_KELVIN is the whit\n[…]\nffect.__post_init__ (no inbound rule\ncan fire), TileInfo.from_protocol (no validation) and Serial.from_protocol\n(padding rejection is deliberate DoS protection).\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(color): accept device-reported kelvin 0 on inbound reads",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T08:57:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8d9a666a8219040662eb61dea50530900938eaae",
          "body": "Add a [project.urls] table to pyproject.toml using the labels defined in\nthe PyPA well-known project URLs specification (Homepage, Documentation,\nSource, Changelog, Releasenotes, Issues) so PyPI renders them with the\ncorrect icons.\n\nCorrect several inaccurate claims in the feature lists:\n\n- Remove t\n[…]\ne\n  device classes documentation so it cannot drift again.\n\nAlso document features that were missing: mDNS discovery, the theme\nlibrary, and the animation layer.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs: add project URLs and correct stale feature claims",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T05:07:13Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "aa240a2ae43afb2488a1ac6944ebb88ba812a0f9",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "6.1.0",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-31T04:40:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d61f6e39071474baca2ca6bd6582295f49925534",
          "body": "- refresh_state() re-fetches the volatile state (label, power, colour, opt-in\n  readings, zones and tiles), not \"everything\": the semi-static fields stay as\n  initialisation left them\n- packets 401 and 402 are part of the published LAN protocol and are merely\n  absent from protocol.yml, so they are \n[…]\n clears its field, and that an ambient\n  reading taken while the light is on is stored as -1.0\n\nCo-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs: correct refresh_state and ambient light sensor descriptions",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b85ab4a8e3c53769626eeb844ba5049d5c0c58e3",
          "body": "The integration imports these from their defining modules today. Export them\nfrom the package root so it can import them from lifx directly:\n\n- STATE_REFRESH_DEBOUNCE_MS and INVALID_AMBIENT_LIGHT_RESPONSE (lifx.const)\n- CollectionInfo and DeviceCapabilities (lifx.devices.base)\n- HevConfig and HevCyc\n[…]\n\n\nDeviceCapabilities is also exported from lifx.devices, alongside the other base\ndataclasses.\n\nCo-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat: export symbols used by the Home Assistant integration",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "118b6331b341b6e0d9926ab2975c0b17bfc4ad4e",
          "body": "Addresses the max-effort review of this branch:\n\n- set_group() wrote the new group UUID and label into state.location, leaving\n  state.group stale for the lifetime of the device\n- Device.refresh_state() stamped last_updated without re-reading anything, so\n  is_fresh() reported pre-change power and l\n[…]\non_requests() hold the batch shared\n  by Device and Light instead of two near-identical copies\n\nCo-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): harden state fetches and correct set_group target",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c6ad2c47ccfd2b7b816dd0ba58186af913ad611f",
          "body": "_initialize_state() schedules every request up front, so a failure\npart-way through awaiting them leaves the rest pending and relies on\n_discard_pending() to cancel and consume them. Nothing exercised that\ncancel: the existing failure test lets every other request complete\nimmediately, so each task was already done.\n\nFail one request while another is still in flight and assert the\npending one is cancelled, taking patch coverage of the branch to 100%.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test(devices): cover cancellation of in-flight state requests",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4aff9c633b1cf621789650adb75a4954b1fd1e80",
          "body": "Light.__init__ was an untyped *args/**kwargs passthrough that only\ncalled super(), which erased the constructor signature for Light and\nevery subclass: no call site could be type checked, including typos in\nthe new fetch flags. Deleting it inherits Device.__init__ as written.\n\nfetch_wifi_info and fe\n[…]\ne.py and\nlight.py. Unlike the deferred lifx imports around them, these are not\ncircular-import workarounds - time is already imported at the top of\nboth modules.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(devices): restore Light constructor typing, tidy imports",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2df05cb04e2aa6c2930ef9f7b9995e5deeec7300",
          "body": "Device.refresh_state() claimed to fetch label and power, which it has\nnever done on an already-initialised device: it re-queries only the\nopt-in WiFi reading and stamps last_updated. Describe both paths - full\npopulation via _initialize_state() when there is no state yet, cached\nsemi-static fields l\n[…]\nso documented a RuntimeError for uninitialised\nstate that it cannot raise, because the uninitialised path initialises\ninstead. Drop it from all seven docstrings.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(devices): correct refresh_state docstrings",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a7425b1e0174f7bfc677cb349c052b6d5a99a443",
          "body": "Discovery functions take no fetch_wifi_info or fetch_ambient_light\nargument, so discovered devices start with both flags off. The property\nhas to be set before `async with device`, because that is what runs\nstate initialisation - setting it inside the body silently misses the\nfirst fetch and leaves the field None until a full refresh.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs: explain opt-in state fields on discovered devices",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "23e27f7c8ba0df9a74630176da17b9a5f000e9bd",
          "body": "…ties\n\nAddress the PR #184 review and extend the same pattern to the ambient\nlight sensor.\n\nfetch_wifi_info and fetch_ambient_light are now settable properties on\nDevice as well as constructor arguments, so a caller starts or stops\ncollecting either reading by toggling the flag rather than passing a\n[…]\nlso re-fetches every zone or tile.\n- tests cover the wifi_info and ambient_light keys in as_dict, and the\n  already-initialized branch of Device.refresh_state().\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat(devices): add ambient light to state and make fetch flags proper…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "aab64f7234f58a17fcc7ab74409e8818c59354fc",
          "body": "`refresh_state()` now takes an optional `fetch_wifi_info` argument on every\ndevice class, so an existing device can pick up a WiFi signal reading (or skip\none) without being reconstructed. None keeps the instance setting from the\nconstructor.\n\nWhen state has not been initialised yet, `refresh_state(\n[…]\nnce default; an override that\nenables the query then issues the single extra request it needs.\n\nCo-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat(devices): allow refresh_state to override fetch_wifi_info",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "3ddf7f03956c77cf9e0452baf2f5d95f6e448b77",
          "body": "Add a `wifi_info` field to `DeviceState` so WiFi signal strength is part of\nthe device state snapshot rather than requiring a separate `get_wifi_info()`\ncall.\n\nQuerying the device for the signal is opt-in via a new `fetch_wifi_info`\nparameter on `Device` (also plumbed through `from_ip()`, `connect()\n[…]\nre now optional, and\n`WifiInfo` gains an `as_dict` property consumed by `DeviceState.as_dict`.\n\nCo-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat(devices): add wifi_info to DeviceState",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-31T04:36:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "fbc7096d257e6c0fd805ed392a1a4913dd065b88",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "6.0.1",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-29T15:25:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6377cc2ad3e61064902b9411e9ecc23e91c233f2",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix: switch all examples to use Device.connect() instead of from_ip()",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "be2897610c6539e8f0feb5642e6ecc0d6453487d",
          "body": "SKY is not available on every matrix product: it ships with host firmware\n4.x, so matrix devices on earlier firmware reject or ignore it. It is\nconfirmed on Ceiling, Luna, Tube, Path and the E26 Candle, and the\ndiscontinued Tile is permanently excluded by its terminal 3.50 firmware.\nSending SKY to a\n[…]\nSKY set_effect() with LifxUnsupportedCommandError before any packet\nreaches the wire when the device lacks the matrix capability or runs\nfirmware older than 4.0.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(matrix): gate the SKY effect on capability and firmware",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a7db2bfe87b179b31d725dbff59248a8775d0f31",
          "body": "Both generators emitted valid but unformatted Python, so regenerating\nfrom an unchanged upstream spec still produced a ~950 line diff until\nruff format was run by hand. Run ruff format and ruff check --fix over\nthe output instead, mirroring the CI generated-files job.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "build(generators): format generated code with ruff",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "04405bebbd49fe6c69f3012c001d182ded8331fd",
          "body": "Products 265 and 266 are the US and international LIFX Ceiling 13\", the\nsame 13x13 inch form factor as products 176 and 177, so they share the\n8x8 layout with the uplight on zone 63. Without the layout they fell\nthrough to MatrixLight and lost component control entirely.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(products): add the Ceiling 13\" component layout",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f4cf62326183e010f98d4c55999145bb550c7c82",
          "body": "theme_paint.py paints a built-in or custom palette onto bulbs, multizone\nstrips and matrix devices in a single DeviceGroup.apply_theme() call, and\nreports the paint geometry of each target before applying it. It also\nexercises the DeviceGroup.apply_theme() dispatch fix end to end.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(examples): add a theme painting example",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2ee2f4cf4df053751633092bca7723d3f6e2309a",
          "body": "DeviceGroup.apply_theme() fanned out per device class: self.lights holds\nevery Light subclass, so multizone and matrix devices were painted twice\nconcurrently, racing a single random colour against their zone or tile\ngradient. CeilingLight missed the gradient entirely, because the\nmatrix_lights filter uses type() rather than isinstance().\n\nDispatch polymorphically over self.lights instead, so each device runs\nits own apply_theme() override exactly once.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(api): apply themes once per device",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-29T15:20:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bb67c949247deeadd19590709b3a2bc10d8fb541",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "6.0.0",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-28T15:37:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d73a5302661bde33a3e3d5cb65587a38073bd471",
          "body": "The MultiZone Control example read every zone with no counterpart write,\nso a reader still reached for set_extended_color_zones -- the call that\nbreaks on legacy firmware and past 82 zones.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(devices): show set_all_color_zones in the multizone example",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f712d681a1220427d4ca225ab18177c61982595f",
          "body": "brightness_uint8 rounded, so every wire brightness below 129 of 65535\ncame back as 0. Home Assistant reads a light that is on at brightness 0\nas off and writes that 0 back, switching the light off -- reachable\nwhenever an effect fades through near-zero.\n\nAlso pin the hue quantisation with exact expe\n[…]\nion used approx(abs=1), a tolerance as wide as the one-LSB bias the\nscale-then-round fix removed, so the old expression could come back\nwithout failing anything.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(color): report at least brightness 1 for a lit light",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4f561e2857d0a9cb3e31c566bcec3d42b8a9871c",
          "body": "_restore_zones still called set_extended_color_zones with every captured\nzone, so a strip longer than 82 zones raised ValueError. The broad\nhandler swallowed it, leaving the strip frozen on the effect's last frame\nwith only a warning logged.\n\nDelegating to set_all_color_zones picks the packet type from\ncapabilities and chunks past the extended limit, so the branch here goes\naway as well.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(effects): restore zones via set_all_color_zones",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "632bb82814cc7ff727051a67571bf3f9f8991c39",
          "body": "APPLY_ONLY tells the device to ignore the colours carried by the message\nand flush only what is already buffered. Forwarded to the final packet of\na split write, it discarded that chunk while every earlier chunk stayed\nbuffered, so a long strip came back half written with nothing raised.\n\nAlso state\n[…]\n only checked against a cached zone\ncount. The design spec deliberately never fetches one just to validate,\nbut the docstring read as an unconditional guarantee.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(multizone): reject APPLY_ONLY in set_all_color_zones",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "71d47e13b3cdef11e07f15ac93c49ec32e4bed89",
          "body": "LightState.as_dict called dataclasses.asdict, which bypassed\nDeviceState.as_dict entirely: light states lost has_variable_color_temp,\nkept None kelvin bounds instead of the 1500/9000 defaults, and leaked the\nfirmware build that DeviceState deliberately omits.\n\nEvery state now extends its parent and \n[…]\nndex. JSON object keys are always\nstrings, so the keys are stringified here rather than letting json.dumps\ncoerce them and break every lookup after a round trip.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): build light state dicts from DeviceState.as_dict",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c006899f8c904c3f18f97a5a196457bbbb7b78a8",
          "body": "as_dict became a property, but the discovery example still called it, so\nrunning it died with \"TypeError: 'dict' object is not callable\" before\nprinting a single device. CI never caught it: pyright only includes src\nand pytest only collects tests.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(examples): call HSBK.as_dict as a property",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9cc7258e54f607cf0deb278f40a51c6423efb297",
          "body": "The counterpart to get_all_color_zones: writes zone colors using whichever\npacket the device supports, so callers no longer need to pick between\nSetColorZones and SetExtendedColorZones, know the 82-colour cap, or sequence\nNO_APPLY/APPLY themselves.\n\n    colors = await light.get_all_color_zones()\n   \n[…]\n hardcoded\nto start at zone 0. apply_theme now calls the public method.\n\nDesign and rejected alternatives:\n.claude/specs/2026-07-28-set-all-color-zones-design.md\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat(multizone): add set_all_color_zones",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "dd5e8e97e671b578088fbad5d2cb3c6162632a4a",
          "body": "Design for the public multizone setter that mirrors get_all_color_zones,\ngeneralising the private _write_zone_colors added in 05fcfba to a windowed\nwrite with a caller-supplied apply mode.\n\nRecords why the getter unified trivially and the setter does not: the two\nwrite packets take different shapes \n[…]\n38 below firmware 2.77.\n\nLives in .claude/ rather than docs/ so it is not published to the docs site,\nand uses chore: so it stays out of the generated changelog.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore: spec set_all_color_zones",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8101d991cd8756a1b6b362ea910e16769549b888",
          "body": "apply_theme called set_extended_color_zones(0, colors) directly, with no\ncapability check and no chunking. That meant it raised\nLifxUnsupportedCommandError on firmware without extended multizone, and\nValueError (\"Too many colors\") on any strip with more than 82 zones, such as a\nchained LIFX Z run.\n\n\n[…]\nuct id and set\n_capabilities from the registry; the multizone fixture uses product 32\n(LIFX Z, extended) and the new fallback test uses product 31 (no extended).\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(multizone): make apply_theme work on legacy and long strips",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b4913394f681733f15dd18a853cd1ab1cb04144e",
          "body": "HSBK is a plain class, not a dataclass, so `dataclasses.asdict` left every\ncolor field as an HSBK object and its own `as_dict` was never consulted. No\ndevice state was JSON-serialisable as a result.\n\n`LightState.as_dict` now expands `color`, and the subclasses build on it via\n`super().as_dict` inste\n[…]\nxists there. They are replaced by test_state_serialisation.py,\nwhich asserts against real instances of all six state classes and checks each\nsurvives json.dumps.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): expand HSBK colors in every state's as_dict",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "626f688fab353c2218731583dae6ee73c1ba8c8c",
          "body": "BREAKING CHANGE: HSBK.as_dict is now a property. Callers using `color.as_dict()`\nmust drop the parentheses; calling it raises\n`TypeError: 'dict' object is not callable`.\n\nEvery other class exposing as_dict — TileInfo, DeviceCapabilities,\nFirmwareInfo, CollectionInfo, DeviceState, MatrixLightState and LifxStruct —\nalready exposes it as a property. HSBK was the sole method.\n\nLedFx is the only known consumer and does not use as_dict on any state class.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(color)!: make HSBK.as_dict a property",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5a96dc57d602ad0048086bea52dc10f4bf489ce4",
          "body": "`CeilingLightState.as_dict` returned `downlight_zones` as a raw `slice`, which\n`dataclasses.asdict` passes through untouched and which no serialiser can\nhandle. It is now expanded to `{\"start\": x, \"stop\": y, \"step\": z}`.\n\nThe layouts in quirks.py build `slice(0, 63)` and `slice(0, 127)`, so\n`slice.s\n[…]\ntionally, and the result compares unequal to a step-None original despite\nindexing identically.\n\nTwo existing tests asserted the old passthrough and are updated.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(ceiling): expand downlight_zones slice in as_dict()",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c8653b0f61ad255b5769dde0fb3477159059e903",
          "body": "`round(x)` with no ndigits already returns an int; only `round(x, n)` returns\na float. The wrappers in framebuffer.py and infrared.py were no-ops.\n\nThese were the last two occurrences in the tree; the ones in color.py and\naurora.py were removed alongside the hue quantisation fix.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor: drop redundant int() around round()",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "815f037fdee995f52b6ae06d8980ffb6768d6e43",
          "body": "Add pre-scaled views of the HSBK components for consumers that do not want\nthe internal 0.0-1.0 fractions:\n\n- `saturation_pct` / `brightness_pct` return unrounded percentages (0.0-100.0)\n- `brightness_uint8` returns an 8-bit value (0-255)\n\nHome Assistant wants `(hue, saturation_pct)` for a light's `\n[…]\nrate_frame()` and\n`generate_protocol_frame()`.\n\nThe redundant `int()` wrappers around `round()` in both files are dropped;\nbare `round()` already returns an int.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat(color): add scaled HSBK accessors and replace()",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T15:34:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "378e1e63d38995abacf657bd59305a84411f572b",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore: update copyright year to include 2026",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-28T09:03:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c5191102786437c28fb4bf171c76454bc6727e6c",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.6.4",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-26T12:23:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "55de0d247a5de2355104f28faaaa94053cedd2b3",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): address multizone review feedback",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T12:20:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "64d6dfcdeaf1fae46e8ad91c16762c15bce0ea1e",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test: cover Home Assistant migration fix paths",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T12:20:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5507c41b87bd27ab7f159666c28cb7ebf779b1ca",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix: address Home Assistant migration issues",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T12:20:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "80d567da19109119c151e24c89bad5d369bbeea1",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.6.3",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-26T00:30:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "83f3d256da5d179faf17ba97011b2659e07c7593",
          "body": "The v1.1 close-out planning docs recorded the idle-window fix under its\npre-rebase hash 3d16822, which no longer exists on main -- PR #176 landed as\ndca9e39. STATE.md, MILESTONES.md, the v1.1 milestone audit and the quick-task\nsummary now cite the real commit.\n\nThe quick-task summary also records the 9afe8df follow-up (PR #177): the\nreplacement docstring Note overstated the guarantee, and the two regression\ntests were hardened.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(planning): refresh the discovery idle-window commit refs",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fe8effcd3d714bcf80347f90d207d4acce34707d",
          "body": "The generated entry pulled one bullet per SUMMARY.md, which included deviation headings\nand a stray 'H2' rather than accomplishments. Replaced with six requirement-grouped\noutcomes carrying their measured figures, plus the closeout type, override count and\ndeferred items.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs: curate the v1.1 milestone entry",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f8be2bb2220ff2f55b3898cf8781c95df063b015",
          "body": "Archived to .planning/milestones/v1.1-REQUIREMENTS.md with all 13 requirements marked\ncomplete. /gsd-new-milestone writes a fresh one for the next milestone; history is\npreserved via git rm rather than a plain delete.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore: remove REQUIREMENTS.md for the v1.1 milestone",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "93b8e88078fac2bb3db46080a500e19c8753d098",
          "body": "Archives the v1.1 roadmap, requirements, audit and phase directories to\n.planning/milestones/, collapses ROADMAP.md to a milestone-grouped summary, evolves\nPROJECT.md (v1.1 moved to shipped, carried-forward candidates listed under Active, two\nnew Key Decisions recorded), records the v1.1 retrospecti\n[…]\n with cross-milestone trends,\nand notes the acknowledged deferred items in STATE.md.\n\nSafety checkpoint before REQUIREMENTS.md is removed for the next milestone.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore: archive v1.1 milestone files",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7337d0aee4bc1ec2804f5c7ae3c3dd4296dc2234",
          "body": "…e two audit items\n\nAdds the quick task's plan and summary, the STATE.md Quick Tasks row, and updates\nv1.1-MILESTONE-AUDIT.md: the api.discover() idle-window warning and phase 04's\nunreconciled VALIDATION.md are both resolved, so Nyquist coverage is now 4/4 compliant.\nThe missing 02-01 frontmatter and the dormant SEED-001 remain deferred.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(quick-260726-824): record the discovery idle-window fix and clos…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f9929c3d7dc4b6192871a97407edbeee0f5b6680",
          "body": "…one audit\n\n04-VALIDATION.md still carried the plan-time seed: status draft, every row pending,\nnyquist_compliant false. Nothing had reconciled it after execution, so the milestone\naudit read phase 4 as NOT-VALIDATED even though the coverage exists.\n\nAudited retroactively: all four Wave 0 artefacts \n[…]\ns, the substantive one being that api.discover() awaits create_device() inside the\ndiscovery loop, letting one dead-but-discovered device starve the idle window.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(phase-04): reconcile validation strategy and add the v1.1 milest…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:26:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9afe8df68260c6940173a765306441f2568c5fa7",
          "body": "Follow-up to dca9e39 from a max-effort review of PR #176.\n\nThe docstring claimed a consumer 'cannot expire discovery early', which is\nfalse in the default configuration: DEFAULT_REQUEST_TIMEOUT (16 s) outlasts\nDISCOVERY_TIMEOUT (15 s), so a single unreachable device still ends the sweep\n-- via the o\n[…]\nidle-reset invariant next to the reset (sends deliberately do\nnot reset it), and replaces _make_recording_send([]) with an explicit\n_make_ignoring_send() helper.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(discovery): correct the idle-window docs and harden its tests",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-26T00:19:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "79814c8e2ef45ca48dbc3316ab35de60b12fd7ab",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.6.2",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-25T20:55:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dca9e39e3dc1a1fc684b8638597ff8219d1dbf2c",
          "body": "…indow\n\nThe idle timer was reset only before a response was yielded, so every second\nthe consumer spent counted against it. api.discover() constructs a Device per\nyielded response, and those requests carry a 16s wall deadline against a ~4s\nidle window: one device that answered GetService and then we\n[…]\nthat answers afterwards (verified to fail\nwithout the fix -- the second device is stranded), and one asserting the\noverall deadline still bounds a slow consumer.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(discovery): stop consumer time from expiring the discovery idle w…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-25T20:51:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b635c5a28e18aff5a3952db3b3390dd2e9f197ba",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.6.1",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-25T19:37:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "786ad344909a1192d8cebc46de5062005c681510",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(quick-260726-42c): ship quick task — PR #175 [ci skip]",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-25T19:34:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bdb84f7739311291bf5f36b315211bf65e51b5a5",
          "body": "…it script\n\nget_mac_address() incremented the final octet of the serial for every\nversion_major == 3 firmware, so it returned a MAC no device had. A fleet sweep\nfound LIFX Tiles on firmware 3.50 whose real ARP MAC matched their serial\nexactly.\n\nThe offset now requires version_major == 3 and version_\n[…]\n removed\nmajor-only rule to the docs site and the context7 corpus, and AGENTS.md -- the\ntracked Codex mirror of CLAUDE.md -- still carried the superseded gotcha.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): narrow the MAC offset to firmware 3.70+ and add the aud…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-25T19:33:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "010e9d195b20594b6cc0d2dff03ad57aeef54afe",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/setup-python action to v7",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-25T19:13:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "681924522ac484678af6cdd4dbfb3bdd824ad0e3",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update astral-sh/setup-uv action to v9",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-25T19:08:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d52c856cf933e64d98393234be71995549f7c9c1",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore: commit and upload spike findings",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-25T16:15:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fd2104f88dc97ef7b406509a29c8fafec4f00609",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update pypa/gh-action-pypi-publish digest to ba38be9",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-22T22:13:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "71082bcf27929ac51a8991450519bc6e166cd429",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/checkout digest to 3d3c42e",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-22T20:44:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc34f64bca863adbf270442374d353da1cc4fef8",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/labeler action to v7",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-22T20:24:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f701a8fe64f8f3186019d0e94d5bd89a4b595bd8",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.6.0",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-07-18T01:18:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d99a501b2f896a596e45e71b57e1e06e1d3fa15c",
          "body": "Ack-gated animation flow control, retry/deadline hardening, discovery\nrebroadcast, reliability docs, and phase planning artifacts for the v1.1\nmilestone.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "feat: wire reliability improvements (v1.1)",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-07-18T01:14:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2b9d41c78499161d1dfb7f4dee037eb97c4f488c",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update astral-sh/setup-uv action to v8.3.2",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-11T22:31:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fbc03af01137e41e6d486ce90a34a4bd3b9e92d5",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/labeler digest to b8dd2d9",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-11T22:20:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eeb63a5862dc10adcd1979be7a47b81b8c07e2c8",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update lock file",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-06T23:46:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d70d0efedea5f63b369cb92d941df0ea25e4c4f3",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update astral-sh/setup-uv action to v8.3.0",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-07-06T23:42:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9b496df28a53ff0baa65a405ad7537b01510f3e2",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/setup-python digest to ece7cb0",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-06-26T12:12:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eae31b6300891e838111afe469210b8508e4b9fc",
          "body": null,
          "is_bot": true,
          "headline": "chore(deps): update actions/checkout action to v7",
          "author_name": "renovate[bot]",
          "author_login": "renovate[bot]",
          "committed_at": "2026-06-20T02:18:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cb30f424b717c9853105807866dea22ebad14b0e",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.5.1",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-06-14T00:47:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "08b7e8d9c23708b10aa2ebde27e7765a3f487dd1",
          "body": "_is_stored_state_valid() compared stored vs current hue/saturation as\nraw floats. Now that those expose unrounded decoded values, a color read\nback from the device has the same wire tuple but slightly different raw\nfloats, so stored-state validity could incorrectly fail after a protocol\nround-trip.\n\n[…]\nound-trip regression tests for both uplight and downlight.\n\nReported in code review of #160.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(ceiling): compare stored state at uint16 wire granularity",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-14T00:44:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "515dd91d018ee5481602ee0ad9b270e0fe1eef8f",
          "body": "average(), lerp_hsb() and lerp_oklab() rounded their results to integer\nhue and 2dp saturation/brightness. Those outputs feed the effects layer\n(~10 effects via lerp_oklab) and the theme canvas/generators (average),\nall of which serialise to uint16 — so the 2dp rounding coarsened smooth\ngradients an\n[…]\nserted hue was an int (an artefact\nof the old rounding) now assert a numeric value in range.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(color): stop rounding computed lerp/average outputs",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-14T00:44:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "80b0b2f3a0289b7c635461dd5ae3b53cb8ca8f19",
          "body": "The hue/saturation/brightness properties rounded on read (integer hue,\n2dp sat/bri) and from_protocol() rounded at construction, so downstream\nconsumers could never obtain the device's full-precision values.\n\nReturn the raw stored floats from the getters and decode from_protocol()\nwithout rounding. \n[…]\nhrough that equality.\n\nDevice-state tests that asserted exact decoded values now use approx.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(color): expose raw HSBK values instead of rounded ones",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-14T00:44:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f7e7c805bd7b90749505b9a69df512e8ce8a685d",
          "body": "Automatically generated by python-semantic-release",
          "is_bot": false,
          "headline": "5.5.0",
          "author_name": "semantic-release",
          "author_login": null,
          "committed_at": "2026-06-13T17:59:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6c5933d426fb8b8f98d5b1822656491434f813ac",
          "body": "CeilingLight.__aexit__ ran the threaded save guarded only by\n`except Exception`. asyncio.CancelledError is a BaseException, so a\ncancellation landing while the save was pending bypassed the handler and\nskipped `super().__aexit__()`, leaking the connection despite the\ndocstring's \"always cleaned up\" \n[…]\ncancellation still propagates. Regression\ntest added.\n\nAddresses review feedback on PR #159.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(devices): guarantee ceiling connection cleanup if save is cancelled",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "415006a5bde8988dedad775e64095cfc63c02e93",
          "body": "Serial.from_protocol() keeps only target[:6], so a spoofed StateService\nwith valid-looking serial bytes but non-zero padding (e.g. d073d5010203ffff)\nnormalised to a clean serial and could win the per-serial dedup race\nagainst the real device, poisoning discovery with the attacker's IP/port.\nThe 8-by\n[…]\nero; reject when they are not. Regression test added.\n\nAddresses review feedback on PR #159.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(discovery): reject non-zero target padding in the serial guard",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "6d6d9b0398a417063d658dae8d348f0f315f3b18",
          "body": "codecov measures branch coverage; two changed conditionals had only one\nside exercised:\n- mdns discover_lifx_services idle_expired=True (idle-timeout break)\n- connection request_stream discovery-serial refresh (both sides of\n  serial != self.serial)\n\nFull suite 2522 passed; no missing lines or partial branches on the diff.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test: cover remaining branch partials for 100% patch coverage",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "48c13ae10e0e7f250e3ce0b67d39f6f9c7395653",
          "body": "Adds targeted tests for the changed lines codecov/patch flagged:\n- IdleDeadline.expired convenience property\n- array-of-enums deserialisation tolerating unknown values (base.py)\n- CeilingLight atomic-write failure cleanup (temp unlink + warning)\n- request_stream DEBUG request/reply logging blocks (G\n[…]\n via a mocked IdleDeadline)\n\nFull suite 2519 passed; diff-cover vs origin/main reports 100%.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test: reach 100% patch coverage for the discovery-unification branch",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "010967797649dd96a2412c411399c43fc157ca4d",
          "body": "Phase fully verified: review-fix 6/6, security 11/11 closed, UAT 4/4\nincluding real-hardware validation. Marked complete manually — the\nfree-form ROADMAP (this phase reuses \"Phase 1\" alongside the shipped\nv1.0 Ceiling phase) caused phase.complete to mis-resolve and touch the\nwrong row, so it was reverted. v1.0 shipped record left untouched.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01): mark Unify-discovery-loops phase complete & verified",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "798c45b7a3b021dbfd3f1b7e5a25b485bef4c5ee",
          "body": "Test 4 (real-hardware discovery) confirmed passing by the user after the\nDeviceService fix (0d83deb). All Phase 1 UAT tests pass; status complete.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test(01): UAT complete — 4/4 pass, hardware regression confirmed fixed",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "dbff129c7084af7dfec1162bed05fb09db40dd47",
          "body": "11 threats, all closed: 9 plan-time (T-01-01..T-01-09) verified in-tree,\nplus 2 DoS threats found post-plan and closed with regression coverage —\nT-01-10 (CR-01, size-invalid datagram abort) and T-01-11 (unknown\nDeviceService enum: log amplification + crash surface). threats_open: 0.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(phase-1): add security threat verification",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "244f4149d27b85c523ee78d93527c579dca16bff",
          "body": "Test 4 (real-hardware validation) found ValueError on unknown\nDeviceService(5); fixed in 0d83deb. Re-test against hardware pending.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test(01): record hardware UAT regression + fix in 01-UAT",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0cb5dc638e74ffe7c76ac6d31df66b99e6e60165",
          "body": "Real LIFX devices advertise one StateService per service they support and\nreport service identifiers (e.g. 5) that the bundled protocol.yml does not\ndefine as a DeviceService enum member. The Phase 1 refactor retired the raw\nstruct parser (which discarded the service byte) for StateService.unpack(),\n[…]\n not the scripts.\n\nGates: ruff clean, pyright 0/0/0, full suite 2511 passed / 12 deselected.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(01): tolerate unknown DeviceService values from real hardware",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "db9b7cc9a8dcd54a6c5750bfd5f1ce6d941686bd",
          "body": "Tests 1-3 (discovery via unified path, find_by_label truthful port WR-04,\nreceive_many DeprecationWarning WR-05) verified against the in-process\nemulator and full suite (2508 passed). Test 4 blocked on real-hardware\nvalidation at user request.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test(01): UAT partial — 3/4 verified, hardware check pending",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "8e5f61bacdaf235d671e58b847e98294097d44dd",
          "body": "Review-fix pass resumed and completed; mark phase 1 ready for verifier.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "chore(01): resume bookkeeping — consume HANDOFF.json, update STATE",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "03aa5b6136e3220425831036a89b790d172f058a",
          "body": "All 6 in-scope findings (CR-01, WR-01..WR-05) resolved; 7 Info findings\nout of scope. Documents the resume reconciliation after the prior\nsession died mid-fix.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01): add code review fix report",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "21c2b35e19f4c331c784af50ec655f36e35cdd8c",
          "body": "WR-03: rewrite _discover_with_packet docstring as an async-generator\ncontract (Yields, first-wins dedup, snake_case payload keys) and replace\nthe broken `await ... + sync for` example with `async for`.\n\nWR-04: set DiscoveryResponse.port to the device source port (addr[1])\ninstead of the broadcast de\n[…]\ned to match v6.0.\n\nGates: ruff clean, pyright 0/0/0, full suite 2508 passed / 12 deselected.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(01): finish review-fix pass — WR-03/WR-04/WR-05",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ad35b4e906e0467522a43fdc6d0b2d1eace19fff",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "wip: phase 1 paused at review-fix (3/6 findings fixed)",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "74928f82e20e60ea7721c143a7750a5e3e30b47c",
          "body": "The mDNS loop only called deadline.mark_response() after a successful\nyield of a new serial, so duplicate re-announcements (normal mDNS\nbehaviour) never extended the idle window — the exact premature-expiry\nfailure mode D-04 fixed in the broadcast loop. Hoist mark_response() to\nrun on every valid LI\n[…]\ne honouring idle expiry).\n\nAdds a regression test asserting mark_response is called for both of two\nduplicate responses while dedup still yields a single record.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(01): WR-02 align mDNS idle-reset semantics with D-04",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "76b00efd5797b588254d40929651acd30e6a456d",
          "body": "The hoisted serial guard accepted b'\\x00' * 8 — the LIFX broadcast/\nall-devices target the discovery request itself uses, never a valid\ndevice serial. A spoofed response echoing a zero target produced a\nphantom DiscoveredDevice with serial 000000000000.\n\nReplace the unreachable all-0xff clause (0xff\n[…]\ntrips the\nmulticast-bit check) with an all-zeros check, so the guard now covers\nmulticast, all-0xff and all-zero targets. Adds a generator-level\nregression test.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(01): WR-01 reject all-zeros broadcast target in serial guard",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b33fde1464e73422cc260e649a75dfd77d79ffe8",
          "body": "UdpTransport.receive() raises LifxProtocolError for datagrams outside the\n36-1024 byte range, but the shared discovery loop only guarded the receive\ncall against LifxTimeoutError. A single hostile or malformed UDP datagram\nsent to the discovery socket terminated every discovery API with an\nuncaught \n[…]\nd and oversized datagrams through\nthe genuine UdpTransport.receive() size-validation path and assert the\nsubsequent valid StateService response is still yielded.\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "fix(01): CR-01 drop size-invalid datagrams instead of aborting discovery",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b9d7b8feda9c2601b102023ce85ac2705d7a2497",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01): add code review report",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e23cec0e646180ff7f5468ebcc657f222f8b994c",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01-05): complete discovery error test gap plan",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5c32b06eb5311d34eb04f1b312e5871c64685d2f",
          "body": "- Re-prove malformed StateService payload handling via the shared\n  _discover_with_packet path (D-10); no retired internal references remain\n- Add TestMalformedPayloadHandling: truncated payload yields zero devices\n  without propagating an exception through discover_devices\n- Add TestDiscoverWithPac\n[…]\ngenerator level (D-11)\n- Import _discover_with_packet and DevicePackets (used by new tests)\n- All existing validation and emulator dedup tests retained and green\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "test(01-05): close D-10/D-11 test gaps in discovery error suite",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "74aa4728b8fbb37de2443464624c3dfec9b008a0",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01-03): complete mDNS IdleDeadline adoption plan",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5a57443e6747a08a1c25aaf263d770b5fe124861",
          "body": "… discover_lifx_services\n\n- Replace inline idle/overall deadline arithmetic with IdleDeadline(timeout, idle_timeout)\n- Call deadline.mark_response() after each yielded record (was last_response_time = response_timestamp)\n- Replace bare except Exception around transport.receive() with typed clauses (\n[…]\neplace bare Exception(\"timeout\") side-effects with LifxTimeoutError\n- Add two new tests for LifxNetworkError (no-propagate) and unexpected error (re-raise) paths\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(01-03): adopt IdleDeadline and tighten exception handling in…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "690fd207a77d0712fbbe7320ebd4e83698fb411b",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01-02): complete thin-discover-devices-wrapper plan",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2babaa18174ee500b014a9f0103b49b990efa7df",
          "body": "…ce_state_service\n\n- Remove top-level 'import struct' (only used by _parse_device_state_service)\n- Delete _parse_device_state_service function entirely (D-05): StateService.unpack()\n  via the protocol layer is now the single source of truth for payload parsing\n- Replace discover_devices body (~200 l\n[…]\nver_with_packet\n- Remove TestParseDeviceStateServiceErrors from test_discovery_errors.py (tests\n  pinned to retired internal; D-10); remaining 8 tests still pass\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(01-02): thin discover_devices to wrapper; delete _parse_devi…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fa0a1173e1451c54f0e5d562487c682ad92b9aaf",
          "body": "…packet; adopt IdleDeadline\n\n- Import IdleDeadline from lifx.network.utils alongside allocate_source\n- Replace inline idle/overall timeout arithmetic with IdleDeadline instance\n- Check deadline.idle_expired and deadline.overall_expired for distinct DEBUG log messages\n- Add unconditional serial valid\n[…]\nwas written-only)\n- Align LifxProtocolError handler to use exc_info=True (matching discover_devices)\n- devices_found in completion log now uses len(seen_serials)\n\nSigned-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "refactor(01-02): hoist serial validation + dedup into _discover_with_…",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4345760e8151be97c88450b40157e45d4bb815bf",
          "body": "Signed-off-by: Avi Miller <me@dje.li>",
          "is_bot": false,
          "headline": "docs(01-04): complete deprecate-receive-many plan",
          "author_name": "Avi Miller",
          "author_login": "Djelibeybi",
          "committed_at": "2026-06-13T17:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 75,
      "commits_last_year": 499,
      "latest_release_at": "2026-07-31T09:02:04Z",
      "latest_release_tag": "v6.1.1",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 37,
      "days_since_latest_release": 0,
      "mean_days_between_releases": 5.3
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 57,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "lifx-async",
          "exists": true,
          "license": "UPL-1.0",
          "keywords": [
            "Framework :: AsyncIO",
            "Framework :: Pytest",
            "Intended Audience :: Developers",
            "Natural Language :: English",
            "Operating System :: OS Independent",
            "Programming Language :: Python :: 3.10",
            "Programming Language :: Python :: 3.11",
            "Programming Language :: Python :: 3.12",
            "Programming Language :: Python :: 3.13",
            "Programming Language :: Python :: 3.14",
            "Topic :: Software Development :: Libraries",
            "Topic :: Software Development :: Libraries :: Python Modules",
            "Typing :: Typed"
          ],
          "ecosystem": "pypi",
          "matches_repo": true,
          "registry_url": "https://pypi.org/project/lifx-async/",
          "is_deprecated": false,
          "latest_version": "6.1.1",
          "repository_url": "https://github.com/Djelibeybi/lifx-async",
          "versions_count": 75,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 3963,
          "first_published_at": "2025-11-04T04:01:44.942236Z",
          "latest_published_at": "2026-07-31T09:02:42.075413Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 0
        }
      ]
    },
    "popularity": {
      "forks": 1,
      "stars": 5,
      "watchers": 1,
      "fork_history": {
        "days": [
          {
            "date": "2026-07-15",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 1,
        "total_forks": 1
      },
      "star_history": null,
      "open_issues_and_prs": 2
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "src/lifx/py.typed"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 119553,
      "source_files_sampled": 237,
      "oversized_source_files": 4,
      "agent_instruction_files": [
        "AGENTS.md",
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 22347
    },
    "dependencies": {
      "manifests": [
        "pyproject.toml"
      ],
      "advisories": {
        "error": null,
        "scope": "repository_graph",
        "source": "osv",
        "findings": [],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 67,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "pypi"
      ],
      "dependencies": [],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "annotated-doc",
            "direct": false,
            "version": "0.0.4",
            "ecosystem": "pypi"
          },
          {
            "name": "annotated-types",
            "direct": false,
            "version": "0.7.0",
            "ecosystem": "pypi"
          },
          {
            "name": "backports-asyncio-runner",
            "direct": false,
            "version": "1.2.0",
            "ecosystem": "pypi"
          },
          {
            "name": "beautifulsoup4",
            "direct": false,
            "version": "4.15.0",
            "ecosystem": "pypi"
          },
          {
            "name": "click",
            "direct": false,
            "version": "8.4.2",
            "ecosystem": "pypi"
          },
          {
            "name": "colorama",
            "direct": false,
            "version": "0.4.6",
            "ecosystem": "pypi"
          },
          {
            "name": "coverage",
            "direct": false,
            "version": "7.15.0",
            "ecosystem": "pypi"
          },
          {
            "name": "deepmerge",
            "direct": false,
            "version": "2.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "exceptiongroup",
            "direct": false,
            "version": "1.3.1",
            "ecosystem": "pypi"
          },
          {
            "name": "ghp-import",
            "direct": false,
            "version": "2.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "griffelib",
            "direct": false,
            "version": "2.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "hatchling",
            "direct": false,
            "version": "1.30.1",
            "ecosystem": "pypi"
          },
          {
            "name": "iniconfig",
            "direct": false,
            "version": "2.3.0",
            "ecosystem": "pypi"
          },
          {
            "name": "jinja2",
            "direct": false,
            "version": "3.1.6",
            "ecosystem": "pypi"
          },
          {
            "name": "lifx-async",
            "direct": false,
            "version": "6.1.1",
            "ecosystem": "pypi"
          },
          {
            "name": "lifx-emulator-core",
            "direct": false,
            "version": "3.7.0",
            "ecosystem": "pypi"
          },
          {
            "name": "llmstxt-standalone",
            "direct": false,
            "version": "0.2.0",
            "ecosystem": "pypi"
          },
          {
            "name": "markdown",
            "direct": false,
            "version": "3.10.2",
            "ecosystem": "pypi"
          },
          {
            "name": "markdown-it-py",
            "direct": false,
            "version": "3.0.0",
            "ecosystem": "pypi"
          },
          {
            "name": "markdownify",
            "direct": false,
            "version": "1.2.3",
            "ecosystem": "pypi"
          },
          {
            "name": "markupsafe",
            "direct": false,
            "version": "3.0.3",
            "ecosystem": "pypi"
          },
          {
            "name": "mdformat",
            "direct": false,
            "version": "0.7.22",
            "ecosystem": "pypi"
          },
          {
            "name": "mdformat-tables",
            "direct": false,
            "version": "1.0.0",
            "ecosystem": "pypi"
          },
          {
            "name": "mdurl",
            "direct": false,
            "version": "0.1.2",
            "ecosystem": "pypi"
          },
          {
            "name": "mergedeep",
            "direct": false,
            "version": "1.3.4",
            "ecosystem": "pypi"
          },
          {
            "name": "mkdocs",
            "direct": false,
            "version": "1.6.1",
            "ecosystem": "pypi"
          },
          {
            "name": "mkdocs-autorefs",
            "direct": false,
            "version": "1.4.4",
            "ecosystem": "pypi"
          },
          {
            "name": "mkdocs-get-deps",
            "direct": false,
            "version": "0.2.2",
            "ecosystem": "pypi"
          },
          {
            "name": "mkdocstrings",
            "direct": false,
            "version": "1.0.4",
            "ecosystem": "pypi"
          },
          {
            "name": "mkdocstrings-python",
            "direct": false,
            "version": "2.0.5",
            "ecosystem": "pypi"
          },
          {
            "name": "nodeenv",
            "direct": false,
            "version": "1.10.0",
            "ecosystem": "pypi"
          },
          {
            "name": "packaging",
            "direct": false,
            "version": "26.2",
            "ecosystem": "pypi"
          },
          {
            "name": "pathspec",
            "direct": false,
            "version": "1.1.1",
            "ecosystem": "pypi"
          },
          {
            "name": "platformdirs",
            "direct": false,
            "version": "4.10.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pluggy",
            "direct": false,
            "version": "1.6.0",
            "ecosystem": "pypi"
          },
          {
            "name": "prek",
            "direct": false,
            "version": "0.4.8",
            "ecosystem": "pypi"
          },
          {
            "name": "py-cpuinfo",
            "direct": false,
            "version": "9.0.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pydantic",
            "direct": false,
            "version": "2.13.4",
            "ecosystem": "pypi"
          },
          {
            "name": "pydantic-core",
            "direct": false,
            "version": "2.46.4",
            "ecosystem": "pypi"
          },
          {
            "name": "pygments",
            "direct": false,
            "version": "2.20.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pymdown-extensions",
            "direct": false,
            "version": "11.0.1",
            "ecosystem": "pypi"
          },
          {
            "name": "pyright",
            "direct": false,
            "version": "1.1.411",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest",
            "direct": false,
            "version": "9.1.1",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-asyncio",
            "direct": false,
            "version": "1.4.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-benchmark",
            "direct": false,
            "version": "5.2.3",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-cov",
            "direct": false,
            "version": "7.1.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-retry",
            "direct": false,
            "version": "1.7.0",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-sugar",
            "direct": false,
            "version": "1.1.1",
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-timeout",
            "direct": false,
            "version": "2.4.0",
            "ecosystem": "pypi"
          },
          {
            "name": "python-dateutil",
            "direct": false,
            "version": "2.9.0.post0",
            "ecosystem": "pypi"
          },
          {
            "name": "pyyaml",
            "direct": false,
            "version": "6.0.3",
            "ecosystem": "pypi"
          },
          {
            "name": "pyyaml-env-tag",
            "direct": false,
            "version": "1.1",
            "ecosystem": "pypi"
          },
          {
            "name": "rich",
            "direct": false,
            "version": "15.0.0",
            "ecosystem": "pypi"
          },
          {
            "name": "ruamel-yaml",
            "direct": false,
            "version": "0.19.1",
            "ecosystem": "pypi"
          },
          {
            "name": "ruff",
            "direct": false,
            "version": "0.15.20",
            "ecosystem": "pypi"
          },
          {
            "name": "shellingham",
            "direct": false,
            "version": "1.5.4",
            "ecosystem": "pypi"
          },
          {
            "name": "six",
            "direct": false,
            "version": "1.17.0",
            "ecosystem": "pypi"
          },
          {
            "name": "soupsieve",
            "direct": false,
            "version": "2.8.4",
            "ecosystem": "pypi"
          },
          {
            "name": "termcolor",
            "direct": false,
            "version": "3.3.0",
            "ecosystem": "pypi"
          },
          {
            "name": "tomli",
            "direct": false,
            "version": "2.4.1",
            "ecosystem": "pypi"
          },
          {
            "name": "trove-classifiers",
            "direct": false,
            "version": "2026.6.1.19",
            "ecosystem": "pypi"
          },
          {
            "name": "typer",
            "direct": false,
            "version": "0.26.8",
            "ecosystem": "pypi"
          },
          {
            "name": "typing-extensions",
            "direct": false,
            "version": "4.16.0",
            "ecosystem": "pypi"
          },
          {
            "name": "typing-inspection",
            "direct": false,
            "version": "0.4.2",
            "ecosystem": "pypi"
          },
          {
            "name": "watchdog",
            "direct": false,
            "version": "6.0.0",
            "ecosystem": "pypi"
          },
          {
            "name": "wcwidth",
            "direct": false,
            "version": "0.8.2",
            "ecosystem": "pypi"
          },
          {
            "name": "zensical",
            "direct": false,
            "version": "0.0.47",
            "ecosystem": "pypi"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 67,
        "direct_count": 0,
        "indirect_count": 67
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 1,
        "merged_prs": 177,
        "open_issues": 1,
        "closed_ratio": 0.667,
        "closed_issues": 2,
        "closed_unmerged_prs": 5
      },
      "bus_factor": 1,
      "bot_contributors": 3,
      "top_contributors": [
        {
          "type": "User",
          "login": "Djelibeybi",
          "commits": 381,
          "avatar_url": "https://avatars.githubusercontent.com/u/103232?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "docs.yml",
        "pr-automation.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": true
    },
    "security_signals": {
      "lockfiles": [
        "uv.lock"
      ],
      "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": 4,
            "reason": "branch protection is not maximal on development and all release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "5 out of 5 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": 0,
            "reason": "Found 0/9 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "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": 10,
            "reason": "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": "30 commit(s) and 1 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": 10,
            "reason": "all dependencies are pinned",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 10,
            "reason": "SAST tool is run on all commits",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 10,
            "reason": "security policy file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 10,
            "reason": "GitHub workflow tokens follow principle of least privilege",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 10,
            "reason": "0 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "e4f8c95f6500172276da2f45dc9b8a2e7e896ab0",
        "ran_at": "2026-07-31T09:06:06Z",
        "aggregate_score": 7.2,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": true,
      "has_dependabot_config": true
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-31T09:03:06Z",
      "oldest_open_prs": [
        {
          "number": 167,
          "created_at": "2026-07-15T12:04:10Z",
          "last_comment_at": "2026-07-15T12:33:42Z",
          "last_comment_author": "Djelibeybi"
        }
      ],
      "last_merged_pr_at": "2026-07-31T08:57:16Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 1,
          "created_at": "2025-11-04T03:36:32Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/Djelibeybi/lifx-async",
    "host": "github.com",
    "name": "lifx-async",
    "owner": "Djelibeybi"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "good",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 71,
      "inputs": {
        "security": 78,
        "vitality": 90,
        "community": 36,
        "governance": 56,
        "engineering": 92
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "excellent",
        "name": "Vitality",
        "value": 90,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "excellent",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "commits_last_year": 499,
              "human_commit_share": 0.89,
              "days_since_last_push": 0,
              "active_weeks_last_year": 37
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "37/52 weeks with commits",
                "points": 25.6,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 37
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "499 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 499
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 75,
              "latest_release_tag": "v6.1.1",
              "releases_from_tags": false,
              "days_since_latest_release": 0,
              "mean_days_between_releases": 5.3
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "75 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 75
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~5.3 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 5.3
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "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": 0,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "last human commit 0 days ago",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_maintained",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "at_risk",
        "name": "Community & Adoption",
        "value": 36,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "forks": 1,
              "stars": 5,
              "watchers": 1,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "5 stars",
                "points": 9.8,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 5
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "1 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "1 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "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 (UPL-1.0)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "UPL-1.0"
                    }
                  }
                ],
                "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": "moderate",
            "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": 60,
            "inputs": {
              "packages": [
                "lifx-async"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 3963
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "3,963 downloads/month across pypi",
                "points": 48,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 3963,
                      "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": 56,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "moderate",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 68,
            "inputs": {
              "merged_prs": 177,
              "open_issues": 1,
              "closed_issues": 2,
              "issue_closed_ratio": 0.667,
              "closed_unmerged_prs": 5
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "67% of issues closed",
                "points": 31.2,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 67
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "177/182 decided PRs merged",
                "points": 37.2,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 177,
                      "decided": 182
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/9 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 63,
            "inputs": {
              "followers": 187,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "Djelibeybi",
              "public_repos": 46,
              "account_age_days": 6230
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "187 followers of Djelibeybi",
                "points": 16.4,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 187,
                      "login": "Djelibeybi"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "46 public repos, account ~17 yr old",
                "points": 24.2,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 46
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 17
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "lifx-async"
              ],
              "ecosystems": "pypi",
              "any_deprecated": false,
              "min_days_since_publish": 0
            },
            "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 0 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "75 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 75
                    }
                  }
                ],
                "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": "excellent",
        "name": "Engineering Quality",
        "value": 92,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "excellent",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 94,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": true
            },
            "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": 16,
                "status": "met",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 9.6,
                "status": "met",
                "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": "5 out of 5 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": 90,
            "inputs": {
              "topics": [
                "asyncio",
                "asyncio-python",
                "lifx",
                "lifx-bulbs",
                "lifx-lan-protocol",
                "lifx-lights",
                "llms-txt"
              ],
              "has_wiki": false,
              "homepage": "https://djelibeybi.github.io/lifx-async/",
              "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": "https://djelibeybi.github.io/lifx-async/",
                "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": "7 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 7
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "good",
        "name": "Security",
        "value": 78,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "good",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 72,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 7.2
            },
            "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": "branch protection is not maximal on development and all release branches",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "5 out of 5 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 0/9 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "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": "update tool detected",
                "points": 7.5,
                "status": "met",
                "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": "30 commit(s) and 1 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": "all dependencies are pinned",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is run on all commits",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "GitHub workflow tokens follow principle of least privilege",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "0 existing vulnerabilities detected",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories, No advisories left outstanding. Remaining weights renormalized. Matched 67 resolved dependencies against OSV. This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "indirect_dependencies_free_of_known_advisories",
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_repository",
                "params": {
                  "assessed": 67
                }
              },
              {
                "code": "advisories_repo_graph_caveat",
                "params": {}
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 0,
              "affected_packages": 0,
              "assessed_packages": 67,
              "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": "transitive set not separable from development and test dependencies in this scope",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_scope_not_separable",
                    "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": 67,
              "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": 2
            },
            "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": "good",
        "name": "AI Readiness",
        "value": 76,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.876,
              "agent_instruction_files": [
                "AGENTS.md",
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 22347
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "AGENTS.md, CLAUDE.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "AGENTS.md, CLAUDE.md"
                    }
                  }
                ],
                "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": "78 of 89 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 78,
                      "sampled": 89
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "good",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 82,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "uv.lock"
              ],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [
                "src/lifx/py.typed"
              ],
              "agent_commit_share": 0.21,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0.11
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "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": 11,
                "status": "met",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "src/lifx/py.typed",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "src/lifx/py.typed"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "21 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 21,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "11 of the last 100 commits are automated dependency updates",
                "points": 8,
                "status": "met",
                "details": [
                  {
                    "code": "dependency_bot_commits",
                    "params": {
                      "count": 11,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "all dependencies are pinned",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "good",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 81,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 119553,
              "source_files_sampled": 237,
              "oversized_source_files": 4
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python with type-check config (src/lifx/py.typed)",
                "points": 27,
                "status": "partial",
                "details": [
                  {
                    "code": "typecheck_config_language",
                    "params": {
                      "files": "src/lifx/py.typed",
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "4/237 source files over 60KB",
                "points": 54.1,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 237,
                      "oversized": 4
                    }
                  }
                ],
                "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",
    "deps.dev does not index pypi:lifx-async@6.1.1; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-31T09:06:24.077753Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/d/Djelibeybi/lifx-async.svg",
  "full_name": "Djelibeybi/lifx-async",
  "license_state": "standard",
  "license_spdx": "UPL-1.0"
}

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v1.13.0、模式 v0.27.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计PyPI.