Public record
Software health reportschema 0.27.0 · metrics 1.13.0 · 2026-07-24 05:17 UTC

roomkit-live / roomkit

Pure async Python framework for multi-channel conversations

PythonMIT★ 30 stars⑂ 3 forkssince Feb 2026View on GitHub ↗

roomkit-live/roomkit holds a health index of 64 out of 100, placing it in the Moderate band. It scores highest on Engineering Quality (88/100) and lowest on Security (43/100). It was last updated today. A single contributor accounts for most of its recent work.

64
overall / 100
Moderate

Software health index

Metrics are grouped into weighted categories on one standardized 1–100 scale. Overall starts as their weighted mean; when public evidence triggers the High-Risk Jurisdiction Policy, the rating is adjusted and receives an At risk ceiling of 49. AI Readiness sits outside the overall score.

64
Excellent85-100Exemplary; meets essentially all checked criteria
Good70-84Healthy; minor gaps
Moderate50-69Acceptable with notable gaps; review recommended
At risk30-49Significant weaknesses; adoption warrants caution
Critical1-29Severe problems (abandoned, single-maintainer, no hygiene)
VitalityCommunity &AdoptionSustainability &GovernanceEngineeringQualitySecurityAI Readiness

Score profile

Each axis is a category. The shape matters more than the average — a healthy subject fills the whole shape, while a spike-and-crater profile means strength in one dimension is masking risk in another.

Ownership

RoomkitOrganization
10 followers9 public repossince Feb 2026

This repository is backed by an organization — shared, accountable stewardship that can outlive any single maintainer.

Package ecosystems

RegistryPackageVersionDownloads / moVersionsLast publishTags
PyPIroomkit0.37.08,369970 days agoaiasyncchatmulti-channelrcsroomssmsvoicewhatsapp

Metrics by category

Vitality

Is the project alive — is code being written and are releases shipping?

84Good · 22% of overall
How it's scored
36/36Push recency — last push 0 days ago
15.9/36Commit cadence — 23/52 weeks with commits
18/18Commit volume — 1,212 commits in the last year
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Inputs used
commits_last_year1,212
human_commit_share0.95
days_since_last_push0
active_weeks_last_year23
How it's scored
27/27Ships releases — 86 releases published
36/36Release recency — latest release 0 days ago
27/27Release cadence — a release every ~1.2 days
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Inputs used
releases_count86
latest_release_tagv0.37.1
releases_from_tagsno
days_since_latest_release0
mean_days_between_releases1.2

Community & Adoption

Does the project have users, downloads, attention, and a welcoming setup for contributors?

56Moderate · 18% of overall
How it's scored
23.7/60Stars — 30 stars
2.5/25Forks — 3 forks
0/15Watchers — 0 watchers
Inputs used
forks3
stars30
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

Community health

85Excellent
How it's scored
22.5/22.5README
22.5/22.5License — recognized license (MIT)
18/18CONTRIBUTING guide
13.5/13.5Code of conduct
0/7.2Issue template
0/6.3PR template
Inputs used
has_readmeyes
has_licenseyes
has_contributingyes
has_issue_templateno
has_code_of_conductyes
has_pull_request_templateno
How it's scored
52.3/80Monthly downloads — 8,369 downloads/month across pypi
0/20Registry dependents — not reported by this ecosystem
Inputs used
packagesroomkit
dependents
ecosystemspypi
total_downloads
monthly_downloads8,369
Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.

Sustainability & Governance

Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?

47At risk · 24% of overall
How it's scored
9/54Bus factor — 1 contributor(s) cover half of all commits
0/22.5Commit distribution — top contributor authored 100% of commits
1.4/13.5Contributor breadth — 1 contributors
3/10OpenSSF Scorecard: Contributors — project has 1 contributing companies or organizations -- score normalized to 3
Inputs used
bus_factor1
contributors_sampled1
top_contributor_share1
How it's scored
0/46.8Issue resolution — no issues or no data
25.5/38.3PR acceptance — 8/12 decided PRs merged
0/15OpenSSF Scorecard: Code-Review — Found 0/29 approved changesets -- score normalized to 0
Inputs used
merged_prs8
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs4
Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.
How it's scored
30/30Ownership backing — organization-owned
0/20Verified domain
7.5/25Owner reach — 10 followers of roomkit-live
8.2/25Track record — 9 public repos, account ~0 yr old
Inputs used
followers10
owner_typeOrganization
is_verified
owner_loginroomkit-live
public_repos9
account_age_days168
How it's scored
25/25Published & resolvable — 1 package(s) on pypi
35/35Publish recency — latest publish 0 days ago
20/20Version history — 97 published versions
20/20Not deprecated — active, not deprecated or yanked
Inputs used
packagesroomkit
ecosystemspypi
any_deprecatedno
min_days_since_publish0

Engineering Quality

Are baseline engineering and documentation practices in place?

88Excellent · 20% of overall
How it's scored
24/24CI workflows — 1 workflow(s)
24/24Tests present
16/16Linter config
9.6/9.6Pre-commit hooks
0/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 1 out of 1 merged PRs checked by a CI test -- score normalized to 10
Inputs used
has_ciyes
has_testsyes
has_editorconfigno
has_linter_configyes
has_precommit_configyes
How it's scored
30/30README
25/25Documentation directory
15/15Documentation / homepage site — https://www.roomkit.live
10/10Repository description
0/10Topics
0/10Wiki
Inputs used
topics
has_wikino
homepagehttps://www.roomkit.live
has_readmeyes
has_docs_diryes
has_descriptionyes

Security

Are visible security and supply-chain practices strong, without unresolved high-risk jurisdiction exposure?

43At risk · 16% of overall
How it's scored
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
2.5/2.5CI-Tests — 1 out of 1 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/29 approved changesets -- score normalized to 0
0.8/2.5Contributors — project has 1 contributing companies or organizations -- score normalized to 3
10/10Dangerous-Workflow — no dangerous workflow patterns detected
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5License — license file detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
0/5Packaging — no data
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
5/5Security-Policy — security policy file detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
0/7.5Vulnerabilities — 71 existing vulnerabilities detected
Inputs used
sourceopenssf_scorecard
checks_evaluated17
scorecard_versionv5.5.0
checks_inconclusive1
scorecard_aggregate4.3
Excluded from scoring (no data or not applicable): packaging. Remaining weights renormalized.

AI Readiness

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.

80Good · 0% of overall
How it's scored
45/45Agent instructions — AGENTS.md, CLAUDE.md
15/15Machine-readable docs (llms.txt) — llms.txt present
39.9/40Legible commit history — 71 of 95 human commits state their intent (structured subject or explanatory body)
Inputs used
has_llms_txtyes
legible_history_share0.747
agent_instruction_filesAGENTS.md, CLAUDE.md
agent_instruction_max_bytes34,706
How it's scored
18/18One-command bootstrap — Makefile
22/22Automated tests
11/11Lint / format config
11/11Static type checking — src/roomkit/py.typed
10/10Reproducible environment — lockfile
0/10Demonstrated agent practice — no agent-authored commits among the last 100
8/8Automated maintenance — 5 of the last 100 commits are automated dependency updates
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Inputs used
has_nixno
has_testsyes
lockfilesuv.lock
has_dockerfileno
typed_languageno
bootstrap_filesMakefile
has_devcontainerno
has_linter_configyes
typecheck_configssrc/roomkit/py.typed
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0.05
How it's scored
27/45Type-checkable code — Python with type-check config (src/roomkit/py.typed)
54.8/55Manageable file sizes — 3/1,060 source files over 60KB
Inputs used
primary_languagePython
largest_source_bytes67,638
source_files_sampled1,060
oversized_source_files3
How it's scored
0/40API schema (OpenAPI/GraphQL/proto)
0/20MCP server
40/40Runnable examples — examples
Inputs used
example_dirsexamples
has_mcp_signalno
api_schema_files

Key facts

30GitHub stars
1contributors
1,212commits, last 12 months
0days since last push
86releases
1bus factor
0open issues
PyPIpackage ecosystems

Data collection warnings

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository
  • deps.dev does not index pypi:roomkit@0.37.0; advisories assessed against the repository dependency graph instead

More detail

Star and fork history 0 ★ / 3 ⇿
0Stars
3Forks
69Releases

When each star and fork was added, collected from GitHub and bucketed by day. Cumulative growth sits directly above the daily additions it is made of, so the two read against each other: steady organic accretion looks nothing like an abrupt, short-lived burst. Where that difference is measurable, it is reported as growth authenticity.

12233312026-022026-042026-07
Major 0Minor 16Patch 37
OpenSSF Scorecard 4.3 / 10
4.3aggregate

Independent, tool-agnostic security assessment from the open-source OpenSSF Scorecard. Each check rewards a security practice, not a specific vendor's tool. Checks Scorecard could not determine are marked n/a and excluded from the security score (never counted as zero).Scorecard v5.5.0 · 2026-07-24 05:17 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
10CI-Tests1 out of 1 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/29 approved changesets -- score normalized to 0
3Contributorsproject has 1 contributing companies or organizations -- score normalized to 3
10Dangerous-Workflowno dangerous workflow patterns detected
10Dependency-Update-Toolupdate tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
n/aPackagingpackaging workflow not detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTSAST tool is not run on all commits -- score normalized to 0
10Security-Policysecurity policy file detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
0Vulnerabilities71 existing vulnerabilities detected
Direct dependencies 1
RegistryPackageVersion constraintManifest
PyPIpydantic>=2.9pyproject.toml
All dependencies not collected

The resolved dependency set could not be collected for this report: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Raw JSON report machine-readable
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 12260,
      "has_wiki": false,
      "homepage": "https://www.roomkit.live",
      "languages": {
        "Shell": 11282,
        "Python": 7479794,
        "Makefile": 1080
      },
      "pushed_at": "2026-07-24T05:16:54Z",
      "created_at": "2026-02-05T23:44:45Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-24T05:15:55Z",
      "description": "Pure async Python framework for multi-channel conversations",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": "https://www.roomkit.live",
      "name": "Roomkit",
      "type": "Organization",
      "login": "roomkit-live",
      "company": null,
      "location": "Canada",
      "followers": 10,
      "avatar_url": "https://avatars.githubusercontent.com/u/259731342?v=4",
      "created_at": "2026-02-05T22:52:24Z",
      "is_verified": null,
      "public_repos": 9,
      "account_age_days": 168
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.37.1",
          "kind": "patch",
          "published_at": "2026-07-24T05:15:44Z"
        },
        {
          "tag": "v0.37.0",
          "kind": "minor",
          "published_at": "2026-07-24T04:53:50Z"
        },
        {
          "tag": "v0.36.0",
          "kind": "minor",
          "published_at": "2026-07-21T04:39:21Z"
        },
        {
          "tag": "v0.35.0",
          "kind": "minor",
          "published_at": "2026-07-20T22:08:48Z"
        },
        {
          "tag": "v0.34.0",
          "kind": "minor",
          "published_at": "2026-07-20T16:16:15Z"
        },
        {
          "tag": "v0.33.0",
          "kind": "minor",
          "published_at": "2026-07-20T15:51:38Z"
        },
        {
          "tag": "v0.32.0",
          "kind": "minor",
          "published_at": "2026-07-19T14:24:53Z"
        },
        {
          "tag": "v0.31.0",
          "kind": "minor",
          "published_at": "2026-07-19T13:10:18Z"
        },
        {
          "tag": "v0.30.0",
          "kind": "minor",
          "published_at": "2026-07-16T20:24:21Z"
        },
        {
          "tag": "v0.29.0",
          "kind": "minor",
          "published_at": "2026-07-13T19:39:36Z"
        },
        {
          "tag": "v0.28.0",
          "kind": "minor",
          "published_at": "2026-07-11T21:41:53Z"
        },
        {
          "tag": "v0.26.0",
          "kind": "minor",
          "published_at": "2026-07-10T16:23:24Z"
        },
        {
          "tag": "v0.25.0",
          "kind": "minor",
          "published_at": "2026-07-10T02:01:18Z"
        },
        {
          "tag": "v0.24.0",
          "kind": "minor",
          "published_at": "2026-07-08T04:41:36Z"
        },
        {
          "tag": "v0.23.0",
          "kind": "minor",
          "published_at": "2026-07-07T03:11:48Z"
        },
        {
          "tag": "v0.22.0",
          "kind": "minor",
          "published_at": "2026-07-06T18:59:04Z"
        },
        {
          "tag": "v0.20.0",
          "kind": "minor",
          "published_at": "2026-07-03T15:23:02Z"
        },
        {
          "tag": "v0.19.0",
          "kind": "minor",
          "published_at": "2026-06-26T20:52:57Z"
        },
        {
          "tag": "v0.18.0",
          "kind": "minor",
          "published_at": "2026-06-21T20:19:07Z"
        },
        {
          "tag": "v0.17.2",
          "kind": "patch",
          "published_at": "2026-06-21T03:26:08Z"
        },
        {
          "tag": "v0.17.1",
          "kind": "patch",
          "published_at": "2026-06-21T01:50:25Z"
        },
        {
          "tag": "v0.17.0",
          "kind": "minor",
          "published_at": "2026-06-20T23:46:39Z"
        },
        {
          "tag": "v0.16.0",
          "kind": "minor",
          "published_at": "2026-06-20T03:12:04Z"
        },
        {
          "tag": "v0.15.0",
          "kind": "minor",
          "published_at": "2026-06-18T19:24:42Z"
        },
        {
          "tag": "v0.14.0",
          "kind": "minor",
          "published_at": "2026-06-18T14:38:21Z"
        },
        {
          "tag": "v0.13.0",
          "kind": "minor",
          "published_at": "2026-06-18T01:49:42Z"
        },
        {
          "tag": "v0.12.0",
          "kind": "minor",
          "published_at": "2026-06-17T11:54:19Z"
        },
        {
          "tag": "v0.11.0",
          "kind": "minor",
          "published_at": "2026-06-14T12:06:05Z"
        },
        {
          "tag": "v0.10.0",
          "kind": "minor",
          "published_at": "2026-06-11T15:02:47Z"
        },
        {
          "tag": "v0.9.1",
          "kind": "patch",
          "published_at": "2026-06-11T11:43:31Z"
        },
        {
          "tag": "v0.9.0",
          "kind": "minor",
          "published_at": "2026-06-10T20:53:08Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2026-06-09T22:33:06Z"
        },
        {
          "tag": "v0.7.2",
          "kind": "patch",
          "published_at": "2026-06-07T01:23:12Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2026-05-22T12:52:08Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-05-15T15:48:18Z"
        },
        {
          "tag": "v0.7.0a17",
          "kind": "other",
          "published_at": "2026-04-30T21:28:10Z"
        },
        {
          "tag": "v0.7.0a16",
          "kind": "other",
          "published_at": "2026-04-23T14:06:32Z"
        },
        {
          "tag": "v0.7.0a15",
          "kind": "other",
          "published_at": "2026-04-23T11:35:56Z"
        },
        {
          "tag": "v0.7.0a14",
          "kind": "other",
          "published_at": "2026-04-17T21:43:41Z"
        },
        {
          "tag": "v0.7.0a13",
          "kind": "other",
          "published_at": "2026-04-17T01:31:05Z"
        },
        {
          "tag": "v0.7.0a12",
          "kind": "other",
          "published_at": "2026-04-08T18:09:05Z"
        },
        {
          "tag": "v0.7.0a11",
          "kind": "other",
          "published_at": "2026-04-04T20:58:01Z"
        },
        {
          "tag": "v0.7.0a10",
          "kind": "other",
          "published_at": "2026-04-03T20:35:13Z"
        },
        {
          "tag": "v0.7.0a8",
          "kind": "other",
          "published_at": "2026-04-01T20:26:47Z"
        },
        {
          "tag": "v0.7.0a7",
          "kind": "other",
          "published_at": "2026-03-28T02:25:56Z"
        },
        {
          "tag": "v0.7.0a6",
          "kind": "other",
          "published_at": "2026-03-28T01:51:37Z"
        },
        {
          "tag": "v0.7.0a5",
          "kind": "other",
          "published_at": "2026-03-27T00:56:42Z"
        },
        {
          "tag": "v0.7.0a4",
          "kind": "other",
          "published_at": "2026-03-26T00:58:29Z"
        },
        {
          "tag": "v0.7.0a3",
          "kind": "other",
          "published_at": "2026-03-25T03:00:07Z"
        },
        {
          "tag": "v0.7.0a2",
          "kind": "other",
          "published_at": "2026-03-24T20:56:17Z"
        },
        {
          "tag": "v0.7.0a1",
          "kind": "other",
          "published_at": "2026-03-24T19:08:15Z"
        },
        {
          "tag": "v0.6.13",
          "kind": "patch",
          "published_at": "2026-03-05T21:09:07Z"
        },
        {
          "tag": "v0.6.12",
          "kind": "patch",
          "published_at": "2026-03-05T20:03:38Z"
        },
        {
          "tag": "v0.6.11",
          "kind": "patch",
          "published_at": "2026-03-04T03:53:47Z"
        },
        {
          "tag": "v0.6.10",
          "kind": "patch",
          "published_at": "2026-03-03T17:54:23Z"
        },
        {
          "tag": "v0.6.9",
          "kind": "patch",
          "published_at": "2026-03-02T22:39:52Z"
        },
        {
          "tag": "v0.6.8",
          "kind": "patch",
          "published_at": "2026-03-02T18:07:19Z"
        },
        {
          "tag": "v0.6.7",
          "kind": "patch",
          "published_at": "2026-03-01T03:51:34Z"
        },
        {
          "tag": "v0.6.6",
          "kind": "patch",
          "published_at": "2026-02-28T19:52:35Z"
        },
        {
          "tag": "v0.6.5",
          "kind": "patch",
          "published_at": "2026-02-28T19:41:44Z"
        },
        {
          "tag": "v0.6.4",
          "kind": "patch",
          "published_at": "2026-02-28T16:29:02Z"
        },
        {
          "tag": "v0.6.3",
          "kind": "patch",
          "published_at": "2026-02-28T00:49:16Z"
        },
        {
          "tag": "v0.6.1",
          "kind": "patch",
          "published_at": "2026-02-26T14:38:47Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-02-25T04:37:28Z"
        },
        {
          "tag": "v0.5.3",
          "kind": "patch",
          "published_at": "2026-02-17T19:08:57Z"
        },
        {
          "tag": "v0.5.2",
          "kind": "patch",
          "published_at": "2026-02-17T04:31:17Z"
        },
        {
          "tag": "v0.5.1",
          "kind": "patch",
          "published_at": "2026-02-17T03:04:57Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-02-16T04:51:55Z"
        },
        {
          "tag": "v0.4.18",
          "kind": "patch",
          "published_at": "2026-02-14T01:05:22Z"
        },
        {
          "tag": "v0.4.17",
          "kind": "patch",
          "published_at": "2026-02-13T06:27:14Z"
        },
        {
          "tag": "v0.4.16",
          "kind": "patch",
          "published_at": "2026-02-13T04:25:50Z"
        },
        {
          "tag": "v0.4.15",
          "kind": "patch",
          "published_at": "2026-02-13T03:18:06Z"
        },
        {
          "tag": "v0.4.14",
          "kind": "patch",
          "published_at": "2026-02-12T03:23:02Z"
        },
        {
          "tag": "v0.4.13",
          "kind": "patch",
          "published_at": "2026-02-11T23:26:33Z"
        },
        {
          "tag": "v0.4.12",
          "kind": "patch",
          "published_at": "2026-02-11T18:06:16Z"
        },
        {
          "tag": "v0.4.11",
          "kind": "patch",
          "published_at": "2026-02-11T18:00:03Z"
        },
        {
          "tag": "v0.4.10",
          "kind": "patch",
          "published_at": "2026-02-11T17:23:18Z"
        },
        {
          "tag": "v0.4.9",
          "kind": "patch",
          "published_at": "2026-02-11T03:23:52Z"
        },
        {
          "tag": "v0.4.8",
          "kind": "patch",
          "published_at": "2026-02-10T20:58:31Z"
        },
        {
          "tag": "v0.4.7",
          "kind": "patch",
          "published_at": "2026-02-10T17:59:02Z"
        },
        {
          "tag": "v0.4.6",
          "kind": "patch",
          "published_at": "2026-02-10T15:57:17Z"
        },
        {
          "tag": "v0.4.5",
          "kind": "patch",
          "published_at": "2026-02-10T05:43:54Z"
        },
        {
          "tag": "v0.4.4",
          "kind": "patch",
          "published_at": "2026-02-09T06:37:40Z"
        },
        {
          "tag": "v0.4.3",
          "kind": "patch",
          "published_at": "2026-02-08T17:42:23Z"
        },
        {
          "tag": "v0.4.2",
          "kind": "patch",
          "published_at": "2026-02-08T17:04:50Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-02-07T19:10:39Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "b458ed19d628afc812c986f7c3fe41d4cee8bee5",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.38.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T05:15:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "32a0bd83de1100fdb5b27f35ec855fb230fa6222",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.37.1",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T05:15:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b5bf3028eb0b3002bd54bdc560ef3f15891fdc40",
          "body": "The huddle backend drives HuddleClient with paced=False so RoomKit's\nOutboundAudioPacer owns the outbound clock; that argument only exists\nin buzzkit 0.1.4. With the 0.37.0 floor (>=0.1.3) a huddle session\nfails with a TypeError on connect. 0.1.4 also moves WebSocket I/O to\na dedicated thread and drops late frames instead of bursting them.\n\nSupersedes dependabot PR #12 (lockfile-only bump).",
          "is_bot": false,
          "headline": "fix(buzz): require buzzkit>=0.1.4 — BuzzHuddleBackend needs paced=False",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T05:11:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1a4e5081d90520ca06157bc755cf617d929728c3",
          "body": "…solvable\n\nav>=18 (aiortc requires av<18), opentelemetry-api/sdk>=1.40 (mistralai\npins semantic-conventions<0.61), and transformers (version dictated by\nthe TTS extras' exact pins). Attempting these fails every weekly run\nwith dependency_file_not_resolvable while the lockfile itself resolves\nfine.",
          "is_bot": false,
          "headline": "ci(dependabot): ignore update candidates that upstream pins make unre…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T05:00:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f8d9b3ea47513792085953f8255cbfc90ddd882d",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.38.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T04:53:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7aa023f4d05cdd8155ec35c52b9b94851a50f097",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.37.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T04:53:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5a42ebd27b14ec732a0bd0c4ee8615630b3681c0",
          "body": "…any workflow",
          "is_bot": false,
          "headline": "fix(release): CI guard checks the CI workflow, not the latest run of …",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T04:52:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "51f63720a51205a900058c46a9ace6d3c79eb566",
          "body": null,
          "is_bot": false,
          "headline": "docs: promote changelog for 0.37.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T04:36:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "05db5d0ea8140d7f8e4e229af6f6003302304b99",
          "body": "Update the grouped Python dependencies while keeping the WebRTC stack on a NumPy version supported by numba. Fix ACP prompt completion so trailing session updates are drained before the stream closes, and extend CI to install the real fastrtc dependency set.",
          "is_bot": true,
          "headline": "build(deps): update Python dependencies and harden ACP/WebRTC CI (#10)",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-24T04:32:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "87f70a5e9763eb8dfc3255b7ed725bfc545cfa57",
          "body": null,
          "is_bot": false,
          "headline": "docs: prepare changelog for next release",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T03:57:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "21cf6568a52e79a9b53c47698026c848fe51aeb2",
          "body": null,
          "is_bot": false,
          "headline": "feat: add ACP channel and Claude CLI example",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T03:52:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6d42eb4b95d41904d378c3fab573b1f8350adb85",
          "body": "BuzzHuddleWatcher owns watching/dialing/rejoin and PipelineDebugTaps\ncovers audio-stage capture, so the example drops its custom tap backend,\nparser, and bridge plumbing — what is left is the promise: env, voice\nchannel, watcher, run.",
          "is_bot": false,
          "headline": "refactor(examples): buzz voice agent shrinks to framework wiring",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T02:22:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "653a800704a721a5fbc16b76a9d162fe70471d2d",
          "body": "…n the framework\n\nAn application now only builds its voice channel; the watcher subscribes\nto huddle announcements (kind 48100) through a BuzzRelaySource attached\nwith auto_restart, dials each huddle (client_factory injectable, creates\nclients with paced=False), bridges it, rejoins on connection los\n[…]\nnever delivered outbound through the Buzz channel. Also:\nhuddle_announcement_parser + KIND_HUDDLE_STARTED in sources.buzz, and a\npublic transport property on RealtimeVoiceChannel (mirror of provider).",
          "is_bot": false,
          "headline": "feat(voice): BuzzHuddleWatcher — the announcement-to-call lifecycle i…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T02:22:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "711dbda71ff416629bd3bb05ad52695606a902ac",
          "body": "Replace the hand-rolled watch/reconnect/hangup plumbing with RoomKit\nidioms: a BuzzRelaySource subscribed to kind 48100 emits huddle\nannouncements as room events (auto_restart reconnects to the relay), an\nAFTER_BROADCAST hook dials the huddle, and the transport ends the\nsession itself (end_when_alone / socket drop). The example only reads\nbuzz_end_reason to choose between rejoining and waiting for the next\nhuddle.",
          "is_bot": false,
          "headline": "refactor(examples): buzz voice agent goes through the framework",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T01:35:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc26e18bff13da9775836e1d251f1c58d915dcae",
          "body": "…zzRelaySource\n\nThe relay keeps a huddle alive while any member is connected — the agent\nincluded — so ending the call when the last remote peer leaves is\ntransport policy: the backend now watches the roster (second events()\nsubscriber) and fires the disconnect path with\nsession.metadata[\"buzz_end_r\n[…]\nactly once per session; deliberate disconnects fire no callback.\nBuzzRelaySource gains a kinds passthrough so a source can subscribe to\nmore than chat messages (e.g. huddle announcements, kind 48100).",
          "is_bot": false,
          "headline": "feat(voice): end_when_alone on BuzzHuddleBackend + kinds filter on Bu…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T01:35:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "dcdd21a500466cecd5fdebe0d4645f46b76357ed",
          "body": "…ive relay restarts\n\nThe relay keeps a huddle alive while any member is connected, the agent\nincluded, so the agent must hang up itself when the last human leaves\n(with a grace period for huddles nobody joined yet) — otherwise the\nhuddle never ends and the watch loop never moves on. A dropped audio\n\n[…]\n same huddle with backoff, and a rejected rejoin means the huddle is\nreally over. The announcement subscription also ends silently when the\nrelay restarts, so watch mode reconnects instead of exiting.",
          "is_bot": false,
          "headline": "fix(examples): buzz agent — hang up when alone, rejoin on drops, surv…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T01:23:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5e06dd50119c4f7ccebffccbc50a1b4262fc45b3",
          "body": "…ahead\n\nWith fill_with_silence_when_idle, any >20ms provider lull mid-response\ninserted a silence frame even while the pacer still had jitter headroom,\npermanently displacing the rest of the response — heard as chopped\nspeech with bursty providers. Fill now only fires once the pacer has\nactually fallen behind wall-clock.",
          "is_bot": false,
          "headline": "fix(voice): pacer silence fill must not splice into a response while …",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T00:22:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1d74fa1e285afa6c6015638c8c4792a75a27e625",
          "body": "Bridge a connected buzzkit.HuddleClient to the realtime voice pipeline:\n48 kHz huddle wire resampled to/from the provider's rates with a shared\nsoxr streaming resampler (also adopted by the Twilio backend), outbound\ntiming owned by OutboundAudioPacer, silence fill toward the provider so\nserver VAD sees continuous audio. Ships a Gemini Live example with an\noptional BUZZ_TAP_DIR debug tap that records every hop of the outbound\naudio path. New extra: roomkit[buzz].",
          "is_bot": false,
          "headline": "feat(voice): BuzzHuddleBackend — Opus voice transport over Buzz huddles",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-24T00:21:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d7be03c47600680d0c4f2fd8f948c826b2206112",
          "body": "- BuzzConfig.announce_presence (default True): BuzzRelaySource publishes a\n  kind-20001 'online' presence on connect and heartbeats within its TTL, so the\n  agent shows online in Buzz while running\n- BuzzConfig.auth_tag: optional NIP-OA owner-attestation tag, passed to the\n  BuzzClient and injected into the NIP-42 AUTH event\n- Require buzzkit>=0.1.2",
          "is_bot": false,
          "headline": "feat(channels): Buzz presence heartbeat + NIP-OA auth_tag",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-23T03:46:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a150994a798e6293e7f39db4ae00f8f31f11f6f8",
          "body": "…und publish\n\nsend_event now accepts an optional idempotency_key and sets it on the RoomEvent.\nThe locked pipeline already de-duplicates on it (the in-lock check_idempotency +\nthe unique events(room_id, idempotency_key) index), and send_event traverses\nthat same pipeline — it just had no way to carr\n[…]\ncate, so no second row and no re-broadcast. Default None keeps\nthe prior behaviour, matching inbound events that carry no key.\n\nTest: TestIdempotency::test_send_event_idempotency_key_dedupes_a_resend.",
          "is_bot": false,
          "headline": "feat(framework): idempotency_key on send_event for at-most-once outbo…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-23T03:38:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "99f58677144de623ad870027a3cb99bcc3b5d134",
          "body": "- BuzzConfig.auto_join (default True); BuzzRelaySource self-joins the channel\n  (NIP-29 kind 9000, role=bot) on connect via buzzkit.join_channel, so the\n  agent's messages reach other members and it resolves in mention autocomplete\n- Require buzzkit>=0.1.1 (adds join_channel + the nostr self-tag fix)\n- Type the optional buzzkit import as Any via TYPE_CHECKING so type-checking is\n  stable whether or not the compiled dep is resolvable",
          "is_bot": false,
          "headline": "feat(channels): Buzz auto-join — agent self-joins its channel",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-23T03:05:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc60bb83edb9943322b6c896653f3fdf826ade7e",
          "body": "Source + provider pair (mirrors Discord) bridging a Buzz/Nostr relay channel:\n- BuzzRelaySource wraps buzzkit.BuzzClient (NIP-42 auth + real-time subscribe)\n- BuzzProvider publishes outbound over the HTTP bridge, reusing the source client\n- ChannelType.BUZZ, BuzzChannel factory, config/base/relay/mo\n[…]\nt added as an isolated optional extra (roomkit[buzz]); core stays pure Python\n- Tests (parser + provider + inbound integration) and an echo-bot example\n\nLive-validated against a hosted Buzz community.",
          "is_bot": false,
          "headline": "feat(channels): add Buzz (Nostr relay) transport channel via buzzkit",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-23T02:30:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "725531033d36914446ecc7d7a4e7ab74708f0c0a",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.37.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-21T04:39:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8cfeb679fbb573b4e36227d0da56bc4aa7797ca7",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.36.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-21T04:39:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6c5e0e776c991e84a8219c69528681b75b0c2126",
          "body": "A muted channel's non-streaming response_events were suppressed in\nbroadcast(), but a streaming response was captured into\nresult.streaming_responses and returned *before* the mute check — so a\nmuted streaming intelligence channel still produced a reply.\n\nThis was latent while send_event dropped str\n[…]\n mirroring the response_events suppression and the RFC\n'muting silences the voice, not the brain'. Regression test covers the\nmuted (suppressed, generator never started) and unmuted (delivered)\ncases.",
          "is_bot": false,
          "headline": "fix(router): muting silences a channel's streaming voice too",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-21T03:50:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "22e148fd2b9751e81836f6560638b532ac1eaabc",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.36.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T22:08:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "463131cf1566931f8696fe1bc451b3bbfc7166ff",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.35.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T22:08:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "90e4cb25a74c206578c079e68a94bc7c60cc36f9",
          "body": "A directly-injected event that woke a streaming intelligence channel had\nits response generated and then silently dropped: send_event ran the\nlocked pipeline but omitted the post-lock streaming-response drain the\ninbound path performs, so broadcast_result.streaming_responses was\ncollected and never \n[…]\n lock, exactly like process_inbound. Non-streaming providers were\nunaffected; injections that don't wake an agent are a no-op.\n\nPromotes Unreleased (this + regenerate/voice error surfacing) to 0.35.0.",
          "is_bot": false,
          "headline": "fix(framework): send_event consumes streaming AI responses (0.35.0)",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T22:05:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "30709ee5eec52d82397c5a52be924647b2fed14d",
          "body": "- regenerate_response fires ON_ERROR for a non-streaming intelligence\n  failure (parity with the inbound path); the streaming path already\n  fires its own, so they never double up.\n- _voice_tts: a provider without synthesize_stream emits tts_error, not\n  only an ERROR log.\n- _voice_stt: a continuous\n[…]\nor like the\n  VAD twin.\n- deepgram: transcribe_stream raises on the SDK on_error callback so the\n  consumer marks the stream failed (state.error) and reconnects instead\n  of seeing a clean, empty end.",
          "is_bot": false,
          "headline": "fix(regenerate,voice): surface failures via ON_ERROR / error events",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T21:53:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "567392457b7fcadf8a024bb0d24a0041fc1f1198",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.35.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T16:16:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8d137c48e1c965bf24b6935da4d1b7aedb7c5f67",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.34.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T16:16:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e0fce5db921006f7c9de96464388e034f47e5949",
          "body": "When there is no streaming target, the failure is returned on\nInboundResult.error to a one-shot programmatic caller that logs it\nitself; the framework's own WARNING just duplicated the caller's line\nfor the same incident. A ProviderError on that headless path now logs at\nDEBUG. With a streaming target (interactive) the framework WARNING is\nunchanged, and unexpected errors keep their traceback.",
          "is_bot": false,
          "headline": "fix(inbound): log a headless turn failure at DEBUG, not WARNING",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T16:12:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a7945b56a76c85dde1b087fb2938effa99fd56a",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.34.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T15:51:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "27ee837f4c102f3f7d8fd9b6fa2c55e874905b85",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.33.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T15:51:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b160c93ede7db0ad16b50e2a7b3ab2624d1b3437",
          "body": "…ndResult.error\n\nComplete the InboundResult.error contract symmetrically. The streaming\npath already returned the failure on the result; the non-streaming\ngeneration path and regenerate_response were left with the same hole\nthat caused the original symptom — ON_ERROR fired, but process_inbound /\nreg\n[…]\nng broadcast error too.\n- _ai_generation: log a transient ProviderError as WARNING, no stack.\n\nThe non-streaming ON_ERROR card was already correct; regenerate's\nnon-streaming card remains a follow-up.",
          "is_bot": false,
          "headline": "feat(broadcast): surface non-streaming + regenerate failures on Inbou…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T14:32:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fc3ba0207f3cc339141f641017e62df58217e582",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): promote Unreleased to 0.33.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T13:53:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0e3e6b0cc58b450d8300687e0e1b4ee175e8b2e1",
          "body": "A failure while consuming an intelligence channel's streaming response\n(provider/transport error, context overflow) fired ON_ERROR and then\nvanished: process_inbound returned a result with no signal, so a headless\none-shot caller with no streaming target saw an empty response instead of\nthe error.\n\n\n[…]\nrror-card behaviour is unchanged. A ProviderError — an\nexpected transient, now returned to the caller — is logged as one WARNING\nline without a traceback; any other exception keeps its full traceback.",
          "is_bot": false,
          "headline": "feat(inbound): return response-stream failures on InboundResult.error",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-20T13:50:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "697357dbe3daf30d9b3c309a8fc634e28bb3eddf",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.33.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T14:24:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "aeba56e37c7854e987b0bf96e1e1ea812b900df6",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.32.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T14:24:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "67c30daefd358314d0f107657c4573ef7ecfae05",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): promote Unreleased to 0.32.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T14:20:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9f97141232d978f08f3e894c3b6c4ed9f206c8a3",
          "body": "update_room is a full-row read-modify-write: a caller holding a stale\nRoom silently clobbers concurrent metadata patches and regresses the\nevent_count/latest_index/timers counters maintained by commit_event.\npatch_room_metadata(room_id, patch, *, unset=()) touches only the keys\nit is given — documented non-atomic default on the ABC, single\n(metadata - unset) || patch JSONB update on the Postgres store.",
          "is_bot": false,
          "headline": "feat(store): atomic patch_room_metadata API",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T14:14:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "75b1df575b196a7004f9cc6a6ba684a38d1d4161",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.32.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T13:10:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bca627cf96769128c791f9df1f2df7218c131f0d",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.31.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T13:10:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e6af918acace9320565c22d5c53faf7fee68ca32",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): promote Unreleased to 0.31.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T05:33:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eab5641ae750d5734c84db4208f8d0378409e90c",
          "body": "…T_DELETED hooks\n\nNew EventOpsMixin exposes host-owned update_event()/delete_event() on\nRoomKit: mutations run under the room lock, authorization stays with the\ncaller, and the new HookTrigger.ON_EVENT_UPDATED / ON_EVENT_DELETED fire\nafter the lock is released. The store gains delete_event() (hard d\n[…]\ns it silently dropped.\nThe RFC §10.3 inbound EDIT/DELETE path fires the same triggers through\nthe generalized deferred async-hook sink, so observers see every\nstored-state change regardless of origin.",
          "is_bot": false,
          "headline": "feat(events): direct update/delete APIs with ON_EVENT_UPDATED/ON_EVEN…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-19T05:08:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "20076b7bd51968662693572dfaadaf753b376fdc",
          "body": "* build(deps): bump the python-deps group with 35 updates\n\n---\nupdated-dependencies:\n- dependency-name: pydantic\n  dependency-version: 2.13.4\n  dependency-type: direct:production\n  update-type: version-update:semver-minor\n  dependency-group: python-deps\n- dependency-name: anthropic\n  dependency-vers\n[…]\n---\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\nCo-authored-by: Sylvain Boily <sylvainboilydroid@gmail.com>",
          "is_bot": true,
          "headline": "build(deps): bump the python-deps group with 35 updates (#7)",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-17T13:03:13Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d66ffebc72bc5df8d6426b310341b068a2bda8b9",
          "body": "Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 7.\n- [Release notes](https://github.com/astral-sh/setup-uv/releases)\n- [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v7)\n\n---\nupdated-dependencies:\n- dependency-name: astral-sh/setup-uv\n  dependency-version: '\n[…]\nirect:production\n  update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
          "is_bot": true,
          "headline": "build(deps): bump astral-sh/setup-uv from 4 to 7 (#6)",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-17T12:59:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2e77c14da2d815a6295c4ed8bc4281f64cefe09e",
          "body": "Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.\n- [Release notes](https://github.com/actions/checkout/releases)\n- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/actions/checkout/compare/v4...v7)\n\n---\nupdated-dependenc\n[…]\nirect:production\n  update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
          "is_bot": true,
          "headline": "build(deps): bump actions/checkout from 4 to 7 (#4)",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-17T12:59:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "51d90f10d6a56c8a715bb9f735679ab7a5c3d236",
          "body": "Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.\n- [Release notes](https://github.com/actions/upload-artifact/releases)\n- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)\n\n---\nupdated-dependencies:\n- dependency-name: actions/upload-artifac\n[…]\nirect:production\n  update-type: version-update:semver-major\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>\nCo-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>",
          "is_bot": true,
          "headline": "build(deps): bump actions/upload-artifact from 4 to 7 (#5)",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-17T12:47:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "60e77050b50b4960a431737f27d9daee01358fa7",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.31.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-16T20:24:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f45391293182c862fec50c0cdb2fbe32e7844605",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.30.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-16T20:24:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bbd924bf87b2111b88d56e77948bea648a3122cf",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): promote Unreleased to 0.30.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-16T20:19:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f4eace66130b1b90bbabf7c148192ad764b7a803",
          "body": "Follow-up to the (parent_event_id, index) change: reusing the name\nidx_events_parent meant init()'s CREATE INDEX IF NOT EXISTS no-op'd against the\nold single-column index, so only fresh databases got the composite.\n\nShip the composite under a new name, idx_events_parent_index, so init() creates\nit a\n[…]\nhat predate it, since init() never drops.\n\nTests: schema pins the composite name/columns and that the single-column index\nis not re-created; the migration drops only when present and no-ops otherwise.",
          "is_bot": false,
          "headline": "fix(store): make the composite thread index reach existing databases",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-16T20:16:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a9e6a4d80a2fc98eacc8b7824629c3361a51ae81",
          "body": "Thread-reply pagination filters events by parent_event_id then reads forward\nORDER BY index. The single-column idx_events_parent forced a sort of the whole\nthread on every page; widening it to (parent_event_id, index) returns the page\nalready ordered. The leading column still serves plain parent_event_id lookups,\nso no existing query loses its index.",
          "is_bot": false,
          "headline": "perf(store): index thread replies on (parent_event_id, index)",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-16T18:50:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "49d948cd4f3228c8d1bd49566f72db67b991fbad",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.30.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:39:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2b57baa3a6f4a9cca0b0c5698ea0e2b0161712f7",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.29.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:39:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b455ace2ce9fa698a8dcda93d3b5c9aed15aa33b",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): promote Unreleased to 0.29.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:37:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ea61d5e71853692a7376ba13ec680a885d6df2bc",
          "body": "… shortcut\n\nThe \"already released, just re-push\" shortcut read the version from the worktree\n_version.py. If the dev-cycle commit itself failed (the bump staged but not\ncommitted), the worktree already showed the .dev version, so a re-run matched\nthe shortcut, pushed, and exited — orphaning the staged bump. It now reads the\nversion from HEAD: when the dev-cycle commit failed, HEAD is still the release\ncommit (not .dev), so the run falls through and re-runs to finish the commit.",
          "is_bot": false,
          "headline": "chore(release): read HEAD, not the worktree, for the dev-cycle resume…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:26:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d98aa702b5772d86fe0d13304b15490dedfbabf9",
          "body": "Three residual issues from the commit_event unification: some events reached the\ntimeline PENDING, and a reentry's injection was ordered before its cause.\n\n- Injected events (_deliver_injected_events) were committed with their default\n  PENDING status; they are delivered timeline events and now comm\n[…]\nen its injections are\n  delivered — the cause takes the lower index, mirroring the main path where the\n  event commits before broadcast.\n\nAdds test_reentry_injection_commits_delivered_after_its_cause.",
          "is_bot": false,
          "headline": "fix(pipeline): commit injected/child events DELIVERED, in causal order",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:26:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a65b1357048f70b2cee14a9596aed7f4e299b927",
          "body": "The PyPI existence check aborted every re-run once the version was on PyPI,\nblocking two legitimate resumes: uploading the second artifact after the first\nlanded, and finishing the dev-cycle commit/push after a successful publish.\n\n- A local tag v${VERSION} now distinguishes a resume from a fresh re\n[…]\n\n- An early exit detects a fully released version whose next dev cycle was opened\n  (tree on a .dev version + tag present) and just re-pushes instead of aborting.\n- The dev-cycle commit is idempotent.",
          "is_bot": false,
          "headline": "chore(release): resume after a partial or complete PyPI publish",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:09:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3b77b1bf3b75617ca4959212dce3a595fc3bbbd3",
          "body": "The prior change made the inbound path atomic but left four writers still\nbypassing commit_event, so the timeline and the room counters could still\ndiverge (event_count / latest_index off, events stuck PENDING):\n\n- regenerate.py: the regenerated response was stored PENDING via\n  add_event_auto_index\n[…]\n declares _commit_event on the RegenerateMixin for the type\nchecker. Task tests that mocked/asserted add_event_auto_index now use\ncommit_event; adds regenerate + greeting consistency regression tests.",
          "is_bot": false,
          "headline": "fix(pipeline): route the last timeline writes through commit_event",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T19:09:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fdbd6ef9d39da7313f31788f7b2ad75b0a6002b0",
          "body": "Two resume hazards remained after moving the SBOM ahead of the Git mutations:\n\n- The version bump (sed on _version.py / test_public_api.py) runs before the\n  build, so a failure there left the tree dirty and a re-run aborted at the\n  clean-tree check. The check now tolerates uncommitted changes to e\n[…]\n already existed, so a re-run after a\n  failed PyPI upload (Release created, publish failed) could not reach the\n  retryable publish step. Creating the Release is now skipped when it already\n  exists.",
          "is_bot": false,
          "headline": "chore(release): make release.sh re-runnable end to end",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:43:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "92f512e74ebab5c650005ba5725faa596d29f86a",
          "body": "…est_index\n\nFollow-up to the atomic-commit work: the trigger message committed atomically,\nbut AI reentries and streamed segments did not — the review reproduced the\ndivergence (event_count=4, latest_index=2, a PENDING AI reply at index 3), which\nRFC §10.1 step 13 forbids.\n\nEvery path that adds to a\n[…]\n/test_commit_atomicity.py (reentry DELIVERED + counter consistency,\nchain-depth-blocked consistency, policy no-phantom, streaming DELIVERED) and a\nreal-Postgres end-to-end AI-reentry consistency test.",
          "is_bot": false,
          "headline": "fix(pipeline): commit every timeline write atomically; no phantom lat…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:43:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f47cd47ee5977477bb5babe1b28577938472dc96",
          "body": "…edx-bom\n\nThe SBOM was generated by fetching cyclonedx-bom over the network AFTER the\nversion commit and tag, so a flaky download left the release half-prepared and a\nre-run failed on \"nothing to commit\" / \"tag already exists\". Build and SBOM now\nrun before the commit/tag while the tree is still clean; the generator is pinned\n(cyclonedx-bom==7.3.0) for a reproducible SBOM; and the commit and tag steps are\nidempotent so a run that fails on the network is safe to re-run.",
          "is_bot": false,
          "headline": "chore(release): generate the SBOM before any Git mutation, pin cyclon…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:03:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b7d0b0e0c4cd3251b28f8872785da36bfc22ae76",
          "body": "The matrix presented itself as the single conformance gate while several\nassertions only checked for the presence of a field or class. Its docstring now\ndistinguishes behavioural checks from structural (API-surface) ones and points\nto the feature suites that own the end-to-end coverage (test_postgre\n[…]\ntest_ai_chain_depth for reentry depth, test_channel_abc for\ntranscoding). The timers auto-pause/close, chain-depth blocking, and\ntranscoder-fallback checks are upgraded from structural to behavioural.",
          "is_bot": false,
          "headline": "test: make the Level 0 conformance matrix honest and behavioural",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:03:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5f382adb1640eed098b4770c70bf3408bd398836",
          "body": "Per the project policy of exporting new public classes: roomkit.store now\nexports PostgresStore and PostgresAdvisoryLockManager, and the top-level\nroomkit package exports RoomLockManager and InMemoryLockManager (the documented\nlocking extension point, RFC §13.5). Importing the postgres submodules stays\nasyncpg-free (asyncpg is imported lazily on instantiation), so a base install\nis unaffected.",
          "is_bot": false,
          "headline": "feat(api): export lock managers and Postgres store/lock",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:03:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a1a206aa21100ed3d5a8a004489562a1d9d1ef1d",
          "body": "…the lock\n\nTwo RFC-conformance fixes from the pre-0.29.0 review.\n\nP1 (RFC §8.1 / §10.1 step 12 / §14.3) — the inbound commit was not atomic:\nthe index was assigned with get_event_count(), then the event and the room\ncounters were written in separate store calls. Two processes without an\nadvisory loc\n[…]\nw collected into a pending_error_hooks_out sink and run\nafter the lock is released, like AFTER_BROADCAST. A deterministic test proves\nthe hook fires with the room absent from the _held_rooms lock set.",
          "is_bot": false,
          "headline": "fix(pipeline): commit inbound events atomically; defer ON_ERROR past …",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-13T18:03:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "39f77becbee1a4186d7cf7665c741ea196e47c04",
          "body": "…lease\n\nEach release now ships a per-version Software Bill of Materials\n(roomkit-<version>.cdx.json): a CycloneDX inventory of the runtime dependency\ntree (core + the providers extra) generated from the frozen lockfile via\ncyclonedx-py, attached as a GitHub Release asset. Lets downstreams audit a\nspecific version for vulnerabilities and licenses long after release —\ncomplements the blocking pip-audit gate and Dependabot.",
          "is_bot": false,
          "headline": "ci(release): generate a CycloneDX SBOM and attach it to the GitHub Re…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T20:12:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "51f52e6c8f37acd6ff85b65a03b5c6f67dae568c",
          "body": "Adds tests/test_conformance_level0.py — one assertion per RFC §25.1 Core\n(REQUIRED) requirement, each mapped to its section: room lifecycle/timers,\nevent types & content, sequential indexing (§8.1), channel interface,\nbinding access/mute/visibility, permission enforcement (§7.5), SYNC/ASYNC\nhooks + \n[…]\ny. Behavioural where clean, structural where a full harness adds no\nsignal — the single 'is RoomKit Level 0 conformant?' gate and a regression net\nfor the spec invariants before higher-risk refactors.",
          "is_bot": false,
          "headline": "test: executable RFC Level 0 conformance matrix",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T19:41:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "48dc789523e576c2018b1253d503dfb3324ff942",
          "body": "A tested, opt-in repair for databases carrying duplicate (room_id, index) rows\nfrom a pre-fix release: in one transaction it renumbers each affected room's\nevents to a unique sequential 0..N-1 (by index, created_at, id), reconciles the\nroom counters, and (re)creates idx_events_room_index as UNIQUE. \n[…]\n SQL on production.\n\nVerified against postgres:16: dry-run reports without changing; repair yields\nunique sequential indices + a UNIQUE index that rejects further duplicates;\nnoop on a clean database.",
          "is_bot": false,
          "headline": "feat(store): PostgresStore.dedupe_event_indices() repair helper",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T19:12:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c37772f8e160d7f9a83ac215e1ff257c4700c960",
          "body": "The PL/pgSQL RAISE WARNING can be swallowed by asyncpg, so init() now checks\nidx_events_room_index after applying the schema and logs a roomkit-side warning\nwhen it is not UNIQUE — making the degraded (duplicate indices present,\nmulti-process safety off) state visible in the application logs.",
          "is_bot": false,
          "headline": "fix(store): log a visible warning when the events index stays non-unique",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T19:04:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e5da560436de771e5c4ee8652c2b086bdd3d7ee3",
          "body": "… UNIQUE\n\nH1b's UNIQUE(room_id, index) migration ran in init()'s SCHEMA and raised\nUniqueViolationError when the existing table already held duplicate\n(room_id, index) rows from a prior release's races — crashing app startup.\ninit() must never fail on existing data (same rule as the destructive-migr\n[…]\nunique index) and logs a WARNING telling\nthe operator to deduplicate; a clean database still upgrades to UNIQUE.\n\nVerified against postgres:16 with a seeded duplicate index=0.\nRegression from 8839130.",
          "is_bot": false,
          "headline": "fix(store): don't fail init() when upgrading idx_events_room_index to…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T19:00:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6735d6d8a4430288c232bb4c471293e74c510c80",
          "body": "'uv pip install fastapi' pulled a newer starlette whose TestClient requires\nhttpx2 and raised a StarletteDeprecationWarning that pytest (filterwarnings=\nerror) turned into a collection error. 'uv run --with fastapi' resolves fastapi\nagainst the locked deps, keeping a working starlette/httpx combination.",
          "is_bot": false,
          "headline": "ci: resolve fastapi via 'uv run --with' in the integration job",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T18:45:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0c6536380681f0bcdbcd1914a4727cf1562b6e5e",
          "body": "New 'integration' job with a postgres:16 service container (POSTGRES_DSN) and\nfastapi installed, running the tests that self-skip in the fast unit job:\nPostgres store CRUD/migration, the multiprocess index-safety proof (H1b), and\nthe WebRTC /webrtc/offer auth path (P0-2). The fast 'test' job stays mock-only\nfor quick feedback.",
          "is_bot": false,
          "headline": "ci: run real-Postgres + WebRTC integration tests",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T18:41:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7328f81c787d3a4965d5a35476e8b7a801c467c5",
          "body": "The UNIQUE(room_id, index) constraint surfaced pre-existing test setups that\ninserted several events at the default index 0 via add_event — the framework\nnever does this. _make_event now assigns a per-room monotonic index (reset per\ntest), mirroring the framework and the cursor tests that already us\n[…]\nte index and that two advisory-lock managers\n(separate pools = two processes) sharing one DB serialize concurrent index\nassignment into unique sequential indices. Verified locally against postgres:16.",
          "is_bot": false,
          "headline": "test(store): distinct indices in Postgres tests + multiprocess H1b proof",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T18:41:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a2b0fee79277b686191cba678e91134e577b514f",
          "body": "ruff format on the new file (missed in the H1b commit; CI's repo-wide\n`ruff format --check .` caught it).",
          "is_bot": false,
          "headline": "style: format postgres_lock.py",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T18:16:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "88391309ea6d67fec8d5d8853a9d2cd32d9713b0",
          "body": "A shared PostgresStore behind multiple processes could assign duplicate event\nindices: the index is computed as count(*) under a per-process in-memory lock\nthat does not coordinate across processes, and events(room_id, index) had no\nunique constraint — so duplicates persisted silently and broke pagi\n[…]\n-memory store is paired with InMemoryLockManager.\n- RoomLockManager gains close() (default no-op), called by RoomKit.close().\n\nReview: H1b. RFC §8.1 / §13.5 / §14.3 updated in roomkit-specs (a7fb3ef).",
          "is_bot": false,
          "headline": "feat(store): distributed room locking + UNIQUE(room_id, index)",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T18:13:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "102ebd3a498849627fe65d081f40fecb00e00c68",
          "body": "Only the streaming consumption path fired ON_ERROR on a provider/inference\nfailure. Three other paths swallowed the error into a log line, leaving the\nroom with no error event — so hosts (e.g. Luge's error card) rendered nothing:\n\n- an error raised in the AI channel's on_event before the stream begi\n[…]\nresponse now re-raises instead of swallowing, so a\nnon-streaming failure reaches the same path. One firing site covers every\nnon-streaming failure route.\n\nTests: tests/test_inbound_error_surfacing.py.",
          "is_bot": false,
          "headline": "fix(inbound): surface swallowed agent-turn failures to ON_ERROR",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-12T04:06:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "066025e8b9f02658f837e01d8bcd22f9925df4d9",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.29.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T21:41:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8c969357a26258a6736568e2d9ed95a0ee6b3feb",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.28.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T21:41:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "13bc0b511ce63477e6955af6ba880ae68398ad69",
          "body": "0.27.0 reached PyPI but not git (no tag, no GitHub Release) because the script\npublished to PyPI before pushing git state, and never checked the CHANGELOG.\nThree guardrails: abort if the version already exists on PyPI; abort if\nCHANGELOG.md has no entry for it; and publish to PyPI LAST, after the commit,\ntag, and GitHub Release are pushed, so a failed upload is retryable and PyPI is\nnever ahead of the repository.",
          "is_bot": false,
          "headline": "ci(release): harden release.sh against the 0.27.0-style partial release",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T21:38:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0edc4fb042d4e3836d278f3f83fa10f02de3ee75",
          "body": "0.27.0 was published to PyPI (2026-07-10) but never got a CHANGELOG entry or a\ngit tag; add it retroactively. 0.28.0 documents the production-readiness\nremediation (Postgres migration, WebRTC auth, inbound-timeout atomicity, tool\nauthorization, PII redaction, supply-chain gating).",
          "is_bot": false,
          "headline": "docs(changelog): document 0.27.0 (retro) and 0.28.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T21:38:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e1f5bc05d9a9d338b6e3dcea5850bda6a926cea9",
          "body": "…ocstring\n\nMIN-1: validation._matches_type was a 7-arm if/elif keyed on json_type — the\nif-chain-for-dispatch that CLAUDE.md rule 5 forbids. Replaced with a\n_TYPE_CHECKS registry (type name -> predicate); behaviour identical, verified\nby test_tool_arg_validation.\n\nMIN-2: _webrtc_auth module docstring narrated the pre-fix state in past tense\n(\"that path was never authenticated\"). Reworded to present tense describing\ncurrent behaviour and the gate's role — comments describe state, not history.",
          "is_bot": false,
          "headline": "refactor(tools): dispatch table for JSON type checks; present-tense d…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T18:51:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dc39f047f0ba4b08662e9e78d9303d6d4637a5e0",
          "body": "TEMP-1: inbound_locked.py comments pinned to RFC pipeline step numbers\n(\"RFC §10.1 step 12/15\", \"steps 3-12\"). Step numbers in the RFC's ASCII\npipeline can renumber; the section anchor cannot. Keep \"§10.1\", drop the\nvolatile step numbers.\n\nTEMP-2: postgres_schema.py / postgres.py said \"legacy v1 sch\n[…]\nma\". CLAUDE.md\nrule 1 bans \"legacy\" in comments, and \"v1\" already names the format\nprecisely without the temporal connotation.\n\nBoth violate the rule that comments describe current state, not history.",
          "is_bot": false,
          "headline": "style: drop temporal markers from code comments",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T18:50:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d3ee25bd5add669197c2e209bb0f2468cdf0155a",
          "body": "…t neonize)\n\nReview: 'le README propose roomkit[neonize], extra inexistant'.",
          "is_bot": false,
          "headline": "docs(readme): fix WhatsApp Personal extra name (whatsapp-personal, no…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T18:07:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "43c54f17ea57c09f0dcfeff037580ab75163c0ae",
          "body": "InMemoryStore returned shallow model_copy() objects, so mutating a nested\nfield (e.g. event.metadata['x']['y']) on a read object silently mutated the\nstored object. Reads now deep-copy. Adds ConversationStore.close() (default\nno-op, idempotent) and calls it from RoomKit.close() so a PostgresStore\nconnection pool no longer leaks on shutdown.\n\nReview: high-risks 'immutabilité fictive du store mémoire' + 'cycle de vie incomplet'.",
          "is_bot": false,
          "headline": "fix(store): deep-copy InMemoryStore reads; add ConversationStore.close()",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T18:07:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c1b108f59cd68751dd710ed9fd9711af317170a3",
          "body": "New 'Dependency audit (core)' CI job audits the core runtime dependency set\n(pip-audit on the exported non-dev lockfile) — blocking, and stays green\nbecause core is just pydantic + transitives. Optional extras (which pull the\nheavier, occasionally-vulnerable trees) are kept current by a new\ndependabot.yml (uv + github-actions ecosystems, weekly).\n\nReview: high-risk 'dépendances vulnérables' + 'la CI ne fait que Bandit'.",
          "is_bot": false,
          "headline": "ci: add blocking pip-audit for core deps + Dependabot",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T18:07:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3cf562a0cd438c76805fd95caee60e05e8f2fd70",
          "body": "…audio decode\n\nSTT transcripts, TTS/AI responses and screen-agent typed text were logged in\nclear at INFO level, and server WebSocket transports base64-decoded inbound\naudio with no size bound. Content log sites now go through a central redact()\ngate (telemetry/redaction.py) that emits a length-only\n[…]\ntly enabled (ROOMKIT_LOG_CONTENT / set_content_logging)\nand drop to DEBUG level. Inbound audio frames are capped before decode via\nvoice/_limits.py (Twilio + realtime WS paths).\n\nReview: H4 (partial).",
          "is_bot": false,
          "headline": "fix(privacy): redact message content in logs by default; cap inbound …",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T17:45:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b4409c1782c65fbedf0d2887d5e43485d32e451a",
          "body": null,
          "is_bot": false,
          "headline": "chore: bump development version to 0.28.0.dev0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:46:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c1053d49dfaa53bd9da5ba3fef154ccd225b99ee",
          "body": "Three tool-authorization gaps: a failure building context for the\nBEFORE_TOOL_USE hook allowed the call by default (now denies, fail-closed);\nrealtime voice ran the tool handler before the blocking hook, so the side\neffect happened even when blocked (authorization now runs before the\nhandler, matchi\n[…]\ndency-free validate_tool_arguments\nenforces required fields and primitive types on both paths before\nexecution).\n\nReview: H1 (partial). Sandbox ToolPolicy exemption left as a separate\ndesign decision.",
          "is_bot": false,
          "headline": "fix(tools): fail closed on tool authorization; validate arguments",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:21:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e4c82523cc2415d17b8a06e65ea689e347b99c38",
          "body": "The whole locked pipeline (persist DELIVERED -> broadcast -> counters) was\nwrapped in one asyncio.wait_for, so a timeout during a slow broadcast left\nthe event stored as DELIVERED while the caller got blocked=process_timeout\nand the room counters were never updated. _process_locked now splits at the\n[…]\nroom-counter bump commit atomically via _commit_event, so the\ntimeline and counters can never diverge.\n\nReview: P0-3. Normative RFC updated in roomkit-specs (roomkit-rfc.md\nsections 10.1, 13.6, 14.3).",
          "is_bot": false,
          "headline": "fix(core)!: scope process_timeout to the pre-commit phase only",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:20:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9fbdda6d3e19659ed3001536a1a957c960a9163f",
          "body": "…Connection\n\nThe auth callback ran only when a WebSocket object was present, but WebRTC\nconnections arrive over an HTTP POST /webrtc/offer with no WebSocket, so\nthat path was never authenticated and an RTCPeerConnection was allocated\nfor any caller (auth bypass + DoS surface). A new shared helper,\nr\n[…]\nre delegation, and mount_fastrtc_voice/mount_fastrtc_av now require an\nexplicit allow_anonymous=True when no auth callback is supplied. Applied to\nboth the voice and audio+video mounts.\n\nReview: P0-2.",
          "is_bot": false,
          "headline": "fix(voice)!: authenticate WebRTC /webrtc/offer before creating a Peer…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:20:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7229449d560a6fec371a037bb7acf739ce4b159e",
          "body": "…migrate()\n\ninit() ran a SCHEMA that DROP...CASCADE'd every table when it detected a\nlegacy v1 (JSONB-blob) schema, so a routine connect after an upgrade could\nwipe rooms, events, participants and identities. init() now runs additive,\nidempotent DDL only and raises PostgresSchemaError when a v1 sche\n[…]\nation moves to an explicit, opt-in\nPostgresStore.migrate(dry_run=True, confirm=False), serialized by a\nPostgreSQL advisory lock.\n\nReview: P0-1. Docs updated in roomkit-docs (guides/postgres-store.md).",
          "is_bot": false,
          "headline": "fix(store)!: never DROP tables from PostgresStore.init(); add opt-in …",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:20:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "588bd3bb071d45f9138e6ffeb3573b4fb61f318c",
          "body": "Offset-based list_events / get_activity_timeline can now return the most\nrecent `limit` events (still ascending) instead of the room's opening\nevents — the right shape for a reconnect snapshot. Implemented across the\nConversationStore ABC, InMemoryStore, and PostgresStore.",
          "is_bot": false,
          "headline": "feat(store): add newest_first offset pagination to list_events",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T16:19:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cb28b6727de104d9a5354c3ec983d5532c80e586",
          "body": "Bump the PyPI Development Status classifier from '3 - Alpha' to\n'4 - Beta' to reflect that RoomKit is feature-complete and near\nproduction readiness.",
          "is_bot": false,
          "headline": "chore: promote development status to Beta (4)",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-11T13:38:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6f3f452f0dab9569e4a14ac13674d1a48ce5e60b",
          "body": "…stings\n\nThe hook-trigger count had drifted across docs (CLAUDE.md said 27, README\nsaid 35) while the HookTrigger enum has 65 members. Set both to 65. In\nAGENTS.md's quick-reference: add the membership triggers\n(ON_PARTICIPANT_JOINED/LEFT), list BEFORE_TOOL_USE / ON_USER_INPUT_REQUIRED,\ndrop the phantom ON_OBSERVATION (not a real trigger), and point to RFC §9.2\nas the authoritative complete set. Verified against len(HookTrigger) == 65.",
          "is_bot": false,
          "headline": "docs: correct hook-trigger count to 65 (was 27/35) and fix trigger li…",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-10T17:06:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c43e89b5bfa043df6681cba17d7414b3fce11ba3",
          "body": "The explicit-membership feature (0.26.0) shipped without an example.\nexamples/room_membership.py demonstrates add_member (idempotent join),\nlist_members/is_member, ON_PARTICIPANT_JOINED/LEFT hooks, remove_member\n(soft leave), and list_read_markers 'seen by' aggregation. Runs clean\nend-to-end; referenced by the Room Membership guide in roomkit-docs.",
          "is_bot": false,
          "headline": "docs(examples): add runnable room membership example",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-10T16:35:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6fff326f77d00a6b815ac769aad1f7bf5c83f2e2",
          "body": null,
          "is_bot": false,
          "headline": "Begin 0.27.0.dev0 development",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-10T16:23:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b58cc0cced132d63e4462ba60eaf717204adf41",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 0.26.0",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-10T16:23:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9e478c33df861662f7ff52d4fabd3878c5016361",
          "body": "The membership feature pushed room_lifecycle.py to 462 LOC of code, ~40%\nover the 300-line target, and mixed two responsibilities (room CRUD +\nparticipant lifecycle vs. explicit join/leave). Move add_member /\nremove_member / list_members / is_member into a dedicated MembershipMixin\n(HelpersMixin bas\n[…]\net_room resolved cross-mixin via the same\nannotation-stub pattern used elsewhere). room_lifecycle.py drops to 329\nLOC; no public API or behaviour change (methods still resolve on RoomKit\nvia the MRO).",
          "is_bot": false,
          "headline": "refactor(core): extract MembershipMixin from RoomLifecycleMixin",
          "author_name": "Sylvain Boily",
          "author_login": "sboily",
          "committed_at": "2026-07-10T16:12:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 86,
      "commits_last_year": 1212,
      "latest_release_at": "2026-07-24T05:15:44Z",
      "latest_release_tag": "v0.37.1",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 23,
      "days_since_latest_release": 0,
      "mean_days_between_releases": 1.2
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": true,
      "health_percentage": 87,
      "has_issue_template": false,
      "has_code_of_conduct": true,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "roomkit",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "ai",
            "async",
            "chat",
            "multi-channel",
            "rcs",
            "rooms",
            "sms",
            "voice",
            "whatsapp",
            "Development Status :: 4 - Beta",
            "Framework :: AsyncIO",
            "Intended Audience :: Developers",
            "License :: OSI Approved :: MIT License",
            "Programming Language :: Python :: 3",
            "Programming Language :: Python :: 3.12",
            "Programming Language :: Python :: 3.13",
            "Topic :: Communications",
            "Topic :: Communications :: Chat",
            "Typing :: Typed"
          ],
          "ecosystem": "pypi",
          "matches_repo": true,
          "registry_url": "https://pypi.org/project/roomkit/",
          "is_deprecated": false,
          "latest_version": "0.37.0",
          "repository_url": "https://github.com/roomkit-live/roomkit",
          "versions_count": 97,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 8369,
          "first_published_at": "2026-02-02T23:14:31.916695Z",
          "latest_published_at": "2026-07-24T04:53:54.053993Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 0
        }
      ]
    },
    "popularity": {
      "forks": 3,
      "stars": 30,
      "watchers": 0,
      "fork_history": {
        "days": [
          {
            "date": "2026-02-07",
            "count": 1
          },
          {
            "date": "2026-02-17",
            "count": 1
          },
          {
            "date": "2026-07-02",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 3,
        "total_forks": 3
      },
      "star_history": null,
      "open_issues_and_prs": 1
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": true,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [
        "Makefile"
      ],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "src/roomkit/py.typed"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 67638,
      "source_files_sampled": 1060,
      "oversized_source_files": 3,
      "agent_instruction_files": [
        "AGENTS.md",
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 34706
    },
    "dependencies": {
      "manifests": [
        "pyproject.toml"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "pypi"
      ],
      "dependencies": [
        {
          "name": "pydantic",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=2.9"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 1,
        "merged_prs": 8,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 4
      },
      "bus_factor": 1,
      "bot_contributors": 1,
      "top_contributors": [
        {
          "type": "User",
          "login": "sboily",
          "commits": 1221,
          "avatar_url": "https://avatars.githubusercontent.com/u/5470187?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.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": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "1 out of 1 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/29 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 3,
            "reason": "project has 1 contributing companies or organizations -- score normalized to 3",
            "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 0 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": null,
            "reason": "packaging workflow not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 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": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 0,
            "reason": "71 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "b458ed19d628afc812c986f7c3fe41d4cee8bee5",
        "ran_at": "2026-07-24T05:17:18Z",
        "aggregate_score": 4.3,
        "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-24T05:16:01Z",
      "oldest_open_prs": [
        {
          "number": 13,
          "created_at": "2026-07-24T05:16:54Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ],
      "last_merged_pr_at": "2026-07-24T04:32:38Z",
      "ci_last_conclusion": null,
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/roomkit-live/roomkit",
    "host": "github.com",
    "name": "roomkit",
    "owner": "roomkit-live"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 64,
      "inputs": {
        "security": 43,
        "vitality": 84,
        "community": 56,
        "governance": 47,
        "engineering": 88
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 84,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 80,
            "inputs": {
              "commits_last_year": 1212,
              "human_commit_share": 0.95,
              "days_since_last_push": 0,
              "active_weeks_last_year": 23
            },
            "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": "23/52 weeks with commits",
                "points": 15.9,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 23
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "1212 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 1212
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 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": 86,
              "latest_release_tag": "v0.37.1",
              "releases_from_tags": false,
              "days_since_latest_release": 0,
              "mean_days_between_releases": 1.2
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "86 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 86
                    }
                  }
                ],
                "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 ~1.2 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.2
                    }
                  }
                ],
                "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": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "moderate",
        "name": "Community & Adoption",
        "value": 56,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 26,
            "inputs": {
              "forks": 3,
              "stars": 30,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "30 stars",
                "points": 23.7,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 30
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "3 forks",
                "points": 2.5,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "excellent",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": true,
              "has_issue_template": false,
              "has_code_of_conduct": true,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 18,
                "status": "met",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 13.5,
                "status": "met",
                "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": 65,
            "inputs": {
              "packages": [
                "roomkit"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 8369
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "8,369 downloads/month across pypi",
                "points": 52.3,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 8369,
                      "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": "at_risk",
        "name": "Sustainability & Governance",
        "value": 47,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 13,
            "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 1 contributing companies or organizations -- score normalized to 3",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "at_risk",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 48,
            "inputs": {
              "merged_prs": 8,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 4
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "8/12 decided PRs merged",
                "points": 25.5,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 8,
                      "decided": 12
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/29 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 46,
            "inputs": {
              "followers": 10,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "roomkit-live",
              "public_repos": 9,
              "account_age_days": 168
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "organization-owned",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "owner_organization",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "10 followers of roomkit-live",
                "points": 7.5,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 10,
                      "login": "roomkit-live"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "9 public repos, account ~0 yr old",
                "points": 8.2,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 9
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 0
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "roomkit"
              ],
              "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": "97 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 97
                    }
                  }
                ],
                "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": 88,
        "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": "1 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "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": "1 out of 1 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "good",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 80,
            "inputs": {
              "topics": [],
              "has_wiki": false,
              "homepage": "https://www.roomkit.live",
              "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://www.roomkit.live",
                "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": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "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": "at_risk",
        "name": "Security",
        "value": 43,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Packaging. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "packaging"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 43,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 17,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 1,
              "scorecard_aggregate": 4.3
            },
            "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 not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "1 out of 1 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/29 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 0.8,
                "status": "partial",
                "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 0 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 not detected",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file 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": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "71 existing vulnerabilities detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "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": 3
            },
            "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": 80,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "has_llms_txt": true,
              "legible_history_share": 0.747,
              "agent_instruction_files": [
                "AGENTS.md",
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 34706
            },
            "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": "llms.txt present",
                "points": 15,
                "status": "met",
                "details": [
                  {
                    "code": "llms_txt_present",
                    "params": {}
                  }
                ],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "71 of 95 human commits state their intent (structured subject or explanatory body)",
                "points": 39.9,
                "status": "partial",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 71,
                      "sampled": 95
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "good",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 80,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "uv.lock"
              ],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [
                "Makefile"
              ],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [
                "src/roomkit/py.typed"
              ],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0.05
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "Makefile",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Makefile"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 11,
                "status": "met",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "src/roomkit/py.typed",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "src/roomkit/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": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "5 of the last 100 commits are automated dependency updates",
                "points": 8,
                "status": "met",
                "details": [
                  {
                    "code": "dependency_bot_commits",
                    "params": {
                      "count": 5,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "good",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 82,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 67638,
              "source_files_sampled": 1060,
              "oversized_source_files": 3
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python with type-check config (src/roomkit/py.typed)",
                "points": 27,
                "status": "partial",
                "details": [
                  {
                    "code": "typecheck_config_language",
                    "params": {
                      "files": "src/roomkit/py.typed",
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "3/1060 source files over 60KB",
                "points": 54.8,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 1060,
                      "oversized": 3
                    }
                  }
                ],
                "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",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
    "deps.dev does not index pypi:roomkit@0.37.0; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-24T05:17:25.080458Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/r/roomkit-live/roomkit.svg",
  "full_name": "roomkit-live/roomkit",
  "license_state": "standard",
  "license_spdx": "MIT"
}

Scores are signals, not warranties. They reflect publicly visible practices on GitHub — not a code audit, and not a security guarantee.

Missing data is excluded and weights renormalized, never scored as zero. Methodology is versioned and open: metrics v1.13.0, schema v0.27.0 — full methodology · metrics wiki.

How one result sits in the wider record: aggregate statisticsPyPI.