原始 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"
}