公开记录
软件健康报告模式 0.27.0 · 指标 1.13.0 · 2026-07-24 21:36 UTC

iRootPro / lofi-player

A small TUI for lofi internet radio. Streams, YouTube live, ambient sounds

GoMIT★ 1 星标⑂ 0 复刻始于 2026年4月在 GitHub 上查看 ↗

iRootPro/lofi-player 的健康指数为 100 分中的 42 分,处于「存在风险」区间。 其得分最高的类别是Vitality(60/100),最低的是Community & Adoption(24/100)。 最近一次更新在 36 天前。 近期的大部分工作由 1 位贡献者完成。

42
总分 / 100
存在风险

软件健康指数

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

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

评分画像

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

所有权

iRootPro个人账户
0 关注者81 个公开仓库始于 2016年5月

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

软件包生态系统

注册表软件包版本月下载量版本数最近发布
Gogithub.com/iRootPro/lofi-playerv0.1.12-1336 天前

按类别列示的指标

活力

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

60中等 · 占总体的 22%

开发活跃度

40存在风险
评分方式
18/36推送新近度 — 最近一次推送于 36 天前
3.5/36提交节奏 — 52 周中有 5 周有提交
18/18提交量 — 最近一年 101 次提交
0/10OpenSSF Scorecard:Maintained — project was created within the last 90 days. Please review its contents carefully
所用输入
commits_last_year101
human_commit_share1
days_since_last_push36
active_weeks_last_year5

发布纪律

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

社区与采用

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

24危急 · 占总体的 18%
评分方式
0/60星标 — 1 个星标
0/25复刻 — 0 个复刻
0/15关注者 — 0 位关注者
所用输入
forks0
stars1
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

50中等
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(MIT)
0/18CONTRIBUTING 指南
0/13.5行为准则
0/7.2议题模板
0/6.3PR 模板
所用输入
has_readme
has_license
has_contributing
has_issue_template
has_code_of_conduct
has_pull_request_template

可持续性与治理

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

34存在风险 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0/22.5提交分布 — 头号贡献者编写了 100% 的提交
1.4/13.5贡献者广度 — 1 位贡献者
0/10OpenSSF Scorecard:Contributors — project has 0 contributing companies or organizations -- score normalized to 0
所用输入
bus_factor1
contributors_sampled1
top_contributor_share1
评分方式
0/46.8议题解决 — 没有议题或无数据
0/38.3PR 接受 — 没有已裁定的拉取请求或无数据
0/15OpenSSF Scorecard:Code-Review — Found 0/30 approved changesets -- score normalized to 0
所用输入
merged_prs0
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs0
已排除计分(无数据或不适用):议题解决, PR 接受。 其余权重已重新归一化。
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
0/25所有者影响力 — iRootPro 有 0 位关注者
25/25既往记录 — 81 个公开仓库,账户约 10 年
所用输入
followers0
owner_typeUser
is_verified
owner_loginiRootPro
public_repos81
account_age_days3,734
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — go 上有 1 个软件包
35/35发布时效 — 最近一次发布于 36 天前
20/20版本历史 — 13 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesgithub.com/iRootPro/lofi-player
ecosystemsgo
any_deprecated
min_days_since_publish36

工程质量

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

56中等 · 占总体的 20%

工程实践

60中等
评分方式
24/24CI 工作流 — 1 个工作流
24/24存在测试
0/16Linter 配置
0/9.6Pre-commit 钩子
0/6.4.editorconfig
0/20OpenSSF Scorecard:CI-Tests — 无数据
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config
已排除计分(无数据或不适用):OpenSSF Scorecard:CI-Tests。 其余权重已重新归一化。

文档

50中等
评分方式
30/30README
0/25文档目录
0/15文档 / 主页站点
10/10仓库描述
0/10主题标签
10/10Wiki
所用输入
topics
has_wiki
homepage
has_readme
has_docs_dir
has_description

安全

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

34存在风险 · 占总体的 16%

安全态势

34存在风险
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — 无数据
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
0/5Packaging — 无数据
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — no SAST tool detected
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
7.5/7.5Token-Permissions — GitHub workflow tokens follow principle of least privilege
6/7.5Vulnerabilities — 2 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate3.4
已排除计分(无数据或不适用):ci_tests, packaging。 其余权重已重新归一化。

AI 就绪度

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

58中等 · 占总体的 0%
评分方式
0/45代理指令 — 没有 CLAUDE.md / AGENTS.md / 编辑器规则
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 97 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.97
agent_instruction_files
agent_instruction_max_bytes
评分方式
12.6/18一条命令的引导启动 — go.mod(工具链约定,无任务运行器)
22/22自动化测试
0/11Lint / 格式化配置
11/11静态类型检查 — Go(静态类型)
10/10可复现环境 — lockfile
0/10已体现的代理实践 — 最近 100 次提交中没有代理编写的提交
0/8自动化维护 — 未观察到自动依赖更新
0/10OpenSSF Scorecard:Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
所用输入
has_nix
has_tests
lockfilesgo.sum
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0
toolchain_manifestsgo.mod
dependency_bot_commit_share0
评分方式
45/45可类型检查的代码 — Go(静态类型)
55/55可控的文件大小 — 采样的 37 个源文件中有 0 个超过 60KB
所用输入
primary_languageGo
largest_source_bytes34,574
source_files_sampled37
oversized_source_files0

关键数据

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

数据采集警告

  • 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

更多细节

OpenSSF Scorecard 3.4 / 10
3.4综合

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

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
不适用CI-Testsno pull request found
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
不适用Packagingpackaging workflow not detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTno SAST tool detected
0Security-Policysecurity policy file not detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
10Token-PermissionsGitHub workflow tokens follow principle of least privilege
8Vulnerabilities2 existing vulnerabilities detected
直接依赖 5
注册表软件包版本约束清单文件
Gogithub.com/atotto/clipboardv0.1.4go.mod
Gogithub.com/charmbracelet/bubblesv1.0.0go.mod
Gogithub.com/charmbracelet/bubbleteav1.3.10go.mod
Gogithub.com/charmbracelet/lipglossv1.1.0go.mod
Gogopkg.in/yaml.v3v3.0.1go.mod
全部依赖 未采集

本报告未能采集到解析后的依赖集合:GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 14018,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "Go": 251223,
        "HTML": 10875,
        "Shell": 3111,
        "Python": 4709
      },
      "pushed_at": "2026-06-18T09:43:03Z",
      "created_at": "2026-04-27T17:48:55Z",
      "owner_type": "User",
      "updated_at": "2026-06-18T09:42:58Z",
      "description": "A small TUI for lofi internet radio. Streams, YouTube live, ambient sounds",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "Go",
      "significant_languages": [
        "Go"
      ]
    },
    "owner": {
      "blog": null,
      "name": null,
      "type": "User",
      "login": "iRootPro",
      "company": null,
      "location": null,
      "followers": 0,
      "avatar_url": "https://avatars.githubusercontent.com/u/19166457?v=4",
      "created_at": "2016-05-03T09:54:29Z",
      "is_verified": null,
      "public_repos": 81,
      "account_age_days": 3734
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.1.12",
          "kind": "patch",
          "published_at": "2026-06-18T09:43:49Z"
        },
        {
          "tag": "v0.1.11",
          "kind": "patch",
          "published_at": "2026-05-20T11:09:41Z"
        },
        {
          "tag": "v0.1.10",
          "kind": "patch",
          "published_at": "2026-05-20T10:09:55Z"
        },
        {
          "tag": "v0.1.9",
          "kind": "patch",
          "published_at": "2026-05-15T06:08:59Z"
        },
        {
          "tag": "v0.1.8",
          "kind": "patch",
          "published_at": "2026-05-13T11:14:42Z"
        },
        {
          "tag": "v0.1.7",
          "kind": "patch",
          "published_at": "2026-05-13T11:00:27Z"
        },
        {
          "tag": "v0.1.6",
          "kind": "patch",
          "published_at": "2026-05-13T10:02:43Z"
        },
        {
          "tag": "v0.1.5",
          "kind": "patch",
          "published_at": "2026-05-13T08:36:08Z"
        },
        {
          "tag": "v0.1.4",
          "kind": "patch",
          "published_at": "2026-05-12T13:16:38Z"
        },
        {
          "tag": "v0.1.3",
          "kind": "patch",
          "published_at": "2026-05-03T19:00:56Z"
        },
        {
          "tag": "v0.1.2",
          "kind": "patch",
          "published_at": "2026-05-02T19:23:30Z"
        },
        {
          "tag": "v0.1.1",
          "kind": "patch",
          "published_at": "2026-04-27T18:13:01Z"
        },
        {
          "tag": "v0.1.0",
          "kind": "minor",
          "published_at": "2026-04-27T17:53:54Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "9a340a77e7668a24327a26d62771287328ed94ac",
          "body": null,
          "is_bot": false,
          "headline": "fix: persist theme selection immediately",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-06-18T09:42:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7f80415bf39267f0b1a96495f99720361c4086df",
          "body": null,
          "is_bot": false,
          "headline": "fix: cap initial buffer for live radio",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-20T11:09:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "956fb3d3c7c32c59e619abf2f66e1746e90c3c22",
          "body": null,
          "is_bot": false,
          "headline": "feat: add network buffer settings",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-20T10:09:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2df9f8443747e1ebe9dfcb7e6e855ce8df11e913",
          "body": null,
          "is_bot": false,
          "headline": "fix: reconnect stalled radio streams",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-15T06:07:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4ce486b4f1f6f10b0f6fed5d1a98ece2ba0dd20a",
          "body": null,
          "is_bot": false,
          "headline": "feat: add light themes",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-13T11:14:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "da085b7e023a027f8fc2632ee0d6e7ebcc1bc5f7",
          "body": null,
          "is_bot": false,
          "headline": "feat: add theme picker",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-13T10:59:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9813ed6b6c31407eb55e3ad1a0deefec62574c96",
          "body": null,
          "is_bot": false,
          "headline": "feat: add station sharing",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-13T10:01:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "73dce8a64bd8dd1b4148a7a86aa6d5508b3c5953",
          "body": null,
          "is_bot": false,
          "headline": "audio: support macOS play pause media key",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-13T08:35:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c76ce91cde65d6bc6de28ce78db786292c36515d",
          "body": null,
          "is_bot": false,
          "headline": "fix: isolate mpv startup from user config",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-12T12:09:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "62692c179cddb7485a6beda2ee1f020be66a60af",
          "body": null,
          "is_bot": false,
          "headline": "Update README demo GIF",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-04T11:22:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "55d8a0820d7de6edccd84232f227a288b54bf45f",
          "body": null,
          "is_bot": false,
          "headline": "docs: add PNG demo preview",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-03T19:16:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ded0f0d33ed20e64731350a6a28ce7b9e9f3dc72",
          "body": null,
          "is_bot": false,
          "headline": "fix: keep mpv IPC responsive under event bursts",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-03T18:59:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6ff0f7f31cc4432ce83fb4afcf2e76f276403c52",
          "body": null,
          "is_bot": false,
          "headline": "docs: update pinned install version",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-02T19:22:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "770d734aab03838c3e192d81a1dab7168112ad19",
          "body": null,
          "is_bot": false,
          "headline": "fix: polish TUI layout and modal overlays",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-05-02T19:21:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d6161dd8a0db0b5f4b6accf52fcf37c344e01d75",
          "body": null,
          "is_bot": false,
          "headline": "docs: refresh demo gif",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T19:26:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0ec712733b0acdd1f984e9661565ffc9a5aa67b4",
          "body": "Reorders the install section so Homebrew (which auto-installs mpv\nand yt-dlp) is clearly the path of least resistance. Adds a note to\nthe one-line installer pointing at the runtime-deps table since\nnon-brew installs need mpv installed separately. Lifts the supported\nplatforms line to the top of the section.",
          "is_bot": false,
          "headline": "docs: highlight Homebrew as the recommended install path",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T18:17:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8bd081d803f39b1eec320fcb114376ac41aa33c2",
          "body": null,
          "is_bot": false,
          "headline": "chore: ignore local /sounds/ scratch dir",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T18:12:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ed55394d704d52493ff3e892f60dff86e1be0a52",
          "body": "Releases are now cut locally with `goreleaser release --clean` rather\nthan from a CI workflow, mirroring the iRootPro/rdr setup. Goreleaser\nuploads binaries to GitHub Releases and pushes the formula to\niRootPro/homebrew-tap in one shot, using the local gh-cli token —\nno PAT secret needed in the repo\n[…]\nl/bin\n  (override with INSTALL_DIR=...). Hints when mpv is missing.\n- README.md / README.ru.md: install section adds Homebrew tap and\n  one-line installer paths; release notes describe the local flow.",
          "is_bot": false,
          "headline": "chore(release): switch to local release flow with Homebrew tap",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T18:10:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9764af8574cd6d05f7097032c13964d0f9258b4d",
          "body": "- README.md: language switcher in the header pointing at README.ru.md;\n  Features list mentions the friendly-first-run behavior; runtime\n  dependencies section now distinguishes the mpv hard-fail with the\n  styled card from the yt-dlp soft warning that keeps the rest of\n  the app playable.\n- README.ru.md: full Russian translation of the README, same\n  structure and links so the cross-references stay symmetric.",
          "is_bot": false,
          "headline": "docs: add Russian README and document graceful dependency handling",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T17:47:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "53173c5b348a40d2f26d80e2c88c2ee297238809",
          "body": "mpv missing on $PATH no longer crashes with a raw error. Instead a\nstyled `lofi.player can't start` card is rendered to stderr with\nplatform-specific install commands (brew / apt / pacman / dnf), and\nthe process exits 1.\n\nyt-dlp missing degrades to a soft warning rather than refusing to\nstart when t\n[…]\nause replaces the playCmd with an\n  error toast that names the missing tool.\n- The station list paints YouTube rows in muted hint color and\n  appends an `unavailable` badge so the user sees the cause.",
          "is_bot": false,
          "headline": "feat: friendly handling of missing runtime dependencies",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T17:46:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f410efa5076ef3612edfb75f8875c4d0d91e6450",
          "body": "Rewrites the README around the public-release shape:\n\n- Badges (CI, latest release, Go version, MIT)\n- Centered demo GIF (demo/lofi-player.gif from the vhs tape)\n- Features bullet list (radio, YouTube, ambient mixer, themes,\n  mini mode, statusline, persistence, edit/add/delete stations,\n  stream in\n[…]\nnd auto-detect note\n- Themes / mini mode / tmux statusline sections\n- Credits for CC0 ambient samples (rain, fire, white noise, cafe,\n  thunder) with original freesound.org links\n- MIT license callout",
          "is_bot": false,
          "headline": "docs: rewrite README with badges, demo, full keybindings",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T12:01:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5318e95fc81a0ebf4a88d7c7ce27d22f340e4751",
          "body": "UX polish:\n- Animation tick chains hoisted above modal dispatch so the logo\n  shimmer and live-pulse keep ticking when mixer/add-station modals\n  are open (the modal updaters only know about KeyMsg and were\n  silently swallowing tea.Tick chains, killing them forever).\n- Full-help card switched from \n[…]\nens a small `delete <name>?` confirmation modal centered in\n  the frame; `y/enter` commits, `n/esc` cancels. Deleting the\n  currently-playing station pauses playback and clears the\n  now-playing card.",
          "is_bot": false,
          "headline": "feat(tui): UX polish, stream info, edit/delete stations",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T12:01:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "95e7d8408caa7d24f3dcc7782522957c2bdcce1e",
          "body": "Adds the bits needed to publish the project to GitHub:\n\n- LICENSE (MIT, 2026 Alexander Neupokoev)\n- .goreleaser.yaml — builds linux/{amd64,arm64} + darwin/{amd64,arm64},\n  archives include README and LICENSE, checksums, ldflags inject\n  main.version\n- .github/workflows/ci.yml — vet + test + build on\n[…]\ne — /dist/ for goreleaser output\n- main.go — `--version`/`-v` flag, ldflags-overridable `version`\n  variable, ShowStreamInfo wiring through state\n- demo/ — vhs tape and pre-rendered GIF for the README",
          "is_bot": false,
          "headline": "chore(release): packaging artifacts and version flag",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-27T12:00:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cc8d1c398625c02b0f4efc99b9a4e497fc41c67d",
          "body": "Two new CC0 ambient channels appended to the registry, both sourced\nfrom freesound.org and processed via ffmpeg into 4-minute Opus loops:\n  - cafe.opus    ← freesound 32910 by ToddBradley (CC0)\n  - thunder.opus ← freesound 717890 by TRP (CC0)\n\nTotal ambient set: rain, fire, white_noise, cafe, thunde\n[…]\nover the expanded 5-channel registry — channel order\nwalk-through, label/icon metadata, and indicator rendering all\nparametrized off ChannelIDs() rather than hard-coded against the\noriginal 3 entries.",
          "is_bot": false,
          "headline": "Add cafe and thunder ambient channels; restyle mixer icons",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T18:41:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "316f5be4f4cdec7b75b84f42a7f13747ebc5b071",
          "body": "Adds the shimmering \"lofi\" ASCII logo on the right side of the\nmain view, vertically centered against the body height. Resolved\ntrivial conflicts where ambient-mixer and the logo each appended\nnew tick messages, tea.Cmd factories, and view helpers — kept\nboth. Bumped logo_test.go's NewModel call to match the new mixer\nparameter introduced on main.",
          "is_bot": false,
          "headline": "Merge branch 'feat/equalizer'",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T18:18:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "043e1b784dd2ffb6327de87a5a0c571d17bb01ee",
          "body": "Trim the spacer between the ♪ prefix and the L from 2 cells to 1\nso the music note reads as part of the word rather than floating\nbeside it. Swap the i's middle-dot (•) for a black-circle (●) so\nthe i carries comparable visual weight to the heavy-stroked L, O,\nand F instead of trailing off into a pin-prick.",
          "is_bot": false,
          "headline": "TUI: tighten logo prefix gap and beef up the i's dot",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T18:10:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "629b6aa0ee69d043db644d6ec693ebf08e206041",
          "body": "Move the shimmering logo from the top of the right column to its\nvertical middle. JoinHorizontal with lipgloss.Center pads the logo\nabove and below to match the full body height (now-playing card +\nstations list), so the right side reads as balanced rather than\ntop-heavy with empty space below the original 3-row logo strip.",
          "is_bot": false,
          "headline": "TUI: vertically center logo against body height",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T18:07:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f205a09b61ac6d4d0bb10e69c146e915803bf10",
          "body": "Swap the light box-drawing glyphs (│─╭╮╰╯├) for their heavy\ncounterparts (┃━┏┓┗┛┣) so the letters carry more visual weight and\nthe shimmer wave reads more strongly across them. Bump the i's dot\nfrom · (middle dot) to • (bullet) to match the heavier strokes.",
          "is_bot": false,
          "headline": "TUI: thicken logo strokes with heavy box-drawing",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T18:03:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1a86fd53eb134fbacbca58cfab7b3a2a825bc9b9",
          "body": "Dot the \"i\" with a middle-dot glyph so the rightmost letter reads\nas a real lowercase \"i\" rather than a bare divider. Echo the music\nnote from the frame's top-border title on the middle row of the\nlogo, so the brand element lives in both the frame chrome and the\nlarger panel — same glyph, two scales.",
          "is_bot": false,
          "headline": "TUI: refine logo with dot-i and ♪ prefix",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T17:57:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "29ae60ffe5b8486772a026ba959cb271a6cabdbb",
          "body": "The vertical-bar equalizer felt too \"club\" for a lofi player; swap\nit for a small static \"lofi\" logo built from rounded box-drawing\ncharacters. A soft shimmer crest sweeps across the logo at 150 ms\nper cell, brightening cells from Muted → Secondary → Primary as the\nwave passes, with a brief dim \"res\n[…]\nanimation reads as a calm breath rather than a continuous spinner.\n\nShimmer freezes on pause and the logo collapses entirely when no\nstation is selected, matching the previous equalizer's state rules.",
          "is_bot": false,
          "headline": "TUI: replace equalizer with shimmering ASCII logo",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T17:47:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a9b7043ff40ce1518566481d00c34914d478cd6d",
          "body": "Adds the ambient mixer feature (Phase 4c from plans/lofi-player-plan.md):\n3-channel mixer (rain/fire/white_noise) layered over the main station,\nopened with 'x', state persisted via debounced save, embedded CC0 loops\nfrom freesound.org. See plans/2026-04-25-ambient-mixer-design.md for the\ndesign and plans/2026-04-25-ambient-mixer-plan.md for the implementation\nplan.",
          "is_bot": false,
          "headline": "Merge branch 'ambient-mixer'",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T17:44:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "10104624fbcf9f20b1730151331a8ea73399f55a",
          "body": "- rain.opus  ← freesound 525046 by speakwithanimals (CC0)\n- fire.opus  ← freesound 760474 by True_Killian (CC0)\n- white_noise.opus ← freesound 132275 by assett1 (CC0)\n\nAll three are public-domain field recordings. Encoded to Opus 64 kbps\nstereo with 2/3-second fade in/out and EBU R128 loudness norma\n[…]\nization\nto -23 LUFS. README documents the regen recipe so future contributors\ncan reproduce or swap the embeds without spelunking git history.\n\nTotal embed footprint ~4.3 MB (was ~4.2 MB synthesized).",
          "is_bot": false,
          "headline": "audio: replace synthesized loops with CC0 freesound recordings",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T17:42:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3e790501edf5e6f26d17b6942780892e8f25560a",
          "body": "The previous placeholders were 1-3 seconds of literal silence — fine for\nunit tests, useless for verifying the audio pipeline by ear. Swap them\nfor 4-minute Opus loops generated via ffmpeg's anoisesrc:\n\n- rain.opus  — pink noise filtered (highpass 500 / lowpass 7000) with\n  light tremolo to feel lik\n[…]\nd recordings — fine for\nshipping a first release (similar to what mynoise.net and Brain.fm\nship), with real CC-BY recordings as a follow-up before v1.0. Recipe\nand licensing live in the assets README.",
          "is_bot": false,
          "headline": "audio: replace silent placeholders with synthesized noise textures",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-26T17:24:09Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "79731cbaae5d5fc7171fca2efc8652015921a67b",
          "body": "- Init the mixer at startup (non-fatal on failure: the modal still\n  opens, channels show as 'unavailable').\n- Restore last-session ambient volumes from state.json before the\n  Bubble Tea program starts so playback resumes immediately.\n- Plumb a SaveAmbient closure that load-modify-saves the state file\n  on each debounced tick. Errors flow back to the model as toasts.\n- On clean shutdown, snapshot Volumes() into the final state.Save.",
          "is_bot": false,
          "headline": "main: wire ambient mixer init, restore, and persistence",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T20:05:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "11bafeaecd54d0f0bd6b1fe72c374f1791e4f517",
          "body": "SaveAmbient callback returns error now; the debounce handler renders an\nerror toast on failure, matching how config.Save errors flow through\nupdateAddStation. Tighten the nil-callback test (toast remains nil, seq\nunchanged) and add a wiring assertion that mixer keys return a non-nil\ntea.Cmd.",
          "is_bot": false,
          "headline": "tui: surface ambient save errors as toast (matches addform pattern)",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T20:05:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "36a388d9797a462fb74d02bc7af706fc3662c1ef",
          "body": "Sixteen vertical bars sit right of the now-playing block, animated\non a 120 ms tick via per-bar sin-driven phases. Bars freeze on pause,\nquiet to a small ripple while loading, and disappear when no station\nis selected or when the terminal is too narrow. The animation has no\naudio data behind it — the mpv JSON-IPC backend never surfaces PCM —\nso phases are seeded deterministically and snapshot tests are stable.",
          "is_bot": false,
          "headline": "TUI: animate decorative equalizer next to now-playing card",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T20:02:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "11f6689c402e66eaf4fcb8af5d961e96c6752b69",
          "body": null,
          "is_bot": false,
          "headline": "tui: debounce ambient state save by 500ms",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T20:01:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6d4e49190d1939ee46697af39c89fa9d66740307",
          "body": "Adds a compact 'mid-dot + icons' indicator to the station line in\nrenderNowPlaying. Pulls active channel ids from the audio mixer in\ncanonical order; hides entirely when nothing is active.",
          "is_bot": false,
          "headline": "tui: show active ambient channels next to station",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:57:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fd734c7ed39b7bf1c1da211f5e4c800d0045f062",
          "body": null,
          "is_bot": false,
          "headline": "tui: regression test for inert global keys in mixer modal",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:56:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "566936b46f6791416cdd2d578355dbfc3f976f63",
          "body": "Centered rounded card with 3 channel rows (icon, label, bar, value),\ntitle and hint bar. Selected row highlighted; muted (volume==0) rows\ndim; disabled rows show 'unavailable'.",
          "is_bot": false,
          "headline": "tui: render ambient mixer modal card",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:55:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "14a1f92c2e9ccc98a80ab1bf5502afef9a309def",
          "body": "Wire the modal's nav and volume keys: j/k pick a channel, h/l adjust by\n±5, H/L by ±25, 0 mutes the active channel, 1 maxes it. Volume changes\ngo straight to the audio mixer's SetVolume; the modal model just tracks\nwhich row is selected.\n\nAdds audio.SetCacheDirForTest so cross-package tests can redirect the\nambient cache root without depending on $XDG_CACHE_HOME (which is\nignored by os.UserCacheDir on darwin).",
          "is_bot": false,
          "headline": "tui: ambient mixer keybindings (jk/hl/HL/0/1)",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:54:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ea70f41d6acd8a8fb591da9a936c131a340cfdd4",
          "body": null,
          "is_bot": false,
          "headline": "tui: introduce mixerModel.Selected for modal state",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:53:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "951267d23994d9959ba10d2555196a6d12fde48d",
          "body": "- Adds modeMixer to viewMode plus a mixer field on Model and a\n  mixerModel struct in mixer.go (selection state only; volumes\n  live on audio.AmbientMixer).\n- NewModel now takes *audio.AmbientMixer; main.go constructs one\n  via audio.NewAmbientMixer (Init/Close wired up in Task 15).\n- Update routes input to updateMixer while modeMixer is active;\n  for now it only handles esc/x to close, plus q/ctrl+c. Full\n  navigation/volume keys land in Task 10.",
          "is_bot": false,
          "headline": "tui: add modeMixer with open/close transitions",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:52:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8dd6047b8e2ce56639e5f557e0888dace96b8a9a",
          "body": null,
          "is_bot": false,
          "headline": "tui: bind 'x' to open ambient mixer",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:49:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6946f08f5084328faa04ca313695954849cab546",
          "body": null,
          "is_bot": false,
          "headline": "state: persist ambient channel volumes",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:48:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f71b2172b8cd268cbd805acc639389c43c022439",
          "body": null,
          "is_bot": false,
          "headline": "audio: ambient mixer Volumes and ActiveIDs snapshots",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:48:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6313665eecc4235a6d4faad59747dc7d3df9a75e",
          "body": null,
          "is_bot": false,
          "headline": "audio: ambient mixer set-volume with pause-on-zero",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:45:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b863ecf58c83f81f3ab18cc2e939cd1c14300e22",
          "body": null,
          "is_bot": false,
          "headline": "audio: close orphaned mpv on Init failure; document placeholder regen",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:43:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8ebff4b04f19e14fe30b9be5548a24120a569db8",
          "body": "scripts/fetch-radiopotok.py scrapes chillout/jazz/classical/trance\ncategories from radiopotok.ru, deduplicates by URL+name, prefers\nmp3 > aac > other > m3u8, and emits configs/radiopotok.yaml.\nYouTube-only stations are converted to youtube-kind entries.\n\nOutput schema matches internal/config.Station so users can copy\nentries straight into ~/.config/lofi-player/config.yaml. Defaults\nin code are unchanged.",
          "is_bot": false,
          "headline": "configs: bundle ~200-station radiopotok.ru preset",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:42:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b62baded926d4c9369669be2d53ea05fb73ee869",
          "body": "Adds ambientPlayer wrapping per-channel mpv subprocess (loop=inf, idle=no,\nvolume=0, pause=yes). Init spawns one per channel; failure marks the\nchannel disabled and continues. Close terminates all subprocesses in\nparallel.\n\nTwo pragmatic fixes alongside the new code:\n- ambient_player cleanup no long\n[…]\ne text placeholders in ambient_assets/ with valid\n  silent Opus loops (1/2/3-second durations for distinct SHA-256). mpv\n  can decode these, so spawn tests don't depend on its decode-failure\n  timing.",
          "is_bot": false,
          "headline": "audio: spawn mpv per ambient channel on init",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:39:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "05dc2125c00e55486076ba9cb359c8640ba26100",
          "body": null,
          "is_bot": false,
          "headline": "audio: fsync materialize and wrap error contexts",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:24:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8cdf6e7ebf33aa4f9fe57de41f15a1f179b1a6a2",
          "body": null,
          "is_bot": false,
          "headline": "audio: embed ambient loop files and extract on init",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:19:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9a43b86bea1a95d5a967be92e4892e638ac9945c",
          "body": null,
          "is_bot": false,
          "headline": "audio: scaffold ambient channel registry",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:14:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1435e270f6a8bafb1443cff1cce14f3aaa395bae",
          "body": null,
          "is_bot": false,
          "headline": "plans: add ambient mixer implementation plan",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:10:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "410b3125ee214dde3acc0eb4ca4b2c8b1984014e",
          "body": null,
          "is_bot": false,
          "headline": "gitignore: add .worktrees/ for isolated feature workspaces",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:03:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "27e0a27e3f3133bba975c307187fcba1c6fd56ce",
          "body": null,
          "is_bot": false,
          "headline": "plans: add ambient mixer v1 design",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T19:00:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "348859348cb1fa307728576fa3848136b548f303",
          "body": "Two cells of whitespace between an icon and its label looked like a\ngap; one cell reads as a properly-paired tag. Apply to:\n\n  - title bar logo: \"♪  lofi.player\" → \"♪ lofi.player\"\n  - stations section header: \"  stations\" → \" stations\"\n\nVisually balances against the volume widget in the title bar (which\nalready used a single space) and tightens the leading icons against\ntheir text without losing breathing room.",
          "is_bot": false,
          "headline": "TUI: tighten icon-to-label spacing in logo and stations header",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:38:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "63eb623dd5e2ff43ffe4abe6906755180409f826",
          "body": "The previous Warning-yellow \"?\" stood out as a stranger in the\notherwise muted bottom border. Use AppTitle (Primary purple)\ninstead, matching the logo glyph at top-left and the volume icon\nat top-right. The three \"interactive entry points\" are now\nvisually consistent — same color says \"these belong together\".\n\n\"help\" word stays in HelpDesc muted so the hint as a whole still\nreads quietly against the border.",
          "is_bot": false,
          "headline": "TUI: paint \"?\" hint in the brand Primary color",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:35:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e6d5eda58aed4c2f8318afb24da9174d7f075105",
          "body": "The persistent row of bindings under the stations list was visual\nchatter for users who already know the keys. Replace it with a\nsingle muted \"? help\" hint embedded in the bottom border (mirroring\nthe volume widget that lives in the top border):\n\n  ╭─ ♪ lofi.player ─────────  󰕿 ▰▰▰▰▰▰▱▱▱▱▱▱▱▱ ─╮\n  │\n[…]\no — same logic\n  - Orphaned helpers gone: KeyMap.ShortHelp, KeyMap.MiniShortHelp,\n    Model.renderShortHelp, package-level renderBindings\n  - bubbles/key import removed from view.go (no longer needed)",
          "is_bot": false,
          "headline": "TUI: drop the inline help bar, embed \"? help\" in the bottom border",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:33:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e501b685fb5ab09199d570fb5067fd31f741bd2",
          "body": "Three follow-up tweaks on the volume widget in the frame's title\nbar:\n\n  - VolLabel switches from Muted to Primary so the speaker icon\n    matches the fill cells in color. Icon + bar now read as one\n    unified \"volume control\" rather than icon (decorative) + bar\n    (the actual indicator).\n  - The \n[…]\nighter in the title bar\n    without the components feeling cramped.\n\nVolPercent style stays — it's still used by the --statusline CLI\nmode where the bar isn't shown and the digit is the entire signal.",
          "is_bot": false,
          "headline": "TUI: tighten volume widget — purple icon, no percent, less padding",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:27:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6a077f86ee87bb5a144b33b343392b5a5349f0ce",
          "body": null,
          "is_bot": false,
          "headline": "TUI: drop Bold on volume icon — pointless on the MD glyph",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:24:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3fa77fbe158cffdac713b583d1a32ae62565e0f3",
          "body": "Tried the FA volume-up (small-but-with-waves) and Bold-FA variants\nsince the previous MD switch — neither gave the visual weight the\ntitle bar needed. Settle on MD volume_high (U+F057F, \"󰕿\"): heavier\nsilhouette, quiet muted gray, sits at proper size in the title\nbar. The lack of sound-wave arcs is a known trade-off the user\naccepts for the visual presence.\n\nBold on the volume label is dropped — pointless on the MD glyph\nwhich renders identically.",
          "is_bot": false,
          "headline": "TUI: switch volume icon back to MD volume_high silhouette",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:24:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b56ee24bf67f4522dea5326916d71546138c6487",
          "body": "The FA volume-up glyph reads as light/thin in the title bar. Apply\nBold to the icon style so terminals with a heavier weight in their\nNerd Font show a thicker speaker; on fonts without that variant the\nfallback is identical to before, no layout break.",
          "is_bot": false,
          "headline": "TUI: bold the volume icon for a heavier glyph render",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:22:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "21051d20c5914d5219acf7e6b2f7f2642aefe795",
          "body": "The Unicode 🔊 emoji ended up reading as a color 3D button that broke\nthe flat muted style and sat taller than a single text row. Drop the\nemoji and go back to FA volume-up (U+F028, \"\"). It's small, but\nin-style with the rest of the Nerd Font icons (logo, stations) and\nsits cleanly inline on the title bar.",
          "is_bot": false,
          "headline": "TUI: revert volume icon to plain FA volume-up",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:21:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9dcc711ede6bd40495fe1d00d8969da3adb4dd48",
          "body": "Both Nerd Font candidates were either too small (FA volume_up ),\nunrecognized on the user's font (FA broadcast-tower , rendered as\n\"≡↑\"), or sound-wave-less (MD volume_high 󰕿). Drop the Nerd Font\ndependency for this glyph and use 🔊 (U+1F50A SPEAKER WITH THREE\nSOUND WAVES), the standard Unicode emoji\n[…]\no font-dependent fallback. The\nnative emoji color may override the Muted style we apply, which is\nfine for this use: the speaker is meant to read as a recognizable\nicon, not as a quiet section marker.",
          "is_bot": false,
          "headline": "TUI: use the unicode speaker emoji for the volume icon",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:18:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f78375e86e549c8f29fba51b5354cd35540a19e1",
          "body": "FA volume_up () was visually too small for the title bar — it\nread as an afterthought next to the bar. FA broadcast-tower ()\nis heavier in most Nerd Fonts and clearly signals \"broadcasting\nsound\" with the antenna + wave arcs, so the volume widget reads\nwith proper weight at the top-right of the frame.",
          "is_bot": false,
          "headline": "TUI: bigger volume icon — switch to FA broadcast-tower",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:17:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a189c8524e1ce1dd12f862e6bcd05299e060d3d",
          "body": "Two icon walk-backs after the user clarified what they actually\nmeant:\n\n  - Logo glyph reverts from headphones () to the original music\n    note (♪). The headphones change was based on a misread of the\n    user's request — they were asking about the volume icon, not\n    the logo.\n  - Volume icon rev\n[…]\nith no sound waves, which read as \"mute\" / no\n    audio. FA volume_up has the radiating wave arcs that make the\n    speaker visually \"active\" — exactly the \"playing audio\" feel\n    the user was after.",
          "is_bot": false,
          "headline": "TUI: revert logo to music note, switch volume back to FA volume-up",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:16:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f92f4d406085acba438e6ab3ceac178f4f7dab8f",
          "body": "The static ♪ note read as \"this is sheet music\" rather than \"this is\nplaying audio\". Headphones (FA U+F025, \"\") put the brand into a\nlistening posture — visually the title bar now signals \"you're\nhearing something through this app\" instead of \"music notation\".",
          "is_bot": false,
          "headline": "TUI: swap logo glyph from music note to headphones",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:14:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dfd8bb3879df135dfd4bfbf1faf02eb5738d643b",
          "body": "Two related polish points: the spring-animated volume bar made the\ntitle bar dance during +/- presses (filler hyphens shifted left and\nright as the percent string changed width 80% → 100%), and the user\npreferred discrete 5%-jumps over the smooth fill.\n\nDrop the spring animation entirely:\n  - intern\n[…]\n.100 range, so the\nfiller \"─\" segments don't jitter when the digit count changes.\n\nrenderVolume reads m.volume directly; the percent and the bar\nnaturally agree because they share the source of truth.",
          "is_bot": false,
          "headline": "TUI: instant volume changes with fixed-width percent",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:12:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f164acbaa93bd2a404e4925954f34f659af4882b",
          "body": "Two fixes for the volume widget in the title bar:\n\n1. The percent digit was reading m.volume (the target) while the bar\n   filled cells came from m.volumeDisplayed (the spring-animated\n   value). On +/- the digit jumped instantly to \"85%\" while the bar\n   was still climbing through 80→81→...→85, so \n[…]\nn a void\". Muted gives the empty cells\n   enough contrast to read as a quiet placeholder rail, so the\n   filled cells look like progress drawing across a track instead of\n   appearing out of thin air.",
          "is_bot": false,
          "headline": "TUI: sync volume percent with the animated bar; brighter empty rail",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:08:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a0246a2bd257e3f23069263b9282d5aa773cb6bc",
          "body": "Volume bar + icon + percent now live as the right-side label in the\nframe's top edge, replacing the clock. The body of the app drops the\ndedicated volume row entirely:\n\n  ╭─ ♪ lofi.player ─────────  󰕿  ▰▰▰▰▰▰▱▱▱▱▱▱  85% ─╮\n  │                                                  │\n  │  ● Lofi Girl 24/7 \n[…]\nsegments.\n\nrenderVolume keeps its previous spring-animation logic (volumeDisplayed\nticks toward volume, see anim.go) — the rendered output now happens to\nland in the title bar rather than its own row.",
          "is_bot": false,
          "headline": "TUI: move volume widget into the frame's top border",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T18:03:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "635f22e77821056c8c51c951d0a4c02291798b93",
          "body": "Symmetric labeling: stream-kind rows get \"· stream\" instead of being\nunmarked. Both source types are now visually equal — the user can\nalways tell which entries route through ytdl_hook and which hit mpv\ndirectly, without having to remember which one is the default.\n\nstationKindIcon switches from a YouTube-only conditional to printing\n\"· \" + EffectiveKind() for any non-empty kind (so a future custom\nkind shows up too instead of silently disappearing).",
          "is_bot": false,
          "headline": "TUI: tag every station with its kind (\"· youtube\" / \"· stream\")",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T17:58:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1f10f32ee578939e84cf2e4b528d131eff2db3e6",
          "body": "The \"· yt\" abbreviation was unfamiliar enough to read as noise.\nSpell out \"· youtube\" — extra characters are worth it for the\nzero-ambiguity reading. The rule \"tag the exception, not the\ndefault\" stays: stream (default) entries still get no marker, only\nYouTube-kind rows pick up the muted suffix.",
          "is_bot": false,
          "headline": "TUI: spell out \"youtube\" instead of the cryptic \"yt\"",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T17:56:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "956789252e7feffb8861559bb94d7b1777975885",
          "body": "The Nerd Font youtube codepoint (U+F167) doesn't render reliably —\non the user's terminal it showed up as a generic ◇ diamond, which\ndefeats the whole point of an at-a-glance source indicator.\n\nSwitch to a small textual tag in muted: \"· yt\" appended after the\nstation name on YouTube-kind rows. Strea\n[…]\n stays clean and the rare\nYouTube entry visually stands out without the eye having to parse a\nrss-vs-yt distinction on every line.\n\nRemoves the now-unused iconKindYouTube and iconKindStream constants.",
          "is_bot": false,
          "headline": "TUI: textual \"· yt\" tag for YouTube stations, drop unreliable glyph",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T17:54:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a6a1b45629a69df6036b631d7866e78f65d57af8",
          "body": null,
          "is_bot": false,
          "headline": "Remove stray view.go.orig left from a patch attempt",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T17:52:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "97ab1995d52e9b94f8bc3965c3aa6d09f3525ecf",
          "body": "Each station picks up a tiny Nerd Font icon indicating its source:\n (FontAwesome youtube) for Kind=youtube, and  (FontAwesome rss)\nfor Kind=stream — direct HTTP/Icecast streams. The glyph is rendered\nin the SectionHeader (Muted) tone, so it sits as a quiet tag next\nto the bold station name without c\n[…]\nname, so a quick visual scan shows which entries route through\n    yt-dlp.\n\nstationKindIcon is the only render helper added; it falls back to\nempty string for unknown kinds rather than rendering tofu.",
          "is_bot": false,
          "headline": "TUI: station-kind glyph next to station names",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T17:51:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "494bac2b3866137ffdb4479c9af7195047c6e73f",
          "body": "While a station is playing (loading flag cleared), the live ●\nindicator now alternates between full intensity and dimmed every\n700 ms via the SGR Faint attribute. Calm 1.4 s breath, no color\nchange — reads as \"alive\" rather than \"blinking alarm\".\n\nImplementation:\n  - new pulseTickMsg + pulseTick com\n[…]\np (one bool flip + one tea.Tick reschedule)\n\nFaint chosen over per-color RGB math so the pulse adapts to the\ncurrent theme automatically — every theme's StatusLive picks up the\ndim treatment for free.",
          "is_bot": false,
          "headline": "TUI: soft heartbeat pulse on the live ● indicator",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T14:04:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "70e822c4f03bbe3fca668c3222fb27ff5ffc3153",
          "body": "The brand and the clock now ride the top edge of the outer frame,\ntitle-bar style:\n\n  ╭─ ♪ lofi.player ──────────────────  16:57 ─╮\n\nThis frees the row inside the frame that the standalone header was\noccupying, makes the app look more like a self-contained desktop\nwindow, and removes the visual repe\n[…]\nline top and bottom gives the content vertical breath.\n\nviewFull and viewMini drop their renderHeader call (the brand has\nmoved to the frame). renderHeader is removed since nothing else\nreferences it.",
          "is_bot": false,
          "headline": "TUI: title and clock embedded in the frame's top border",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T14:03:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d654df26cabd5d8b10ef4fe8d5f518d02ec1acfa",
          "body": "The frame was stretching across the full terminal width. On a 200+\ncell screen the content hugged the left edge with a sea of empty\nspace on the right. Pin the frame to 100 cells and center it with\nlipgloss.PlaceHorizontal — on wide terminals the app reads as a\ncontained panel sitting in the middle \n[…]\ntent starts wrapping awkwardly).\nInner content width passes through as frameWidth - 2 so headers\nand the now-playing track-line truncation budget align inside the\nborders the same way they did before.",
          "is_bot": false,
          "headline": "TUI: center the app frame at a fixed width on wide terminals",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:56:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5fb6f46f2b0af0f6b652626b7048d3e783e9a788",
          "body": "Two nested rounded borders (outer \"app\" + inner \"now playing\")\nwere competing for attention. Drop the inner one — the outer frame\nis now the only container on screen, and now-playing reads as the\ntop section inside that frame, given visual weight by the bold\nstation name and the leading ●/◯/spinner \n[…]\no leftPad-prefixed lines.\nThe track-line truncation budget tracks innerWidth (terminal width\nminus leftPad on both sides, capped at nowPlayingMaxWidth) so long\ntitles still get the ellipsis treatment.",
          "is_bot": false,
          "headline": "TUI: drop the inner now-playing card, keep the outer app frame",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:52:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5eff956b9e357e5905f60d3b8dea72c134dd4bc8",
          "body": "The whole UI now lives inside a single rounded card so the layout\nreads as one contained surface instead of a stack of unrelated rows\nfloating in the terminal. Border color is Subtle (one rung quieter\nthan the now-playing card's Muted) so the two borders coexist —\nouter reads as \"app frame\", inner r\n[…]\ntal padding stays at 0 so the existing leftPad inside content\nkeeps doing the indent (no double-indent).\n\nNo changes to behavior or any other rendering logic — the wrap is a\nsingle addition in View().",
          "is_bot": false,
          "headline": "TUI: wrap the entire app in an outer rounded border",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:48:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "37d80348263b5f68e27e34da96269071ea033c06",
          "body": "…ding\n\nThree small follow-ups from the latest screenshot:\n\n- Volume bar widens from 10 cells to 14 cells. The bar was reading\n  as a token next to the now-playing card; 14 cells gives 5%-step\n  changes a noticeable visual jump and brings the bar closer to the\n  card's visual weight.\n- Adds back a bl\n[…]\ncells of breathing room reads as\n  intentional layout rather than \"barely indented\". Card MarginLeft\n  follows automatically since it derives from len(leftPad).\n\nNo behavior changes; tests stay green.",
          "is_bot": false,
          "headline": "TUI cosmetic pass: wider volume bar, breathing room, premium left pad…",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:41:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6f3a4db6c5669a513391c7a0ddf1b5963fc18000",
          "body": "Five small visual tweaks the user pointed out from the latest screenshot:\n\nThe now-playing card no longer stretches across the full terminal\nwidth. nowPlayingMaxWidth = 80 caps it; on a 200-cell terminal the\ncard now reads as a focused element rather than a stretched panel\nwith one line of content f\n[…]\nons · 5\".\nTrivial information density boost — also balances the leading icon.\n\ngo vet + go test still green; no test changes needed since none of\nthese touched user-visible behavior beyond aesthetics.",
          "is_bot": false,
          "headline": "TUI cosmetic pass: card width, help labels, cursor color, station count",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:25:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a717e43f7a5d58255b55d576ad9a5807546d71e9",
          "body": "The loading flag in the TUI was getting stuck while music was already\nplaying because mpv's pause property doesn't fire a change event when\nloadfile transitions from one playing stream to another (false → false\nisn't a \"change\"). The TUI was waiting for PlaybackStarted that would\nnever arrive.\n\nSwit\n[…]\nner to\nflip back to ●.\n\nThe pause property handler stays as a fallback for genuine\npause/resume transitions where the property does change, so toggling\nspace on the same station still works as before.",
          "is_bot": false,
          "headline": "audio: emit PlaybackStarted on mpv playback-restart event",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:18:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "62f87746ead8ef3cf8abcb29860b6ebb5e9ab562",
          "body": "User decided this added complexity without enough day-to-day value.\nThe git history (commit 91c5d1f) preserves the full implementation;\nthis removes it from the runtime cleanly.\n\nDeleted packages:\n  - internal/pomodoro: state machine + Stats type + tests\n  - internal/notify: OS desktop notifications\n[…]\n streak +\nlistened mentions.\n\ngo.mod stays the same — no external deps were unique to pomodoro.\nAll tests still pass, no exported audio/config/state/theme/tui API\nbeyond pomodoro pieces was disturbed.",
          "is_bot": false,
          "headline": "Drop pomodoro / streak / listened-time feature",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:11:42Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "96e9064f2f4b4effccc572a6bdcb5af5a7ed1387",
          "body": "The spinner that lived in the track line (\"buffering metadata\") fired\nwhile audio was already playing — a visible animation during normal\nlistening was noise, not information. Revert that branch back to the\nquiet \"…\" placeholder.\n\nThe status-indicator spinner stays as is: it animates only while\nloading is true (between dispatching playCmd and the first\nPlaybackStarted event from mpv), which is the genuine \"not yet\naudible\" window.",
          "is_bot": false,
          "headline": "TUI: drop the track-line spinner, keep the status one",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:02:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f5ac3ee7cdadd022906364b6c2582c03203d772d",
          "body": "A new \"loading\" flag on Model tracks the gap between dispatching a\nplayCmd (or autoplay on startup) and the first PlaybackStarted event\nmpv sends back. While loading is true, the status indicator — both\nin the now-playing card and beside the playing-station row in the\nlist — renders the existing bra\n[…]\nrefreshes spinner.Style so the spinner color tracks the active theme.\nTestView_ShowsPlayingMarker updated to drive the model through the\nloading → playing transition explicitly via PlaybackStartedMsg.",
          "is_bot": false,
          "headline": "TUI: spinner in status indicator while mpv is buffering",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T13:00:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1ec6e89c7c345856134be910a1b0c5668d766c94",
          "body": "Static \"…\" gave no signal that the player was actually doing\nsomething while ICY/media-title metadata was in flight. Replace it\nwith bubbles/spinner.Dot — the canonical braille spinner\n\"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\", 10 FPS — followed by a muted \"buffering\" label.\n\nSpinner ticks live globally: Init returns m.spinner.\n[…]\nrendered and the tick is a no-op.\n\nSpinner color uses the active theme's Muted tone so the animation\ndoesn't dominate the now-playing card; it reads as a quiet activity\nindicator rather than an alarm.",
          "is_bot": false,
          "headline": "TUI: braille spinner for buffering placeholder",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:56:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fc64578c3f638b52884fc440c5d6f22f953b2939",
          "body": "mpv's ytdl_hook briefly reports a YouTube URL tail\n(\"watch?v=jfKfPfyJRdk\") as media-title before the resolved video\ntitle arrives a few seconds later. The TUI was rendering the URL\nfragment as the track line (\"watch?v=jfKfPfyJRdk\" in muted under\nthe station name), which looks broken.\n\ntranslatePrope\n[…]\n…\" placeholder\ninstead, which then gets replaced by the real title once\nytdl_hook resolves it.\n\nTable-driven test covers the YouTube case plus a few generic URL\nshapes (http, rtmp, query-string-only).",
          "is_bot": false,
          "headline": "audio: suppress URL-fragment media-title from ytdl_hook",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:53:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5a9789d3133d7faf400c9fbc3363ac82fbf23dc6",
          "body": "The \"+ add station\" affordance under the list duplicated the\n\"a add station\" entry already in the help bar — two parallel paths to\nthe same action, both visually present at the same time. Drop the\ninline line; the help-bar entry stays. The empty-list case keeps the\n\"press a to add one\" hint so a fre\n[…]\n\\n stacked with\nthe gap and inflated the spacing.\n\nNet: same elements, tighter layout, no information loss. Orphan\nAddStation style removed from styles.go since its only consumer\nwas the deleted line.",
          "is_bot": false,
          "headline": "TUI polish: drop \"+ add station\", tighten inter-block spacing",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:50:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4dc342553614ed0087fb08f131a67d6980cf0fb1",
          "body": "Three small follow-ups after the previous reskin landed:\n\nformatTrack now distinguishes \"real ICY metadata\" from \"media-title\nfallback\". When mpv has both Artist and Title (the normal Icecast\ncase), the line stays in foreground + warning accent — the user is\nlistening to a song. When only Title exis\n[…]\nank line between the last station and \"+ add station\" is\nremoved too — the affordance now reads as part of the list rather\nthan a stranded line below it.\n\nTests still pass; only renderer code shifted.",
          "is_bot": false,
          "headline": "TUI polish: track styling, spacing, card padding",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:46:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "55c65a344ecaaae5edecb4da8661e369cb6af4a3",
          "body": "Visual reskin pulling the UI toward modern-dashboard territory while\nkeeping it minimal: icons earn the identity, a single rounded card\nanchors the focus element, and the rest stays free-form.\n\nNow-playing wraps in a rounded-border lipgloss card (Muted border,\nhorizontal padding, MarginLeft to align\n[…]\n new\nindicator glyphs (TestView_ShowsPlayingMarker now asserts ● / ◯\ninstead of \"live\"/\"paused\"). Orphan Separator style removed from\nstyles.go — its only consumer was the now-deleted renderSeparator.",
          "is_bot": false,
          "headline": "TUI: nerd-font icons, now-playing card, status glyphs",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:42:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5b7eb3cfeb99f9d5f0663b042d35082bfebf4d2a",
          "body": "Two real rendering bugs surfaced on the user's first screenshot:\n\nA1. Column alignment in pomodoro / today panels was busted because\nfmt.Sprintf(\"%-Ns\", styled, ...) counts ANSI escape bytes toward the\nfield width, so styled labels got zero padding (\"listened<1m\" instead\nof \"listened   <1m\"). Replac\n[…]\ninstead of \"(no stations configured)\" — fresh-install users see\nthe next step without consulting docs.\n\ngo vet + go test stay clean. No exported API changes — only renders\ninside internal/tui shifted.",
          "is_bot": false,
          "headline": "TUI polish: column alignment, layout balance, cursor + empty states",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:28:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ad4859cab5c900224f50c4d6ad4573c326ca18dc",
          "body": "The plan §5.1 mockup hinted at this with a \"+ add station\" placeholder\nin the station list, but until now the only way to add a station was\nto edit config.yaml by hand. New `a` binding pops a centered modal\nform built on bubbles/textinput: name + url, Tab cycles fields, Enter\nsaves, Esc cancels. Val\n[…]\n.\n\ngo.mod / go.sum: add the bubbles/textinput transitive dep\n(github.com/atotto/clipboard).\n\nREADME updated with the new binding and a one-liner explaining the\nmodal flow plus the auto kind detection.",
          "is_bot": false,
          "headline": "TUI: add-station modal form (a key)",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:13:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c1f891cb36a05dd44c00e3e58fbf00ff9b4f0385",
          "body": "adrg/xdg.ConfigFile/StateFile resolves to ~/Library/Application Support\non macOS, despite the project plan §9 explicitly wanting the XDG-style\n~/.config and ~/.local/state paths on both platforms (the convention\nterminal-tool users expect). Result: the README pointed users at one\nlocation while conf\n[…]\n\nfile is absent or on Linux).\n\nREADME: rewords the config / state sections to match reality and to\nreassure users that no manual file or directory setup is needed —\nboth are auto-created on first run.",
          "is_bot": false,
          "headline": "Fix XDG path resolution on macOS, drop adrg/xdg dep",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T12:06:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5a059bfd63f08cca4323cced9bb9b3bd0c57b639",
          "body": "config.Station gains a Kind field with two recognized values: \"stream\"\n(default — direct HTTP/Icecast URL passed to mpv as-is) and \"youtube\"\n(routed through mpv's built-in ytdl_hook script). Empty Kind defaults\nto \"stream\" so existing Phase 0-3 configs keep working with no changes.\nHelpers Effective\n[…]\nkey parsing back to KindStream, and YAML round-trip of\nkind: youtube. Example config gets a commented-out Lofi Girl entry as\na starting template; README documents the new requirement and config\nshape.",
          "is_bot": false,
          "headline": "Phase 4a: YouTube station kind via mpv's ytdl_hook",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T11:56:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "91c5d1f7bcb444d7584e0639e86b29a60cbca3ae",
          "body": "internal/pomodoro: pure state machine — no goroutines, no time.Now()\ninside the package. Sessions store an absolute EndsAt rather than a\ncountdown so laptop sleep / suspend doesn't drift the timer; Tick\nadvances exactly one phase per call even when \"now\" is far past\nEndsAt. Stats track ListenedToday\n[…]\nts, partial overrides keep defaults for unmentioned\nfields, and `pomodoro:` (null) re-fills defaults.\n\nREADME updated to Phase 3 surface; the example config now includes the\ndocumented pomodoro block.",
          "is_bot": false,
          "headline": "Phase 3: pomodoro focus timer with stats and notifications",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T10:03:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9a0190cbc1855bfe9407a6571c4a7d9ac8450396",
          "body": "Themes. Three new palettes (catppuccin-mocha, gruvbox-dark, rose-pine)\njoin Tokyo Night, all sharing the same 10-role surface. theme.Names()\nexposes a stable ordering and theme.Next drives the new `t` cycle\nbinding; switching is instantaneous because Styles is just rebuilt\nfrom the new palette.\n\nSta\n[…]\norizontally centered on the available width.\n\nREADME updated to Phase 2 surface; tests cover theme registration,\nstate round-trip + atomicity + Pomodoro preservation, and the existing\nTUI smoke suite.",
          "is_bot": false,
          "headline": "Phase 2: themes, state persistence, mini mode, statusline, polish",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T09:26:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5e246379d4055c7b29a2a19fc9cc37caae1e5f9b",
          "body": "internal/audio: new package wrapping the mpv subprocess. ipcClient\ndemultiplexes JSON-line messages by request_id (sync.Map), serializes\nwrites through a mutex, and runs a 1 MiB-cap scanner read loop that\nroutes property-change events to a buffered channel. Player layers the\nhigh-level surface on to\n[…]\nerror responses, large lines, malformed input,\ncontext cancellation, post-close behavior); Player.translateOne for\nall event variants and dedup behavior. Manual mpv integration is the\nacceptance gate.",
          "is_bot": false,
          "headline": "Phase 1: real audio via mpv JSON-IPC",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T09:15:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7b20105079553e92ce6dd63aa2daab1336f1d445",
          "body": "Bootstraps the Bubble Tea TUI per plan §6 Phase 0. Audio is faked —\nspace toggles a \"live\"/\"paused\" status flag on the cursor's station and\nmarks it with a ♪ glyph. Real playback arrives in Phase 1 via mpv IPC.\n\ninternal/config: YAML loader on the XDG path. Defaults() ships four\nSomaFM/Radio Paradis\n[…]\nt from §5.1.\n\nTests cover the core (config round-trip, defaults, malformed YAML;\ntheme Lookup; volume clamping, cursor bounds, play/pause toggle, help\ntoggle, quit). View rendering is verified by eye.",
          "is_bot": false,
          "headline": "Phase 0: UI scaffold with Tokyo Night theme",
          "author_name": "Alexander Neupokoev",
          "author_login": "Alexsatter",
          "committed_at": "2026-04-25T08:41:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 13,
      "commits_last_year": 101,
      "latest_release_at": "2026-06-18T09:43:49Z",
      "latest_release_tag": "v0.1.12",
      "releases_from_tags": false,
      "days_since_last_push": 36,
      "active_weeks_last_year": 5,
      "days_since_latest_release": 36,
      "mean_days_between_releases": 5.1
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 42,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "github.com/iRootPro/lofi-player",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "go",
          "matches_repo": true,
          "registry_url": "https://pkg.go.dev/github.com/iRootPro/lofi-player",
          "is_deprecated": false,
          "latest_version": "v0.1.12",
          "repository_url": "https://github.com/iRootPro/lofi-player",
          "versions_count": 13,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": null,
          "latest_published_at": "2026-06-18T09:42:44Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 36
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 1,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [
        "go.mod"
      ],
      "largest_source_bytes": 34574,
      "source_files_sampled": 37,
      "oversized_source_files": 0,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "go.mod"
      ],
      "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": [
        "go"
      ],
      "dependencies": [
        {
          "name": "github.com/atotto/clipboard",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.1.4"
        },
        {
          "name": "github.com/charmbracelet/bubbles",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.0.0"
        },
        {
          "name": "github.com/charmbracelet/bubbletea",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.3.10"
        },
        {
          "name": "github.com/charmbracelet/lipgloss",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.1.0"
        },
        {
          "name": "gopkg.in/yaml.v3",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v3.0.1"
        }
      ],
      "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": 0,
        "merged_prs": 0,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "Alexsatter",
          "commits": 101,
          "avatar_url": "https://avatars.githubusercontent.com/u/118963855?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "go.sum"
      ],
      "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": null,
            "reason": "no pull request found",
            "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/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "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": "no SAST tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 10,
            "reason": "GitHub workflow tokens follow principle of least privilege",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 8,
            "reason": "2 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "9a340a77e7668a24327a26d62771287328ed94ac",
        "ran_at": "2026-07-24T21:36:50Z",
        "aggregate_score": 3.4,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-06-18T09:43:40Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": null,
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/iRootPro/lofi-player",
    "host": "github.com",
    "name": "lofi-player",
    "owner": "iRootPro"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "at_risk",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 42,
      "inputs": {
        "security": 34,
        "vitality": 60,
        "community": 24,
        "governance": 34,
        "engineering": 56
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "moderate",
        "name": "Vitality",
        "value": 60,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "at_risk",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "commits_last_year": 101,
              "human_commit_share": 1,
              "days_since_last_push": 36,
              "active_weeks_last_year": 5
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 36 days ago",
                "points": 18,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 36
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "5/52 weeks with commits",
                "points": 3.5,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "101 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 101
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 13,
              "latest_release_tag": "v0.1.12",
              "releases_from_tags": false,
              "days_since_latest_release": 36,
              "mean_days_between_releases": 5.1
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "13 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 13
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 36 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 36
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~5.1 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 5.1
                    }
                  }
                ],
                "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": "critical",
        "name": "Community & Adoption",
        "value": 24,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 1,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "1 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "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": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "at_risk",
        "name": "Sustainability & Governance",
        "value": 34,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "critical",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution, PR acceptance. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution",
                    "pr_acceptance"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 1,
            "inputs": {
              "merged_prs": 0,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0
            },
            "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": "no decided pull requests or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_decided_prs_or_data",
                    "params": {}
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 44,
            "inputs": {
              "followers": 0,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "iRootPro",
              "public_repos": 81,
              "account_age_days": 3734
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "0 followers of iRootPro",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 0,
                      "login": "iRootPro"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "81 public repos, account ~10 yr old",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 81
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 10
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "github.com/iRootPro/lofi-player"
              ],
              "ecosystems": "go",
              "any_deprecated": false,
              "min_days_since_publish": 36
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on go",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "go"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 36 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 36
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "13 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 13
                    }
                  }
                ],
                "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": "moderate",
        "name": "Engineering Quality",
        "value": 56,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_ci_tests"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 60,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "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": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "moderate",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": false,
              "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": 0,
                "status": "missed",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": null,
                "points": 0,
                "status": "missed",
                "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": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 34,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): CI-Tests, Packaging. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "ci_tests",
                    "packaging"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 34,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "scorecard_aggregate": 3.4
            },
            "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": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "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/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "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": "no SAST tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "GitHub workflow tokens follow principle of least privilege",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "2 existing vulnerabilities detected",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 58,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.97,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "97 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 97,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 56,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "go.sum"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [],
              "agent_commit_share": 0,
              "toolchain_manifests": [
                "go.mod"
              ],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "go.mod (toolchain convention, no task runner)",
                "points": 12.6,
                "status": "partial",
                "details": [
                  {
                    "code": "toolchain_convention",
                    "params": {
                      "files": "go.mod"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "Go (statically typed)",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "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": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "primary_language": "Go",
              "largest_source_bytes": 34574,
              "source_files_sampled": 37,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Go (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/37 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 37,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "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"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-24T21:36:54.976794Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/i/iRootPro/lofi-player.svg",
  "full_name": "iRootPro/lofi-player",
  "license_state": "standard",
  "license_spdx": "MIT"
}

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

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

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