公开记录
软件健康报告模式 0.27.0 · 指标 1.13.0 · 2026-07-23 01:15 UTC

chrissnell / graywolf

A fully-featured APRS stack with modern web UI

Go · Svelte · RustGPL-2.0★ 177 星标⑂ 19 复刻始于 2026年4月在 GitHub 上查看 ↗

chrissnell/graywolf 的健康指数为 100 分中的 62 分,处于「中等」区间。 其得分最高的类别是Vitality(82/100),最低的是Security(40/100)。 最近一次更新在 4 天前。 近期的大部分工作由 1 位贡献者完成。

62
总分 / 100
中等

软件健康指数

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

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

评分画像

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

所有权

Chris Snell个人账户
129 关注者91 个公开仓库始于 2011年9月@adobe

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

软件包生态系统

注册表软件包版本月下载量版本数最近发布
Gogithub.com/chrissnell/graywolfv0.14.11-1195 天前

按类别列示的指标

活力

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

82良好 · 占总体的 22%
评分方式
36/36推送新近度 — 最近一次推送于 4 天前
11.8/36提交节奏 — 52 周中有 17 周有提交
18/18提交量 — 最近一年 1,925 次提交
10/10OpenSSF Scorecard:Maintained — 30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
所用输入
commits_last_year1,925
human_commit_share1
days_since_last_push4
active_weeks_last_year17

发布纪律

90优秀
评分方式
27/27有发布版本 — 已发布 100 个发布版本
36/36发布时效 — 最近一次发布版本于 5 天前
27/27发布节奏 — 约每 3.3 天发布一次
0/10OpenSSF Scorecard:Signed-Releases — Project has not signed or included provenance with any releases.
所用输入
releases_count100
latest_release_tagv0.14.11
releases_from_tags
days_since_latest_release5
mean_days_between_releases3.3

社区与采用

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

52中等 · 占总体的 18%
评分方式
36.4/60星标 — 177 个星标
10.5/25复刻 — 19 个复刻
5.8/15关注者 — 12 位关注者
所用输入
forks19
stars177
watchers12
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

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

可持续性与治理

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

63中等 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0.2/22.5提交分布 — 头号贡献者编写了 99% 的提交
6.8/13.5贡献者广度 — 5 位贡献者
6/10OpenSSF Scorecard:Contributors — project has 2 contributing companies or organizations -- score normalized to 6
所用输入
bus_factor1
contributors_sampled5
top_contributor_share0.992
评分方式
43.5/46.8议题解决 — 93% 的议题已关闭
36.6/38.3PR 接受 — 已裁定的 PR 中 296/309 已合并
1.5/15OpenSSF Scorecard:Code-Review — Found 3/16 approved changesets -- score normalized to 1
所用输入
merged_prs296
open_issues12
closed_issues162
issue_closed_ratio0.931
closed_unmerged_prs13
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
15.2/25所有者影响力 — chrissnell 有 129 位关注者
25/25既往记录 — 91 个公开仓库,账户约 14 年
所用输入
followers129
owner_typeUser
is_verified
owner_loginchrissnell
public_repos91
account_age_days5,417
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — go 上有 1 个软件包
35/35发布时效 — 最近一次发布于 5 天前
20/20版本历史 — 119 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesgithub.com/chrissnell/graywolf
ecosystemsgo
any_deprecated
min_days_since_publish5

工程质量

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

66中等 · 占总体的 20%

工程实践

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

文档

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

安全

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

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

安全态势

40存在风险
评分方式
6.8/7.5Binary-Artifacts — binaries present in source code
0/7.5Branch-Protection — branch protection not enabled on development/release branches
2.2/2.5CI-Tests — 14 out of 15 merged PRs checked by a CI test -- score normalized to 9
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0.8/7.5Code-Review — Found 3/16 approved changesets -- score normalized to 1
1.5/2.5Contributors — project has 2 contributing companies or organizations -- score normalized to 6
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
5/5Fuzzing — project is fuzzed
2.5/2.5许可证 — license file detected
7.5/7.5Maintained — 30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
0.5/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 1
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
0/7.5Vulnerabilities — 33 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate4

AI 就绪度

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

80良好 · 占总体的 0%
评分方式
45/45代理指令 — CLAUDE.md
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 93 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.93
agent_instruction_filesCLAUDE.md
agent_instruction_max_bytes4,522
评分方式
18/18一条命令的引导启动 — Makefile
22/22自动化测试
0/11Lint / 格式化配置
11/11静态类型检查 — web/src/api/tsconfig.json
10/10可复现环境 — Dockerfile, lockfile
8/10已体现的代理实践 — 最近 100 次提交中有 4 次由代理编写或署名代理
0/8自动化维护 — 未观察到自动依赖更新
1/10OpenSSF Scorecard:Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 1
所用输入
has_nix
has_tests
lockfilesCargo.lock, go.sum, package-lock.json
has_dockerfile
typed_language
bootstrap_filesMakefile
has_devcontainer
has_linter_config
typecheck_configsweb/src/api/tsconfig.json
agent_commit_share0.04
toolchain_manifestsCargo.toml, android/app/build.gradle.kts, android/build.gradle.kts, go.mod, graywolf-modem/Cargo.toml
dependency_bot_commit_share0
评分方式
45/45可类型检查的代码 — Go(静态类型)
54.7/55可控的文件大小 — 采样的 1,099 个源文件中有 6 个超过 60KB
所用输入
primary_languageGo
largest_source_bytes117,468
source_files_sampled1,099
oversized_source_files6
评分方式
40/40API 模式(OpenAPI/GraphQL/proto) — docs/handbook/openapi.json, docs/handbook/openapi.yaml, pkg/webapi/docs/gen/swagger.json, pkg/webapi/docs/gen/swagger.yaml, proto/graywolf.proto, proto/platform.proto
0/20MCP 服务器
40/40可运行示例 — examples
所用输入
example_dirsexamples
has_mcp_signal
api_schema_filesdocs/handbook/openapi.json, docs/handbook/openapi.yaml, pkg/webapi/docs/gen/swagger.json, pkg/webapi/docs/gen/swagger.yaml, proto/graywolf.proto, proto/platform.proto

关键数据

177GitHub 星标
5贡献者
1,925最近 12 个月提交数
4距最近推送天数
100发布版本数
1巴士系数(bus factor)
12开放议题
crates.io, Go, Maven, npm, RubyGems软件包生态系统数

数据采集警告

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • Could not fetch crates package 'graywolf-demod' from its registry
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

更多细节

Star 与 Fork 历史 0 ★ / 19 ⇿
0Star
19Fork
58发布

每颗 star 和每个 fork 的添加时间,来自 GitHub 并按天汇总。累计增长位于其构成来源——每日新增——的正上方,二者可相互对照:稳定的自然增长与短暂的突增形态截然不同。当这一差别可被衡量时,它会作为增长真实性予以报告。

0481216201912026-042026-062026-07
主版本 0次版本 5修订 47
OpenSSF Scorecard 4.0 / 10
4.0综合

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

9Binary-Artifactsbinaries present in source code
0Branch-Protectionbranch protection not enabled on development/release branches
9CI-Tests14 out of 15 merged PRs checked by a CI test -- score normalized to 9
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
1Code-ReviewFound 3/16 approved changesets -- score normalized to 1
6Contributorsproject has 2 contributing companies or organizations -- score normalized to 6
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
10Fuzzingproject is fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
1Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 1
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
0Vulnerabilities33 existing vulnerabilities detected
直接依赖 38
注册表软件包版本约束清单文件
RubyGemsfastlaneGemfile
Gogithub.com/coder/websocketv1.8.14go.mod
Gogithub.com/glebarez/go-sqlitev1.21.2go.mod
Gogithub.com/glebarez/sqlitev1.11.0go.mod
Gogithub.com/invopop/jsonschemav0.14.0go.mod
Gogithub.com/pquerna/otpv1.5.0go.mod
Gogithub.com/prometheus/client_golangv1.23.2go.mod
Gogithub.com/prometheus/client_modelv0.6.2go.mod
Gogithub.com/warthog618/go-gpiocdevv0.9.1go.mod
Gogo.bug.st/serialv1.6.4go.mod
Gogo.uber.org/goleakv1.3.0go.mod
Gogolang.org/x/cryptov0.49.0go.mod
Gogolang.org/x/sysv0.42.0go.mod
Gogoogle.golang.org/protobufv1.36.11go.mod
Gogopkg.in/yaml.v3v3.0.1go.mod
Gogorm.io/gormv1.31.1go.mod
crates.iochrono0.4graywolf-modem/Cargo.toml
crates.ioclaxon0.4graywolf-modem/Cargo.toml
crates.iohound3graywolf-modem/Cargo.toml
crates.ioprost0.13graywolf-modem/Cargo.toml
crates.iobytes1graywolf-modem/Cargo.toml
crates.iocpal0.17graywolf-modem/Cargo.toml
crates.ioserde1graywolf-modem/Cargo.toml
crates.ioserde_json1graywolf-modem/Cargo.toml
npm@americana/maplibre-shield-generator^0.1.1web/package.json
npm@chrissnell/chonky-ui^0.4.2web/package.json
npm@internationalized/date^3.8.0web/package.json
npm@xterm/addon-search^0.16.0web/package.json
npm@xterm/addon-serialize^0.14.0web/package.json
npm@xterm/addon-web-links^0.12.0web/package.json
npm@xterm/xterm^6.0.0web/package.json
npmbits-ui^2.0.0web/package.json
npmlucide-svelte^0.577.0web/package.json
npmmaplibre-gl^5.24.0web/package.json
npmopenapi-fetch0.17.0web/package.json
npmpmtiles^4.4.1web/package.json
npmqrcode^1.5.4web/package.json
npmsvelte-spa-router^4.0.0web/package.json
全部依赖 未采集

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

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 37294,
      "has_wiki": false,
      "homepage": null,
      "languages": {
        "Go": 4557490,
        "CSS": 42847,
        "HTML": 2332,
        "NSIS": 6426,
        "Ruby": 2605,
        "Rust": 829048,
        "Shell": 22580,
        "Kotlin": 245353,
        "Svelte": 1087023,
        "Makefile": 20131,
        "JavaScript": 612812,
        "TypeScript": 6257
      },
      "pushed_at": "2026-07-18T18:00:21Z",
      "created_at": "2026-04-01T03:30:12Z",
      "owner_type": "User",
      "updated_at": "2026-07-21T23:14:19Z",
      "description": "A fully-featured APRS stack with modern web UI",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "GPL-2.0",
      "default_branch": "main",
      "license_spdx_raw": "GPL-2.0",
      "primary_language": "Go",
      "significant_languages": [
        "Go",
        "Svelte",
        "Rust"
      ]
    },
    "owner": {
      "blog": "https://chrissnell.com",
      "name": "Chris Snell",
      "type": "User",
      "login": "chrissnell",
      "company": "@adobe",
      "location": "Manhattan, Kansas",
      "followers": 129,
      "avatar_url": "https://avatars.githubusercontent.com/u/1072626?v=4",
      "created_at": "2011-09-23T00:25:23Z",
      "is_verified": null,
      "public_repos": 91,
      "account_age_days": 5417
    },
    "license": {
      "state": "standard",
      "spdx_id": "GPL-2.0",
      "raw_spdx": "GPL-2.0",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.14.11",
          "kind": "patch",
          "published_at": "2026-07-17T05:42:34Z"
        },
        {
          "tag": "v0.14.10",
          "kind": "patch",
          "published_at": "2026-06-30T16:41:47Z"
        },
        {
          "tag": "v0.14.9",
          "kind": "patch",
          "published_at": "2026-06-28T04:55:42Z"
        },
        {
          "tag": "v0.14.8",
          "kind": "patch",
          "published_at": "2026-06-23T00:08:17Z"
        },
        {
          "tag": "v0.14.7",
          "kind": "patch",
          "published_at": "2026-06-20T16:25:37Z"
        },
        {
          "tag": "v0.14.6",
          "kind": "patch",
          "published_at": "2026-06-20T04:38:50Z"
        },
        {
          "tag": "v0.14.5",
          "kind": "patch",
          "published_at": "2026-06-19T03:21:31Z"
        },
        {
          "tag": "v0.14.4",
          "kind": "patch",
          "published_at": "2026-06-19T03:05:45Z"
        },
        {
          "tag": "v0.14.3-beta.1",
          "kind": "prerelease",
          "published_at": "2026-06-18T04:41:34Z"
        },
        {
          "tag": "v0.14.2",
          "kind": "patch",
          "published_at": "2026-06-17T22:42:47Z"
        },
        {
          "tag": "v0.14.1",
          "kind": "patch",
          "published_at": "2026-06-17T18:19:19Z"
        },
        {
          "tag": "v0.14.0",
          "kind": "minor",
          "published_at": "2026-06-15T06:39:59Z"
        },
        {
          "tag": "v0.13.16",
          "kind": "patch",
          "published_at": "2026-06-11T17:41:42Z"
        },
        {
          "tag": "v0.13.15",
          "kind": "patch",
          "published_at": "2026-06-02T16:01:31Z"
        },
        {
          "tag": "v0.13.14",
          "kind": "patch",
          "published_at": "2026-06-02T01:55:09Z"
        },
        {
          "tag": "snapshot-issue-202",
          "kind": "other",
          "published_at": "2026-06-01T23:38:12Z"
        },
        {
          "tag": "v0.13.13",
          "kind": "patch",
          "published_at": "2026-05-30T04:08:13Z"
        },
        {
          "tag": "v0.13.12",
          "kind": "patch",
          "published_at": "2026-05-29T21:31:56Z"
        },
        {
          "tag": "v0.13.11",
          "kind": "patch",
          "published_at": "2026-05-27T23:45:59Z"
        },
        {
          "tag": "v0.13.10",
          "kind": "patch",
          "published_at": "2026-05-24T01:23:46Z"
        },
        {
          "tag": "v0.13.9",
          "kind": "patch",
          "published_at": "2026-05-23T23:20:42Z"
        },
        {
          "tag": "v0.13.8",
          "kind": "patch",
          "published_at": "2026-05-22T19:47:35Z"
        },
        {
          "tag": "v0.13.7",
          "kind": "patch",
          "published_at": "2026-05-22T05:27:11Z"
        },
        {
          "tag": "v0.13.6",
          "kind": "patch",
          "published_at": "2026-05-21T22:42:10Z"
        },
        {
          "tag": "v0.13.5",
          "kind": "patch",
          "published_at": "2026-05-18T00:43:48Z"
        },
        {
          "tag": "v0.13.4-beta.4",
          "kind": "prerelease",
          "published_at": "2026-05-16T19:10:00Z"
        },
        {
          "tag": "v0.13.4-beta.3",
          "kind": "prerelease",
          "published_at": "2026-05-16T07:24:38Z"
        },
        {
          "tag": "v0.13.4-beta.2",
          "kind": "prerelease",
          "published_at": "2026-05-09T17:33:10Z"
        },
        {
          "tag": "v0.13.4-beta.1",
          "kind": "prerelease",
          "published_at": "2026-05-09T17:15:23Z"
        },
        {
          "tag": "v0.13.3",
          "kind": "patch",
          "published_at": "2026-05-09T07:03:21Z"
        },
        {
          "tag": "v0.13.2",
          "kind": "patch",
          "published_at": "2026-05-07T03:44:07Z"
        },
        {
          "tag": "v0.13.1",
          "kind": "patch",
          "published_at": "2026-05-06T01:35:41Z"
        },
        {
          "tag": "v0.13.0",
          "kind": "minor",
          "published_at": "2026-05-05T20:53:30Z"
        },
        {
          "tag": "v0.12.6",
          "kind": "patch",
          "published_at": "2026-05-02T18:18:14Z"
        },
        {
          "tag": "v0.12.5",
          "kind": "patch",
          "published_at": "2026-05-02T17:28:16Z"
        },
        {
          "tag": "v0.12.4",
          "kind": "patch",
          "published_at": "2026-05-02T16:43:45Z"
        },
        {
          "tag": "v0.12.3",
          "kind": "patch",
          "published_at": "2026-05-01T05:53:44Z"
        },
        {
          "tag": "v0.12.2",
          "kind": "patch",
          "published_at": "2026-05-01T05:26:50Z"
        },
        {
          "tag": "v0.12.1",
          "kind": "patch",
          "published_at": "2026-05-01T01:57:54Z"
        },
        {
          "tag": "v0.12.0",
          "kind": "minor",
          "published_at": "2026-04-30T23:05:05Z"
        },
        {
          "tag": "v0.11.4",
          "kind": "patch",
          "published_at": "2026-04-22T05:49:47Z"
        },
        {
          "tag": "v0.11.3",
          "kind": "patch",
          "published_at": "2026-04-22T04:48:24Z"
        },
        {
          "tag": "v0.11.2",
          "kind": "patch",
          "published_at": "2026-04-22T03:46:04Z"
        },
        {
          "tag": "v0.11.1",
          "kind": "patch",
          "published_at": "2026-04-22T02:49:50Z"
        },
        {
          "tag": "v0.11.0",
          "kind": "minor",
          "published_at": "2026-04-22T02:05:15Z"
        },
        {
          "tag": "v0.10.11",
          "kind": "patch",
          "published_at": "2026-04-20T15:37:53Z"
        },
        {
          "tag": "v0.10.10",
          "kind": "patch",
          "published_at": "2026-04-20T03:30:05Z"
        },
        {
          "tag": "v0.10.9",
          "kind": "patch",
          "published_at": "2026-04-20T02:57:36Z"
        },
        {
          "tag": "v0.10.8",
          "kind": "patch",
          "published_at": "2026-04-20T01:26:35Z"
        },
        {
          "tag": "v0.10.7",
          "kind": "patch",
          "published_at": "2026-04-20T00:55:48Z"
        },
        {
          "tag": "v0.10.6",
          "kind": "patch",
          "published_at": "2026-04-20T00:23:26Z"
        },
        {
          "tag": "v0.10.5",
          "kind": "patch",
          "published_at": "2026-04-19T22:55:49Z"
        },
        {
          "tag": "v0.10.4",
          "kind": "patch",
          "published_at": "2026-04-19T19:32:04Z"
        },
        {
          "tag": "v0.10.3",
          "kind": "patch",
          "published_at": "2026-04-19T18:05:46Z"
        },
        {
          "tag": "v0.10.2",
          "kind": "patch",
          "published_at": "2026-04-19T18:01:14Z"
        },
        {
          "tag": "v0.10.1",
          "kind": "patch",
          "published_at": "2026-04-19T07:05:27Z"
        },
        {
          "tag": "v0.10.0",
          "kind": "minor",
          "published_at": "2026-04-19T04:59:43Z"
        },
        {
          "tag": "v0.9.20",
          "kind": "patch",
          "published_at": "2026-04-19T00:19:29Z"
        },
        {
          "tag": "v0.9.19",
          "kind": "patch",
          "published_at": "2026-04-18T19:27:35Z"
        },
        {
          "tag": "v0.9.18",
          "kind": "patch",
          "published_at": "2026-04-18T07:53:51Z"
        },
        {
          "tag": "v0.9.17",
          "kind": "patch",
          "published_at": "2026-04-17T08:00:00Z"
        },
        {
          "tag": "v0.9.16",
          "kind": "patch",
          "published_at": "2026-04-17T07:51:02Z"
        },
        {
          "tag": "v0.9.15",
          "kind": "patch",
          "published_at": "2026-04-17T04:40:29Z"
        },
        {
          "tag": "v0.9.14",
          "kind": "patch",
          "published_at": "2026-04-16T22:37:10Z"
        },
        {
          "tag": "v0.9.13",
          "kind": "patch",
          "published_at": "2026-04-16T18:06:06Z"
        },
        {
          "tag": "v0.9.12-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-16T15:17:59Z"
        },
        {
          "tag": "v0.9.11",
          "kind": "patch",
          "published_at": "2026-04-16T05:34:33Z"
        },
        {
          "tag": "v0.9.10",
          "kind": "patch",
          "published_at": "2026-04-16T03:04:27Z"
        },
        {
          "tag": "v0.9.9",
          "kind": "patch",
          "published_at": "2026-04-15T23:34:33Z"
        },
        {
          "tag": "v0.9.8-beta.5",
          "kind": "prerelease",
          "published_at": "2026-04-15T23:15:27Z"
        },
        {
          "tag": "v0.9.8-beta.4",
          "kind": "prerelease",
          "published_at": "2026-04-15T23:03:59Z"
        },
        {
          "tag": "v0.9.8-beta.3",
          "kind": "prerelease",
          "published_at": "2026-04-15T21:22:50Z"
        },
        {
          "tag": "v0.9.8-beta.2",
          "kind": "prerelease",
          "published_at": "2026-04-15T21:20:34Z"
        },
        {
          "tag": "v0.9.8-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T21:18:01Z"
        },
        {
          "tag": "v0.9.7-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T21:10:39Z"
        },
        {
          "tag": "v0.9.6-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T20:35:35Z"
        },
        {
          "tag": "v0.9.5-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T20:05:13Z"
        },
        {
          "tag": "v0.9.4-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T19:12:57Z"
        },
        {
          "tag": "v0.9.3-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T18:36:43Z"
        },
        {
          "tag": "v0.9.2-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-15T18:23:36Z"
        },
        {
          "tag": "v0.9.2",
          "kind": "patch",
          "published_at": "2026-04-15T06:52:59Z"
        },
        {
          "tag": "v0.9.0",
          "kind": "minor",
          "published_at": "2026-04-15T02:28:09Z"
        },
        {
          "tag": "v0.8.10-beta.6",
          "kind": "prerelease",
          "published_at": "2026-04-15T02:30:01Z"
        },
        {
          "tag": "v0.8.10-beta.5",
          "kind": "prerelease",
          "published_at": "2026-04-15T01:14:36Z"
        },
        {
          "tag": "v0.8.10-beta.4",
          "kind": "prerelease",
          "published_at": "2026-04-14T23:01:25Z"
        },
        {
          "tag": "v0.8.10-beta.3",
          "kind": "prerelease",
          "published_at": "2026-04-14T19:03:38Z"
        },
        {
          "tag": "v0.8.10-beta.2",
          "kind": "prerelease",
          "published_at": "2026-04-14T17:36:49Z"
        },
        {
          "tag": "v0.8.10-beta.1",
          "kind": "prerelease",
          "published_at": "2026-04-14T16:05:31Z"
        },
        {
          "tag": "v0.8.9",
          "kind": "patch",
          "published_at": "2026-04-14T14:45:32Z"
        },
        {
          "tag": "v0.8.8",
          "kind": "patch",
          "published_at": "2026-04-14T05:40:38Z"
        },
        {
          "tag": "v0.8.5",
          "kind": "patch",
          "published_at": "2026-04-13T17:21:04Z"
        },
        {
          "tag": "v0.8.4",
          "kind": "patch",
          "published_at": "2026-04-13T16:24:51Z"
        },
        {
          "tag": "v0.8.3",
          "kind": "patch",
          "published_at": "2026-04-13T13:56:51Z"
        },
        {
          "tag": "v0.8.2",
          "kind": "patch",
          "published_at": "2026-04-13T06:51:01Z"
        },
        {
          "tag": "v0.8.1",
          "kind": "patch",
          "published_at": "2026-04-13T03:57:13Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2026-04-13T02:42:58Z"
        },
        {
          "tag": "v0.7.19",
          "kind": "patch",
          "published_at": "2026-04-12T14:34:38Z"
        },
        {
          "tag": "v0.7.18",
          "kind": "patch",
          "published_at": "2026-04-12T05:42:50Z"
        },
        {
          "tag": "v0.7.17",
          "kind": "patch",
          "published_at": "2026-04-11T23:45:19Z"
        },
        {
          "tag": "v0.7.16",
          "kind": "patch",
          "published_at": "2026-04-10T13:21:42Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "6ac926a165d1ff61276be827d35dc162488cdda7",
          "body": null,
          "is_bot": false,
          "headline": "Release v0.14.11",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T05:34:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9caf126bfdd87fe8300d533b24e3c6af337cf59f",
          "body": "…ility\n\nExplain RF-reachable stations in the station popup (#482)",
          "is_bot": false,
          "headline": "Merge pull request #486 from chrissnell/feature/rf-only-popup-reachab…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T05:22:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "31fb6828c3a1ef605b20549dc1ffeaccb23892ee",
          "body": "…me-padding\n\nPreserve object/item name padding in packet log display",
          "is_bot": false,
          "headline": "Merge pull request #481 from kotfu/fix/packet-log-collapses-object-na…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T05:03:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6336b5381056a0a65abe565a4a2a44629e4d19a2",
          "body": "Code review noted the popup note's tooltip claimed the latest packet\n\"arrived via APRS-IS\", but rfReachableDespiteNonRfLatest also fires when\nthe latest packet is our own TX (beacon heard back on RF) or Internet-to-RF\ngated. Reword the tooltip to the truthful, general \"did not arrive over RF\n(APRS-IS or Internet-to-RF gated)\". Add tests locking in the TX-latest fire\ncase and the null/empty-positions guards.",
          "is_bot": false,
          "headline": "web/map: make RF-reachable note copy accurate for all firing cases",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T04:58:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f63c2d8fc37a9f958c6e863a3e2905306dcf5257",
          "body": "…android-device-step\n\nFix Digirig Lite tone PTT greyed out on Android",
          "is_bot": false,
          "headline": "Merge pull request #380 from chrissnell/feature/fix-digirig-tone-ptt-…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T04:55:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "66c11c4775b331ed5c17d08edf4373742fd2165b",
          "body": "…hannel\n\nAdd per-message TX channel override to new-message dialog (#472)",
          "is_bot": false,
          "headline": "Merge pull request #477 from chrissnell/feature/compose-advanced-tx-c…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T04:54:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "90e9c5fc4da2ecd832c1d67c17452162f8e85f9d",
          "body": "Stop steering SignaLink USB users to serial PTT (GH #451)",
          "is_bot": false,
          "headline": "Merge pull request #454 from chrissnell/fix/signalink-vox-ptt-guidance",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T04:54:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1d390e25800db984ba74ea6af3d48fb21774a2dc",
          "body": "…inimum\n\nEnforce a consistent minimum password length between setup and web login",
          "is_bot": false,
          "headline": "Merge pull request #484 from chrissnell/feature/consistent-password-m…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T04:53:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "125b60ecfc7d0b7add70bdc7a4de0945b8e87bf0",
          "body": "docs: document --group-add audio for Docker sound-card access",
          "is_bot": false,
          "headline": "Merge pull request #485 from chrissnell/feature/docker-audio-group-docs",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T01:28:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a96dceb84248b9839c69a98c4e3d094adb9a0752",
          "body": "A station heard on RF at its current fix stays visible under the Live Map\n\"RF Only\" filter even after it re-beacons via APRS-IS, because the marker\nis plotted at positions[0], which stationcache pins to the most\nRF-reachable copy of that fix (rfRank). The popup badge/via line, however,\nread the late\n[…]\n,\n  node-testable module) with tests covering the IS-latest, gated-latest,\n  digipeated, and non-diverging cases.\n\nNo predicate change: RF Only still keys on positions[0]. Documented in\ninvariant #52.",
          "is_bot": false,
          "headline": "web/map: explain RF-reachable stations in the station popup",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T00:52:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "39e5d795de66b272fe27ffb19a27e97065dd38d8",
          "body": "The container image runs as a non-root user, so --device /dev/snd alone\nis not enough to open the sound-card device nodes (owned root:audio).\nWithout --group-add audio, Detect Devices finds nothing even though\narecord -l lists the card. Document this in the Docker install steps\n(run + compose) and add a troubleshooting callout on the Audio Devices\npage.",
          "is_bot": false,
          "headline": "docs: document --group-add audio for Docker sound-card access",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-17T00:45:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c9144037762da3edefe36d6c0b0d5e46d561416",
          "body": "The web login form required a minimum of 8 characters, but the setup\npath (POST /api/auth/setup and the `graywolf auth set-password` CLI)\naccepted any non-empty password. A short password set during setup was\nthen refused at the web login screen, locking operators out of the\nconfig UI (graywolf#476)\n[…]\nd, so accounts whose password\n  predates the minimum still authenticate.\n- Document the 8-character requirement in the handbook and package\n  post-install output, and record the invariant in the wiki.",
          "is_bot": false,
          "headline": "webauth: enforce a consistent minimum password length",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-16T23:38:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3a601bb1ec99391a62c0582c0b99529a20ced051",
          "body": "Revert \"Render a flat conventional map compass\"",
          "is_bot": false,
          "headline": "Merge pull request #483 from chrissnell/feature/revert-flat-map-compass",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-16T23:18:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "47e77fb99c9fbda44ea137a15f1c94fc0c2658ee",
          "body": "This reverts commit eec28712d19643e5321d9c929d27fd6db9568f59.",
          "is_bot": false,
          "headline": "Revert \"web: render a flat conventional map compass\"",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-16T22:55:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "24a519d736603e3dc69f012377911bff5da6bba8",
          "body": "The APRS Logs raw-packet line (.pkt-raw) used `white-space: normal`,\nwhich collapses runs of spaces to one. Object and Item reports space-pad\nthe 9-char name field, so a correctly-encoded `;PARK     *...` rendered\nas `;PARK *...`, making it look like the encoder had dropped the padding.\n\nSwitch to `\n[…]\n: pre-wrap` so the padding is preserved while the\nline still wraps at the container edge. Display-only change; the encoder\nwas already correct.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "Preserve object/item name padding in packet log display",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-07-15T01:04:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "842ff39cc3b04b3d3d9453ecc7be1df64941fb01",
          "body": "Render a flat conventional map compass",
          "is_bot": false,
          "headline": "Merge pull request #480 from chrissnell/feature/flat-map-compass",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T21:49:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eec28712d19643e5321d9c929d27fd6db9568f59",
          "body": "The live map compass used MapLibre's visualizePitch mode, which tilts the\nnorth needle into a 3D perspective as the map pitches. That reads as\nunusual next to the plain compass most maps use. Turn it off so the\nneedle stays flat and simply points north, matching the standard style.",
          "is_bot": false,
          "headline": "web: render a flat conventional map compass",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T21:41:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7655b068d1bc4feda08fb760ec7a3f87407b2579",
          "body": "Blue dot tracks live GPS on the Live Map",
          "is_bot": false,
          "headline": "Merge pull request #478 from chrissnell/feature/live-blue-dot-tracking",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T19:57:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "012a419b2347be01974e3dcad4361ec2a67ae747",
          "body": "Dedupe the own-position poll so a visibility catch-up firing mid-tick\ncannot race a scheduled poll and let the older of two near-identical\nfixes win. Document why the recenter effect's didAutoFit guard is now\nload-bearing (myPosition refreshes every tick).",
          "is_bot": false,
          "headline": "Guard against overlapping /api/position fetches",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T19:52:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e0ca5e8b2ce9a57a1869dc2914c87d92dba2b84b",
          "body": "- Fix multi-part sends transmitting later parts on the default channel:\n  ComposeBar calls onSend/close once per part, and resetting the channel\n  in close() zeroed it after part 1. Reset on the modal's open transition\n  instead so the choice is stable across the whole batch.\n- Reject a packet-mode channel override with 400 up front (same guard as\n  putMessagesConfig) instead of letting it fail RF or silently fall back\n  to APRS-IS. Add handler tests for the rejection and channel pass-through.",
          "is_bot": false,
          "headline": "Address code review on per-message TX channel override",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T18:29:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7893bf9142c11e2550544d82ed4315df428883a3",
          "body": "The Live Map fetched /api/position only once at page load, so the\nown-position marker froze at the fix present when the map opened and\nnever moved as the operator travelled -- even though the server tracks\nlive GPS continuously. Refresh the position on each poll tick (and on\nthe visibility catch-up) so the blue dot follows the current fix.\n\nFixes #473",
          "is_bot": false,
          "headline": "Poll own position so the map blue dot tracks live GPS",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T18:26:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "117d7ee3ffe93290b2e7c379c94a9f2abe17ea37",
          "body": "aur: add alsa-lib and protobuf to makedepends (#474)",
          "is_bot": false,
          "headline": "Merge pull request #475 from chrissnell/fix/aur-pkgbuild-build-deps",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T18:18:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "49902be817e9c81c2c1ac3b9828c8efd9a92360b",
          "body": "Operators running multiple radios on one instance can now pick which RF\nchannel an individual message transmits on, from a collapsed Advanced\nsection in the new-message dialog. Defaults to the configured TX channel\nso the normal compose flow is unchanged.\n\nThe chosen channel is persisted on the mess\n[…]\ne\nthe same channel as the initial send. The sender resolves the per-row\nchannel with fallback to the live default; packet-mode refusal and RF\navailability now check the effective channel.\n\nCloses #472",
          "is_bot": false,
          "headline": "Add per-message TX channel override to new-message dialog",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T18:16:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4ba08d76e014372a8017f038a6a2c0d4336f57d2",
          "body": "The graywolf-modem Rust crate builds against ALSA (via cpal) and generates\nprotobuf bindings with prost-build, which requires protoc at build time.\nWithout alsa-lib and protobuf in makedepends the AUR build fails.\n\nFixes #474",
          "is_bot": false,
          "headline": "aur: add alsa-lib and protobuf to makedepends",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-14T18:00:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "029550e4fa73130e0e7a6e1661b575640e3c79ad",
          "body": "Add Icom IC-7100 configuration for Raspberry Pi Zero 2W",
          "is_bot": false,
          "headline": "Merge pull request #471 from taco40sauce/add-ic7100-PI-ZERO-2W-config,",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-12T07:59:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a119b5c9459c6c9b7f2a0714cdb0c30e7d7dd411",
          "body": "Indent and space the separator comment to match the other cards, drop a\ntrailing-whitespace line, and add blank lines between config sections.",
          "is_bot": false,
          "headline": "Tidy whitespace in IC-7100 config card",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-12T07:58:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7ff0cbbec0e208cf9b1beb0080f3665b55b72006",
          "body": null,
          "is_bot": false,
          "headline": "debian download/install documentation improvements (#464)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-07-12T07:33:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5778665cc4a6fd2853c6721966dea65a83d3fcfa",
          "body": "Adds a known-working configuration for the Icom IC-7100 + Raspberry Pi Zero 2W running as a combined iGate + Digipeater. Includes audio device settings (with rationale for the -12dB output pad given the IC-7100's USB codec gain quirk), PTT via Hamlib rigctld/CAT (and a note on the correct vs. an eas\n[…]\nincludes a tested comparison of FM vs. FM-D modes (no measurable difference in this setup) and a note on the graywolf-modem subprocess's self-healing behavior during transient USB audio stream errors.",
          "is_bot": false,
          "headline": "Add Icom IC-7100 configuration for Raspberry Pi Zero 2W",
          "author_name": "taco40sauce",
          "author_login": "taco40sauce",
          "committed_at": "2026-07-12T00:28:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "629284a2445a93539e004bb8141f3b60d4f57825",
          "body": "The chonky Input carries a 1rem bottom margin from the stacked-form\ndefault. In the single-row add form, that inflates the input wrapper so\nalign-items:center dropped the Block button below the two fields. Zero\nthe input margin within .block-add so the fields and button align.",
          "is_bot": false,
          "headline": "Fix Block button alignment on Messages blocklist form (#470)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-11T00:20:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb3551cbf9646142bdf757782df7db5bf4681880",
          "body": "* Add call-sign blocklist for messages (#465)\n\nOperators can now mute inbound APRS messages from specific call signs.\nA blocked sender's traffic is dropped in the router's classify step\nbefore persistence and auto-ACK, so its messages never reach the inbox\nand graywolf never acknowledges them -- the\n[…]\n: \"int32\" on integer path params; CI pins swag@v1.16.4, which\nomits them, so docs-check flagged drift across many existing endpoints.\nRegenerate swagger.json/yaml and api.d.ts with the pinned version.",
          "is_bot": false,
          "headline": "Add call-sign blocklist for messages (#465) (#466)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-10T22:48:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "851851e1c124573f5dd6e880e8a6dd232c17213d",
          "body": "* Add per-interface KISS connected-mode passthrough\n\nKISS clients could only send UI frames; non-UI (connected-mode) AX.25\nframes were dropped (\"dropping non-UI frame from kiss client\"), so\nconnected-mode packet apps like Pat/Winlink could not use graywolf as a\nKISS modem even though the modem itsel\n[…]\n allow_connected_mode bypasses the aprs-only refusal that\nax25conn.Manager.Open enforces. Note the exception in invariant #23,\nexpand the kiss code-map entry, and tighten the ServerConfig doc comment.",
          "is_bot": false,
          "headline": "Expose connected-mode AX.25 through KISS (allow_connected_mode) (#469)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-10T22:33:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9bed72ee05a3eab27754781ca7586f665b386bee",
          "body": "…(#468)\n\n* Recover the live map from an unrecoverable WebGL context loss (#461)\n\nNavigating to the live map created a fresh MapLibre map (a new WebGL\ncontext) every visit and tore it down on leave. Browsers cap the number\nof live WebGL contexts per page, and maplibre-gl 5.x nulls map.style when\nit l\n[…]\n a\n  handful of unrelated context losses over a long operator session each\n  recover instead of exhausting the 3-try cap over the page lifetime.\n\n---------\n\nCo-authored-by: chrissnell <chris@0x1b.com>",
          "is_bot": false,
          "headline": "Recover the live map from an unrecoverable WebGL context loss (#461) …",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-10T22:31:01Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "61071245735d8e82017bd155a7d2523493b26b98",
          "body": "The per-conversation routing menu holds local send-path and\nresend-until-ack state. Svelte reused the component instance when only\nthreadKey changed, so opening one conversation's routing carried the\nprevious conversation's overrides. Wrap each mount in {#key threadKey}\nso switching conversations remounts the menu with default state.",
          "is_bot": false,
          "headline": "Fix conversation routing menu leaking state across threads (#460)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-08T06:49:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0274d03e82161753efc7e71b1756647725accfe0",
          "body": "Turn the unset-callsign indicator on the beacon card into a link to\n#/callsign so operators can jump straight to the page that sets the\nstation callsign. Keeps the danger-red styling and adds an underline\nto signal it is clickable.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Link '(Callsign not set)' to the callsign settings page (#459)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-07T05:22:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c8a3cdaff38081ffc053af9c159797f240b64fd2",
          "body": "The beacon card previously rendered a muted '(not set)' when neither\nthe beacon nor station callsign was configured. Make it read\n'(Callsign not set)' in the danger color so operators notice the\nmissing callsign.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Show '(Callsign not set)' in red when beacon callsign is unset (#458)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-07T05:08:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "06eb96eea5efb5a75962ee8f0fdbc84856f5f8ab",
          "body": "…#457)\n\nAddresses graywolf #456:\n\n- ax25conn sessions now self-remove from the manager on any terminal\n  transition into DISCONNECTED. Previously the session goroutine parked\n  in DISCONNECTED forever, leaking a goroutine and a manager slot on\n  every disconnect and causing a reconnect to the same p\n[…]\nari reports the GS control\n  character for e.key under Ctrl).\n- The Advanced timing and windowing settings (N2 retries, T1/T2/T3,\n  paclen, window, modulo, backoff) persist per-device across sessions.",
          "is_bot": false,
          "headline": "Fix AX.25 Terminal connect failures, stale session, and Ctrl-] menu (…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-07T02:14:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d9dafc6f63f5116c0c5eed33b7b26635925199ad",
          "body": "* Add per-conversation send path and resend control (GH #453)\n\nLet individual message threads override the global send path and disable\nack-driven resends, without cluttering the compose UI. Both knobs live in\na single gear popover in the thread header and default to inheriting the\nglobal MessagePre\n[…]\nols\n  disabled (and toasts) so the operator can't overwrite a stored override\n  with the default state shown while offline.\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Per-conversation send path & resend control (#453) (#455)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-06T22:34:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c735806c888780ae245e23152d05dd174a9c900c",
          "body": "The SignaLink USB has no serial port; it keys the radio with an internal\nVOX circuit driven by transmit audio. Two spots implied otherwise:\n\n- pkg/pttdevice/usb_devices.go named the CP2102 (10c4:ea60) chipset\n  \"(SignaLink, Digirig)\". That chip is the Digirig Mobile's; the\n  SignaLink USB contains n\n[…]\nSB as a serial-RTS\n  device.\n\nCorrect both, and add a VOX-section callout telling SignaLink users to\nselect the vox method (no PTT device), since that is the only way to key\na SignaLink from graywolf.",
          "is_bot": false,
          "headline": "Stop steering SignaLink USB users to serial PTT (GH #451)",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-06T21:06:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3631417c14e97cdc6ee4b76197e11468ec1dcba6",
          "body": "…(#435)\n\n* android: feed real status-bar inset to CSS so top bar clears it (GH #390)\n\nThe first GH #390 fix had the mobile top bar reserve the status-bar strip\nvia env(safe-area-inset-top), populated by viewport-fit=cover. That works\non iOS but not in Android WebView, which derives env(safe-area-ins\n[…]\np-side override covers it meanwhile.\n\nAlso use ceil instead of round when converting the inset to CSS px so a\nsub-pixel never under-reserves the strip and lets the bar creep back under\nthe status bar.",
          "is_bot": false,
          "headline": "Fix Android top nav bar rendering behind system status bar (GH #390) …",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-03T17:34:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "91e5d24b6056961a601082cf673d1554202f837d",
          "body": "Operators routinely read the 'Modem' label as 'my device is a modem' and\npick it for a hardware TNC/LoRa modem, which registers no TX backend and\nsilently fails transmit with 'no backend registered for channel'. Mode\nactually names which side provides the radio link, not the device kind.\n\n- Add pare\n[…]\nfrom Modem mode, naming the exact error they\n  would otherwise hit.\n\nMirrors the handbook Mode section. Web build and unit tests pass.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "ui(kiss): clarify Modem vs TNC mode wording (#448)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-02T18:22:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0eeb8ef68f3743278beb9dc594c3ce4547329024",
          "body": "… (#447)\n\nThe Modem/TNC mode choice is the most common KISS setup mistake: a\nhardware TNC or LoRa modem left in Modem mode connects and receives fine\nbut silently fails TX with 'txbackend: no backend registered for\nchannel', because Modem mode expects a software modem on the channel.\n\n- Rewrite the \n[…]\nwn-working\n  devices (USB-C, not GPIO UART), a udev symlink tip to pin the device\n  node, and the no-backend error to troubleshooting.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "docs(handbook): clarify KISS Modem vs TNC mode and hardware-TNC setup…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-02T18:22:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0af3cce9aed83ad865c1249a2ee9a9ab7d626149",
          "body": "* Add design spec for Live Map direct-RX heatmap layer\n\n* Add implementation plan for direct-RX heatmap layer\n\n* Add rx_events store and GET /api/heatmap endpoint\n\n* Add direct-RX heatmap layer to the Live Map\n\n* Document direct-RX heatmap layer in the wiki\n\n* Fix heatmap double-count and timer leak\n[…]\nne source of truth),\nand lastHBitDigi now skips generic path aliases to match CountHops so a\nreal digi isn't masked by a trailing WIDE*. Also clean up the heatmap\npolling timer + layer on map unmount.",
          "is_bot": false,
          "headline": "Add direct-RX heatmap layer to the Live Map (#446)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-02T17:45:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fc206518123e1043ee2e8857bcd8a31e262bca36",
          "body": "Explain that either Graywolf or the TNC can act as the digipeater/iGate\nbut not both, and recommend disabling digipeating on the TNC so Graywolf\nserves as the digi.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Clarify TNC-mode KISS digipeater warning (#442)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-07-01T17:31:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5436a28484ebe389fe5d2cd2ec2f36fd4f021d1e",
          "body": "A station's own beacon position only ever reached the Live Map's\nstation cache through the RF transmit path: the governor TX hook in\nwiring.go re-extracts each frame as it hits the air and feeds it into\nthe cache. An APRS-IS-only beacon (the natural config for a radioless\nor RX-only iGate) bypasses \n[…]\n\nin the wiki: a station's own beacon reaches the map via the transmit\npath, so every transmit leg must feed the station cache.\n\nCo-authored-by: chrissnell <1072626+chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Show APRS-IS-only beacons on the local map (#438) (#440)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-06-30T23:15:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e93c278359da6d1088d120e8cb70fecbe470da3",
          "body": "The release bump regenerated the spec with a swag build that emits\nformat: int32/float64 on numeric fields, which the docs-check drift\nguard rejects. The API is unchanged this release, so restore the\ncommitted spec to its canonical form.",
          "is_bot": false,
          "headline": "Restore canonical OpenAPI spec for v0.14.10",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-30T16:38:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b4beb66c9183125d7a0c72f0eeb9fa59eba03f53",
          "body": null,
          "is_bot": false,
          "headline": "Release v0.14.10",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-30T16:33:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "236412e098c62bfbdd16d600e2a405d4f417e444",
          "body": "AX.25 connected-mode BBSes are half-duplex and don't echo keystrokes;\nthe xterm canvas did no echo of its own, so operators couldn't see what\nthey typed. Restore the classic TNC ECHO ON behavior: echo operator\nkeystrokes locally on the outbound path (Enter->CRLF, backspace rubout,\ncontrol/escape seq\n[…]\nt\nunchanged.\n\nLocal echo defaults on per session and is toggleable via the Ctrl-]\n\"echo on|off\" command for the rare host that echoes.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Add local echo to AX.25 terminal so typed commands are visible (#441)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-30T16:25:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "65cec4c04209885c44405a75b9f12da7c9c50efc",
          "body": "* Prompt a reload when the server is upgraded under an open tab\n\nWhen an operator upgrades graywolf on the server while a browser tab is\nopen, the tab keeps running the old embedded SPA against the new server\nindefinitely. The client reads /api/version exactly once at load and\nnever re-checks, and t\n[…]\nition (which the existing reduced-motion guard suppresses).\n\n---------\n\nCo-authored-by: Claude Opus 4.8 <noreply@anthropic.com>\nCo-authored-by: chrissnell <1072626+chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Prompt a reload when the server is upgraded under an open tab (#433)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-06-30T16:01:56Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "342ff02806ab446efbdd7c0cef9ff66e99011ca8",
          "body": "* Fix auth set-password flag order in Windows docs and add CLI hint\n\nThe Windows install docs showed `graywolf.exe -config X auth set-password`,\nwhich fails: main.go only recognizes a subcommand as os.Args[1], so a flag\nplaced before it sends `auth ...` into the daemon flag parser, which reports\n\"un\n[…]\n it in app.Subcommands + IsSubcommand so the hint can't drift\nout of sync, and cross-reference it from the dispatch switch.\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Fix auth set-password flag order in Windows docs and add CLI hint (#439)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-30T15:50:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9d620e0723c2f021cd34dc2eadd16695f0fee54e",
          "body": "…agement tab (#411)\n\nCapture the operator's decisions: the MCP server uses the streamable-HTTP\ntransport (not stdio) so Graywolf owns its lifecycle; a modembridge-style\nsupervisor plus an mcp_server config singleton drive a persisted on/off\ntoggle; bind localhost by default with optional, token-gate\n[…]\nnd offers an\ninner tabbed, click-to-copy set of per-agent client configs (Claude Code,\nClaude Desktop, Cursor, VS Code, Windsurf, Cline, Zed, generic). Folds into\nM4; updates sections 4.5, 11, 12, 13.",
          "is_bot": false,
          "headline": "docs: settle MCP server deployment -- HTTP transport, supervisor, man…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-29T19:41:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f74ff5f005da082c8eba3099984f18dfd31ee788",
          "body": "Add a Monitoring section covering what the Bad FCS counter means, why it\nis a signal-quality trend rather than an exact packet count, how to judge\nwhether a value is high (ratio and trend, not raw number), and the\ndiagnostic steps for poor FCS performance.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "docs(handbook): explain the Bad FCS dashboard metric (#437)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-29T04:03:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7ac6a7be5c6a52bceb826fdc248436060de54973",
          "body": "* Make landscape mobile a first-class map layout\n\nOn small landscape phones the full desktop sidebar stayed visible,\neating most of the map width (GH #419). Treat landscape phones as a\ncompact layout: the sidebar collapses to a slim vertical icon rail on\nthe left, and the map uses the FAB + bottom-s\n[…]\nbottom resets so no portrait properties bleed through.\n- Add compactLayout.test.js pinning the breakpoint string, so a JS-side\n  edit that drifts from the CSS mirrors is a deliberate, reviewed change.",
          "is_bot": false,
          "headline": "Make landscape mobile a first-class map layout (#436)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-29T04:00:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "86d4ee1fc8a220f9179b1facc94f923e08240678",
          "body": "Object beacons were built with an empty timestamp argument, so the\nencoder substituted the literal \"111111z\" placeholder. That is a fixed\nvalue (\"the 11th at 11:11 UTC\"), so once wall-clock time drifts away\nfrom it, APRS-IS and APRS.fi reject the object as a delayed/out-of-order\npacket and it never \n[…]\nhavior is deterministic under test. ObjectInfo's empty-string fallback\nis left intact for back-compat; the builder now always supplies a value.\n\nCo-authored-by: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "Fix object beacons sending a fixed 111111z timestamp (#412) (#434)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-06-29T03:42:38Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "80fddd97cac6e57c2cb6394eba3ff5dc504676e0",
          "body": "The Rust modem already counts FCS failures per channel and ships them in\nStatusUpdate.rx_bad_fcs, but UpdateFromStatus dropped the field on the\nfloor, so bad frames never surfaced in /metrics.\n\nAdd graywolf_rx_bad_fcs_total{channel}, fed by the same absolute-counter\ndelta translation used for graywo\n[…]\nreport it; KISS-TNC\nchannels stay absent because a hardware TNC validates the FCS itself and\nnever forwards a bad frame over KISS (issue #132).\n\nCo-authored-by: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "Add per-channel bad-FCS Prometheus metric (#431)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-06-29T03:13:12Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "bfe0a040778653e47752c412c9f60aee4a994060",
          "body": "Comment out the surface-fronts overlay wiring on the Live Map so the\nlayer no longer mounts, polls, or appears in the Weather toggle list.\nThe fronts layer/source modules and polling helpers are left intact so\nthe layer can be re-enabled by uncommenting the marked blocks.",
          "is_bot": false,
          "headline": "Disable Fronts map layer (comment out for now) (#427)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-29T03:07:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "acad4a09d3c40e9b18d5738d3fbd003e97184492",
          "body": null,
          "is_bot": false,
          "headline": "Release v0.14.9",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T04:46:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e51ddfcfebf613363d5125e08996074ff5030ef9",
          "body": "* Expose tracker beacon type in the UI to enable SmartBeaconing\n\nSmartBeaconing only engages when a beacon's type is 'tracker' (the\nscheduler's isSmart gate), but the beacon setup form only offered\n'position' and 'object'. A user could enable the global SmartBeaconing\npanel and have a working GPS, y\n[…]\nrve params the API would reject.\n- Document the three-gate SmartBeaconing rule and the UI auto-enable\n  coupling as invariant #55 so future agents find it when debugging\n  'SmartBeaconing won't fire'.",
          "is_bot": false,
          "headline": "Expose tracker beacon type in the UI to enable SmartBeaconing (#428)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T04:35:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e0dfb4d5ca02d105f2b093d9b44af95d8f16449",
          "body": "… (#422)\n\n* fix(map): keep coordinate readout clear of status bar on narrow displays\n\nOn narrow viewports the centered map status bar grows wide enough to\nreach the bottom-right coordinate/zoom readout and hide it. Lift the\nreadout above the status bar below 900px so the two stack instead of\noverlapping.\n\nCloses #418\n\n* docs(map): cross-reference coupled status-bar/coord offsets\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Fix coordinate display overlapping map status text on narrow displays…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T04:32:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "88b1987827a87b59cbc7a4ee833125c0253e7831",
          "body": "…) (#426)\n\n* Fix map track doubling back on duplicate and out-of-order fixes\n\nThe station cache prepended every moved position fix in arrival order and\nonly deduplicated against the head fix. On APRS-IS and digipeated feeds,\nduplicate and out-of-order copies of a beacon are routine, so a delayed\ncop\n[…]\nust still be\ndeduplicated by location + window rather than prepended. This documents\nwhy the scan cannot be skipped for newest-timestamp fixes.\n\nNote the history-DB un-dedup behavior in invariant #53.",
          "is_bot": false,
          "headline": "Fix map track doubling back on duplicate and out-of-order fixes (#421…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T03:07:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b2a2cab5caf867bbe17f7230ccd5af244a7f49bf",
          "body": "…) (#425)\n\n* fix(map): keep callsign trail visible when newest fix leaves the viewport\n\nMap-viewport membership was decided on the head position (positions[0])\nalone, on both ends of the live-map pipeline. When a moving station's\nnewest fix left the visible bbox, the server stopped returning the\nsta\n[…]\n/empty safety. Locks in the server/client symmetry the\nGH #413 fix depends on (invariant #53).\n\nAddresses code-review follow-up: the server side had a regression test but\nthe client predicate did not.",
          "is_bot": false,
          "headline": "Keep callsign trail visible when newest fix leaves the viewport (#413…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T02:41:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "30824baa5eb72b13421f9f951b7b833d20b4288c",
          "body": "* Add manual coordinate entry to fixed point dialog\n\nThe fixed point dialog now shows editable latitude/longitude fields\npre-filled with the clicked location, so operators can type exact\ncoordinates instead of relying only on map clicks. Accepts decimal\ndegrees with an optional N/S/E/W hemisphere le\n[…]\npath.\nWire aria-invalid and aria-describedby on the inputs so the inline\nparse error is announced and tied to the fields. Add a round-trip test\nfor the seeded value plus whitespace/leading-plus cases.",
          "is_bot": false,
          "headline": "Add manual coordinate entry to fixed point dialog (#424)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T02:16:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cf1c2c4d33e1d2bb481d12737f6703a8ec569e30",
          "body": "Document the working configuration from issue #420: USB audio device\nselection with an 8000 Hz sample rate for the Pi Zero 2W, Serial RTS PTT\non /dev/ttyUSB0, the full set of recommended Baofeng UV-5R menu settings\nfor open-squelch APRS, and a step-by-step audio input level procedure.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "handbook: add Baofeng UV-5R + Digirig Mobile + Pi Zero 2W config (#423)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-28T02:06:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b2971cad78136db776eb0372edd86842ee8d406a",
          "body": "writing-plans output for phase M2 (GRA-206): the OsAudio capture-control\nlayer (level dB + mute) across Linux ALSA, Linux PipeWire/Pulse, macOS\nCoreAudio, and Windows WASAPI, plus the --capture-control/--set-capture-db/\n--set-capture-mute CLI. Tasks 1-5 (trait, Linux back ends, dispatch, CLI)\nare fu\n[…]\n7 (CoreAudio, WASAPI)\ngive the exact API contract and are verified on-platform. Grounded in the\nvendored alsa mixer (MilliBel/Selem), the public alsa_card_token mapping,\nand the in-tree windows crate.",
          "is_bot": false,
          "headline": "docs: M2 implementation plan -- OS audio control layer (#414)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T15:01:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e351352fd602d9f84a8a73ade90d752b452f2381",
          "body": null,
          "is_bot": false,
          "headline": "fix(map): color pressure centers L=blue, H=red per request (#415)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T14:53:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e21eded723f647914e42f9d6d5fe5e7a86424629",
          "body": "…orer) (#410)\n\n* test(modem): fix ptt build_driver test that fails to compile under cargo test\n\nbuild_result.unwrap_err() requires the Ok type (Box<dyn PttDriver>) to be\nDebug, which it isn't, so the lib-test target failed to compile. Use\n.err().expect() to extract the error without needing Debug on\n[…]\nandy\naudio-verification tool (no MCP mention).\n\nWiki (glossary.md): a Diagnostics entry for the offline record/decode helpers\npointing at src/record.rs / src/decode.rs / src/wavio.rs and the handbook.",
          "is_bot": false,
          "headline": "M1: graywolf-modem --record and --decode (offline capture + decode sc…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T14:17:42Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2e59a629914766b0e60dc7a129a1f0838b0d06e8",
          "body": "… (#407)\n\nAdds the writing-plans output for GRA-206: a phased roadmap (M1-M5) with\nPhase 1 (the graywolf-modem --record and --decode subcommands) fully\nspecified as bite-sized TDD tasks grounded in the crate (soundcard::spawn,\nAfskDemodulator, take_frames/take_bad_fcs, hound WAV I/O). M2-M5 get their\nown plans as each phase begins.",
          "is_bot": false,
          "headline": "docs: phased implementation plan for the audio auto-tuning MCP server…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T05:45:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dd7fb4171b397cfa5f0f0eb09be1b20fc8faede9",
          "body": "…8)\" (#409)\n\nThis reverts commit 8c3c69fae5d4f4d6094bd33a38f2a7e255b1877b.",
          "is_bot": false,
          "headline": "Revert \"Dashboard: make packet rates a 10-minute rolling average (#40…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T05:10:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1bb6e3b9041b1ad2f49e9f19fc3c395bb23f5880",
          "body": "…(#402)\n\n* feat(map): thicker front lines, bigger pips, drop bare stationary fronts; group layers pane\n\nFronts: line-width 1.2-2.6 -> 1.8-3.8 (troughs' dashes scale with it), pip\nicon-size 0.7-1.0 -> 0.95-1.35 with a touch more spacing. Hide stationary\nfronts entirely -- without alternating opposite\n[…]\n Curvature thresholds are live-tunable (tune({pipWindow,\npipMaxTurn})). 11 web tests pass (incl. a hairpin-exclusion test).\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "feat(map): front styling tweaks + layers-pane reorg (APRS / Weather) …",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:55:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8c3c69fae5d4f4d6094bd33a38f2a7e255b1877b",
          "body": "Replace the lifetime average packets/hour figure with a trailing\n10-minute window: sample the cumulative RX/TX counters on each status\npoll and divide the delta by elapsed wall-clock time, so the rate\ntracks recent activity. Counter resets (station restart) and dropped\nconnections restart the window. Label the figure '10m avg' with a\ntooltip so operators know what it represents.\n\nWindow math is extracted to lib/packetRate.js with unit tests.",
          "is_bot": false,
          "headline": "Dashboard: make packet rates a 10-minute rolling average (#408)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:53:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fa6519874526a8875f45d196fe368356f484128b",
          "body": "…int (#406)\n\nThe hosted-runner image preinstalls Microsoft/Azure apt repos that\nintermittently return 403 ('no longer signed') and fail apt-get update,\nwhich broke the Example Scripts Lint job (and any PR that ran it) while\nonly shellcheck from Ubuntu's own mirror was needed. Remove those\nthird-party source lists before update so an unrelated outage can't fail\nthe job; Ubuntu's own deb822 sources are untouched.",
          "is_bot": false,
          "headline": "ci: stop Microsoft/Azure apt repos from flaking the example-scripts l…",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:48:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c007d18056fbc980d5c99eaf24a63f64de35415",
          "body": "Display average packets/hour, derived from the cumulative RX/TX\ncounters and station uptime, below the Packets RX and Packets TX\ntotals on the dashboard.",
          "is_bot": false,
          "headline": "Dashboard: show packet RX/TX rates beneath total counts (#403)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:42:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a0e314dd8f87587401662092452e5e9d0bc744ec",
          "body": "…(#404)\n\n* feat(map): render model-derived global fronts beneath the WPC overlay\n\nAdds a second GeoJSON source (fronts-world, /fronts/world/latest.geojson) under\nthe existing single Fronts toggle, drawn beneath the WPC layers so analyst\nfronts win over North America and the model shows through elsew\n[…]\nnd a new test\n  asserts every world layer renders beneath every WPC layer; refresh() test now\n  also checks the world source/layers re-add after a style swap.\n- Hoist the doubled firstSymbolId() call.",
          "is_bot": false,
          "headline": "Render model-derived global fronts overlay (beneath WPC, one toggle) …",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:36:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "800740b0ae7afff66ff83791c77b1d83528a1b3d",
          "body": "Design spec for an MCP server that auto-tunes RX audio levels for optimal APRS packet decoding (GRA-206). Docs-only.",
          "is_bot": false,
          "headline": "Design: cross-platform audio auto-tuning MCP server (#405)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-25T04:29:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "830ecb8faedfc574f6c87a378c7b2e6c2e63d6a1",
          "body": "* Detect Windows audio enhancements and warn in Audio Devices tab\n\nWindows \"audio enhancements\" (system effects / APOs such as Loudness\nEqualization, Bass Boost, and noise suppression) sit between the radio\nand the modem and corrupt the AFSK/packet waveform, degrading or\nbreaking decoding. The modem\n[…]\navailable -> UI, which surfaces a\nbanner, a per-detected-card badge, and a per-configured-device warning.\nAlways false on Linux/macOS.\n\n* Document Windows audio enhancements detection in code-map wiki",
          "is_bot": false,
          "headline": "Detect Windows audio enhancements and warn in Audio Devices tab (#401)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T22:12:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8a90baa89fbde6e4e587ee565f282c8a7954f53c",
          "body": "* feat(map): surface-fronts overlay with WMO symbology + layers-pane toggle\n\nGeoJSON source + line/pip/H-L symbol layers (cold triangles, warm semicircles,\ndashed troughs, H/L glyphs with pressure labels), a Fronts toggle defaulted on,\nand full LiveMapV2 wiring with a 5-min manifest poll. Stationary\n[…]\neach sprite (front-cold/warm/occluded) at\nregistration with sdf:false and drop icon-color. 380 web tests pass; build green.\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Surface fronts overlay — map client layer + toggle (#393)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T21:53:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6849d1769faba7fa771d58467912e437d5d9538b",
          "body": "…(#391)\n\n* android: keep top bar below the status bar on edge-to-edge (GH #390)\n\nThe v0.14.8 edge-to-edge change padded the WebView by the top system-bar\ninset, but the SPA's mobile top bar is position:fixed; top:0. A fixed\nelement is pinned to the visual viewport, which WebView top-padding does\nnot\n[…]\nn the\nremote-actions drawer, which is position:fixed; top:0 and full-width on\nphones. Reserve the status-bar and nav-bar safe areas in CSS so its header\nand close button stay clear of the system bars.",
          "is_bot": false,
          "headline": "android: keep top bar below the status bar on edge-to-edge (GH #390) …",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T21:13:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "abddd559ec47f53a51e5757d9293c2cf1afe0364",
          "body": "* Fix RF Only map filter still showing APRS-IS stations\n\nThe RF Only predicate scanned a station's entire accumulated position\ntrail and kept it visible if any fix was ever heard on RF. The live map\ndata store builds that trail by prepending each delta fix, so a mobile\nstation heard on RF earlier an\n[…]\nthat RF Only keys on positions[0] precisely to keep that RF-reachable\nstatic station visible, and that the popup badge may then read APRS-IS.\n\nAdd tests covering TX exclusion and gated-wins-over-hops.",
          "is_bot": false,
          "headline": "Fix RF Only map filter still showing APRS-IS stations (#394) (#399)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T17:02:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d84897c42a555819daf79f2f844410fe903ba025",
          "body": "The iGate TX Channel picker only listed channels and filtered them by\nTX-capability, so there was no way to select \"none\". An operator who\nset up a TX channel for testing and then removed the output device\ncould not revert the iGate to RX-only: the saved tx_channel kept\npointing at a now-non-TX-capa\n[…]\nthe loaded\ntx_channel onto the first available channel so a saved 0 round-trips\nas none instead of silently re-arming TX.\n\nCloses #396\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Allow clearing the iGate TX channel to RX-only (#400)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T16:51:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "98b8a404a4c906532b1a6ec272848c585f49f120",
          "body": "* Fix AX.25 terminal line overlap by normalizing inbound CR/LF\n\nPacket-radio BBSes terminate lines inconsistently: FBB/TNC hosts emit a\nbare CR, JNOS/Linux hosts emit bare LF, and some emit CRLF. xterm runs\nwith convertEol:false (it only rewrites bare LF, never bare CR), so a\nbare-CR stream returned\n[…]\nunks so a CRLF split at a\nWebSocket chunk boundary stays a single line break.\n\n* docs(wiki): note terminal CR/LF normalizer in code map\n\n* docs(wiki): note bare-CR overwrite is intentionally collapsed",
          "is_bot": false,
          "headline": "Fix AX.25 terminal line overlap (CR/LF normalization) (#397)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T16:48:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "92399cbd0484274bef79554b945e9bd35e428513",
          "body": "The time-range selector on the live map seeded its value from the data\nstore default (always 1 hour) and never wrote selections back, so a\nnon-default range was lost on reload. Seed it from the persisted\nmapState.timerange preference instead and write changes back to it.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Persist map layers time range across navigation and reload (#398)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-24T16:37:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "02df65ba7d12630157a0078124a19eabc11631de",
          "body": "* Add digirig lite config from Jacob KB3OMS\n\n* fix errant id",
          "is_bot": false,
          "headline": "Add digirig lite config from Jacob KB3OMS (#389)",
          "author_name": "kotfu",
          "author_login": "kotfu",
          "committed_at": "2026-06-23T03:30:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8233012529c5f8b073275f39d145c71463b5857f",
          "body": null,
          "is_bot": false,
          "headline": "Regenerate OpenAPI spec with swag v1.16.4 to fix drift guard",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T23:59:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "df7cc50e267843d6ee1e68f8cb0b7f392e910656",
          "body": null,
          "is_bot": false,
          "headline": "Release v0.14.8",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T23:45:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9de8f9925d6c513913cd100917150553f86b4ac7",
          "body": "* Add a Hex bytes toggle for non-printable bytes in the packet log\n\nThe non-printable byte tokens (<0x7f>) from #384 are noise for everyday\nlog reading, useful only when diagnosing a malformed packet. Gate them\nbehind a device-local 'Hex bytes' toggle in the log viewer toolbar,\nalongside auto-refres\n[…]\noutweighs Chonky's rule\nafter Svelte scoping. The byte still reads distinctly via the danger\ncolour and semibold weight, just quietly inline.\n\n* Rename the non-printable toggle label to Non-ASCII data",
          "is_bot": false,
          "headline": "Add a Hex bytes toggle for non-printable bytes in the packet log (#388)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T23:14:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d6eb8159e7af194f5246a862dd91cf9133c66ff5",
          "body": "* Add auto-refresh and auto-scroll toggles to APRS Logs\n\nThe packet log polled and followed new packets unconditionally, so a full\nbuffer scrolled and rewrote rows under the operator while they were reading\na packet path -- worst on mobile where only a few rows are visible (#373).\n\nAdd two device-lo\n[…]\nen carries over. Auto-refresh here gates\nonly the packet feed -- the status cards and position keep polling -- so\nthe operator can freeze the log to read it without stalling the rest of\nthe dashboard.",
          "is_bot": false,
          "headline": "Add auto-refresh and auto-scroll toggles to APRS Logs (#386)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T22:11:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "71c0244e4b74dfeba5924f5e73ac7d4d138badd7",
          "body": "* Surface Mic-E beacon comments (GH #377)\n\nparseMicE decoded the free-form comment into MicE.Status but never\ncopied it to pkt.Comment, which is the only field the stationcache and\nmobile UI read -- so mobile-beacon comments (e.g. 'KK4CUK Matt's Cozy')\ndecoded correctly yet rendered blank while aprs\n[…]\nced to assert the base-91 DAO wedged in\nthe telemetry tail is consumed and applied, not merely stripped, and\ndocument that a lone unterminated '|' is treated as a truncated\ntelemetry opener by design.",
          "is_bot": false,
          "headline": "Surface Mic-E mobile-beacon comments (GH #377) (#385)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T20:22:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b7b0b439592e358b8ee3ac66529ff97828e12edb",
          "body": "* Surface non-printable bytes in the APRS packet log (GH #376)\n\nMalformed packets can carry control bytes that render invisibly in the\nlog line yet break decoding -- most famously a 0x7F (DEL) wedged into a\nposition beacon, which silently prevents map plotting. Render each\nnon-printable byte inline \n[…]\nas defensive. Give each\ntoken an accurate per-byte tooltip sourced from the segment, so a\nU+FFFD token reads \"invalid byte (replaced with U+FFFD in transit)\"\ninstead of implying a literal byte 0xFFFD.",
          "is_bot": false,
          "headline": "Surface non-printable bytes in the APRS packet log (GH #376) (#384)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T20:19:13Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7ac573f1e456d5f97e916c5aace03c72fb8e2de1",
          "body": "The default light theme used a bright #ffaa00 for --color-primary, which\ndoubles as a foreground color (links, chonky-ui's outlined btn-primary at\nrest, message leads, status icons). On the light surfaces it sat on, that\nyellow-orange fell to ~1.7:1 contrast on the grey button background -- well\nbel\n[…]\n while clearing WCAG\nAA as text on every light surface (>=4.6:1) and under white fill text\n(5.3:1). graywolf-night is unaffected -- bright orange on its near-black\nbackground is already high-contrast.",
          "is_bot": false,
          "headline": "Improve graywolf default-theme primary contrast (#381)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T18:24:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "df5070908052f5d7cf6bf7a691e5e712251adeba",
          "body": "* Explain how to test keying for wireless PTT methods\n\n\"Test PTT\" on a PTT card manually pulses the PTT wire, but VOX and Digirig\ntone PTT have no wire -- their modem PTT driver is a no-op, so the button\ndid nothing and was rendered permanently greyed. Operators read the dead\nbutton as a broken cont\n[…]\nflags (isToneKeyed, canTestPttWire) so the Test PTT\nbutton and Test TX hint can't drift out of sync when a method is added.\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Explain how to test keying for VOX / Digirig tone PTT (#383)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T17:48:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9ab8058221a4e6e0f380a8b52f2fe78b298ff95d",
          "body": "* android: keep message compose bar above the soft keyboard\n\nOn Android 15+ (targetSdk 36) edge-to-edge is mandatory and the system no\nlonger resizes the window for the soft keyboard, so the Messages tab compose\nbar sat behind the on-screen keyboard. Handle window insets in MainActivity:\nopt into ed\n[…]\nchrome_bg, used by both the theme's\n  statusBarColor and the WebView background.\n- Clarify the MainActivity comment that API 28-29 relies on adjustResize, not\n  the inset path (Type.ime() is 0 there).",
          "is_bot": false,
          "headline": "android: keep message compose bar above the soft keyboard (#379)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T15:42:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0cfab51ab1dcc6cc7d2e434ba3a54d9cca001339",
          "body": "Co-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Recommend Serial RTS PTT for Digirig Mobile (#378)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T15:18:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c8200250c52f6783e11acb0baaf856045df8d0db",
          "body": "The Android \"Digirig Lite (tone PTT)\" method keys the radio via an audio\ntone and needs no serial/HID device, but needsDevice() only skipped the\ndevice-selection step for VOX (ptt_method 4), not tone PTT (ptt_method 5).\nSelecting it pushed the operator into the device picker, which lists no\ndevices \n[…]\nod with no deviceClass, leaving Save permanently\ndisabled so the method could never be applied.\n\nTreat ptt_method 5 like VOX and like desktop's digirig_tone: skip the\ndevice step and persist directly.",
          "is_bot": false,
          "headline": "Fix Digirig Lite tone PTT greyed out on Android",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-22T14:56:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8594e323ed5583bfcd7000bd309c233ec5e52aa7",
          "body": "When the operator opens the live map while already offline, the basemap\nstyle fetch fails, so maplibre-map never fires oncreate, dataStore.start()\nnever runs, and pollingState stays at its initial 'idle' -- the status bar\nshowed a misleading green dot + 'idle'. PR #369's start() seed of\npollingState\n[…]\nfailures into the shared\nconnection store so the offline state is detected even when the map is\nthe first screen to touch the network.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Map status bar: show error dot when offline at mount (GH #374) (#375)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T20:12:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8437c641a0085301477dfcef1025894dc90ebb9d",
          "body": null,
          "is_bot": false,
          "headline": "Release v0.14.7",
          "author_name": "chrissnell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T16:16:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "aaf791841335561f20dd1239160b72daa926a8fa",
          "body": "Co-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Move Mosiah quote between This install and Updates on About tab (#371)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T16:03:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "da56ff2af4a7c725dd2061d980f09945c9771c51",
          "body": "Co-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Move APRS log locate icon to the left of the source callsign (#372)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T16:02:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bf6805ebf8e2c0d6eed75de29eff9219b6338d6f",
          "body": "The pinned fork rev decoded the htstamp buffer assuming the time64\nlayout unconditionally, returning garbage on pre-t64 (time32) libasound\nand flooding cpal with 'get_htstamp was earlier than get_trigger_htstamp'\nerrors / stream rebuilds on Ubuntu 22.04 armv7l (#336). Bump to rev\n56099e8, whose decode is ABI-aware, so one time32 binary is correct on\nboth pre-t64 and t64 userlands. Updates Cargo.lock and documents the\ninverse regression in the design doc.",
          "is_bot": false,
          "headline": "modem: bump alsa-rs fork to ABI-aware htstamp decode (#336) (#370)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T15:12:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b392a9adc53780c6e85a2c31c33e9515a5c06cce",
          "body": "* Show lost-connection indicator instead of fabricated data (#365)\n\nWhen the browser loses contact with the server, the REST client\n(lib/api.js) used to silently fall back to dev mock data on any network\nfailure, so the Dashboard and APRS Logs rendered fake channels, a fake\nposition, and fake beacon\n[…]\nows (incl. 401) are reachable\n  and already marked connected.\n- Logs.svelte: replace the now-stale \"mock fallback\" comment.\n\n---------\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Show lost-connection indicator instead of fabricated data (#365) (#369)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T15:11:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "30b91c0923c8c24254dcc7baa0ccdd19fc5d7895",
          "body": "The offline-map slug grammar reserves the bare slug `world` for the\nsingle global archive; every other slug is namespaced. The two startup\nmigrations that namespace legacy bare slugs predate the world archive and\nrewrote a downloaded `world` row+file to `state/world` on every restart.\nOffline render\n[…]\nrateLegacyArchives,\nand repair any already-mangled `state/world` row and file back to\n`world`. Adds tests and documents the invariant.\n\nCo-authored-by: chrissnell <chrissnell@users.noreply.github.com>",
          "is_bot": false,
          "headline": "Fix world map showing Download button after download (GH #364) (#368)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T14:32:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5ea9e55903f585001812a01d2585d26594a79058",
          "body": "* Fix false 'clock not synchronized' warning on NTP-synced hosts\n\nThe startup clock-sync check classified on the adjtimex STA_UNSYNC status\nbit, which is an unreliable signal for whether a time source is\ndisciplining the clock. chrony leaves STA_UNSYNC set unless the rtcsync\ndirective is configured,\n[…]\nl status' across daemons and\nconfigurations, and matches the diag collector, which already reads\ntimedatectl's NTPSynchronized.\n\n* clocksync: express test out-of-range values relative to the threshold",
          "is_bot": false,
          "headline": "Fix false 'clock not synchronized' warning on NTP-synced hosts (#366)",
          "author_name": "Chris Snell",
          "author_login": "chrissnell",
          "committed_at": "2026-06-20T14:28:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 100,
      "commits_last_year": 1925,
      "latest_release_at": "2026-07-17T05:42:34Z",
      "latest_release_tag": "v0.14.11",
      "releases_from_tags": false,
      "days_since_last_push": 4,
      "active_weeks_last_year": 17,
      "days_since_latest_release": 5,
      "mean_days_between_releases": 3.3
    },
    "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/chrissnell/graywolf",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "go",
          "matches_repo": true,
          "registry_url": "https://pkg.go.dev/github.com/chrissnell/graywolf",
          "is_deprecated": false,
          "latest_version": "v0.14.11",
          "repository_url": "https://github.com/chrissnell/graywolf",
          "versions_count": 119,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": null,
          "latest_published_at": "2026-07-17T05:34:54Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        }
      ]
    },
    "popularity": {
      "forks": 19,
      "stars": 177,
      "watchers": 12,
      "fork_history": {
        "days": [
          {
            "date": "2026-04-19",
            "count": 1
          },
          {
            "date": "2026-04-23",
            "count": 1
          },
          {
            "date": "2026-04-25",
            "count": 1
          },
          {
            "date": "2026-04-27",
            "count": 1
          },
          {
            "date": "2026-05-01",
            "count": 1
          },
          {
            "date": "2026-05-04",
            "count": 1
          },
          {
            "date": "2026-05-06",
            "count": 1
          },
          {
            "date": "2026-05-10",
            "count": 1
          },
          {
            "date": "2026-05-19",
            "count": 1
          },
          {
            "date": "2026-05-21",
            "count": 1
          },
          {
            "date": "2026-06-05",
            "count": 1
          },
          {
            "date": "2026-06-06",
            "count": 1
          },
          {
            "date": "2026-06-11",
            "count": 1
          },
          {
            "date": "2026-06-14",
            "count": 1
          },
          {
            "date": "2026-06-15",
            "count": 1
          },
          {
            "date": "2026-06-22",
            "count": 1
          },
          {
            "date": "2026-07-10",
            "count": 1
          },
          {
            "date": "2026-07-11",
            "count": 1
          },
          {
            "date": "2026-07-22",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 19,
        "total_forks": 19
      },
      "star_history": null,
      "open_issues_and_prs": 20
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": true,
      "has_mcp_signal": false,
      "bootstrap_files": [
        "Makefile"
      ],
      "api_schema_files": [
        "docs/handbook/openapi.json",
        "docs/handbook/openapi.yaml",
        "pkg/webapi/docs/gen/swagger.json",
        "pkg/webapi/docs/gen/swagger.yaml",
        "proto/graywolf.proto",
        "proto/platform.proto"
      ],
      "has_devcontainer": false,
      "typecheck_configs": [
        "web/src/api/tsconfig.json"
      ],
      "toolchain_manifests": [
        "Cargo.toml",
        "android/app/build.gradle.kts",
        "android/build.gradle.kts",
        "go.mod",
        "graywolf-modem/Cargo.toml"
      ],
      "largest_source_bytes": 117468,
      "source_files_sampled": 1099,
      "oversized_source_files": 6,
      "agent_instruction_files": [
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 4522
    },
    "dependencies": {
      "manifests": [
        "Cargo.toml",
        "Gemfile",
        "android/build.gradle.kts",
        "go.mod",
        "graywolf-modem/Cargo.toml",
        "web/package.json"
      ],
      "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": [
        "crates",
        "go",
        "maven",
        "npm",
        "rubygems"
      ],
      "dependencies": [
        {
          "name": "fastlane",
          "manifest": "Gemfile",
          "ecosystem": "rubygems",
          "version_constraint": null
        },
        {
          "name": "github.com/coder/websocket",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.8.14"
        },
        {
          "name": "github.com/glebarez/go-sqlite",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.21.2"
        },
        {
          "name": "github.com/glebarez/sqlite",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.11.0"
        },
        {
          "name": "github.com/invopop/jsonschema",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.14.0"
        },
        {
          "name": "github.com/pquerna/otp",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.5.0"
        },
        {
          "name": "github.com/prometheus/client_golang",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.23.2"
        },
        {
          "name": "github.com/prometheus/client_model",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.6.2"
        },
        {
          "name": "github.com/warthog618/go-gpiocdev",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.9.1"
        },
        {
          "name": "go.bug.st/serial",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.6.4"
        },
        {
          "name": "go.uber.org/goleak",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.3.0"
        },
        {
          "name": "golang.org/x/crypto",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.49.0"
        },
        {
          "name": "golang.org/x/sys",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.42.0"
        },
        {
          "name": "google.golang.org/protobuf",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.36.11"
        },
        {
          "name": "gopkg.in/yaml.v3",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v3.0.1"
        },
        {
          "name": "gorm.io/gorm",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.31.1"
        },
        {
          "name": "chrono",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "0.4"
        },
        {
          "name": "claxon",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "0.4"
        },
        {
          "name": "hound",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "3"
        },
        {
          "name": "prost",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "0.13"
        },
        {
          "name": "bytes",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "1"
        },
        {
          "name": "cpal",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "0.17"
        },
        {
          "name": "serde",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "1"
        },
        {
          "name": "serde_json",
          "manifest": "graywolf-modem/Cargo.toml",
          "ecosystem": "crates",
          "version_constraint": "1"
        },
        {
          "name": "@americana/maplibre-shield-generator",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.1.1"
        },
        {
          "name": "@chrissnell/chonky-ui",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.4.2"
        },
        {
          "name": "@internationalized/date",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^3.8.0"
        },
        {
          "name": "@xterm/addon-search",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.16.0"
        },
        {
          "name": "@xterm/addon-serialize",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.14.0"
        },
        {
          "name": "@xterm/addon-web-links",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.12.0"
        },
        {
          "name": "@xterm/xterm",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^6.0.0"
        },
        {
          "name": "bits-ui",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^2.0.0"
        },
        {
          "name": "lucide-svelte",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.577.0"
        },
        {
          "name": "maplibre-gl",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^5.24.0"
        },
        {
          "name": "openapi-fetch",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "0.17.0"
        },
        {
          "name": "pmtiles",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^4.4.1"
        },
        {
          "name": "qrcode",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.5.4"
        },
        {
          "name": "svelte-spa-router",
          "manifest": "web/package.json",
          "ecosystem": "npm",
          "version_constraint": "^4.0.0"
        }
      ],
      "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": 8,
        "merged_prs": 296,
        "open_issues": 12,
        "closed_ratio": 0.931,
        "closed_issues": 162,
        "closed_unmerged_prs": 13
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "chrissnell",
          "commits": 1907,
          "avatar_url": "https://avatars.githubusercontent.com/u/1072626?v=4"
        },
        {
          "type": "User",
          "login": "kotfu",
          "commits": 9,
          "avatar_url": "https://avatars.githubusercontent.com/u/218536?v=4"
        },
        {
          "type": "User",
          "login": "fromalexx",
          "commits": 5,
          "avatar_url": "https://avatars.githubusercontent.com/u/1144391?v=4"
        },
        {
          "type": "User",
          "login": "SteveHawk",
          "commits": 1,
          "avatar_url": "https://avatars.githubusercontent.com/u/22629232?v=4"
        },
        {
          "type": "User",
          "login": "taco40sauce",
          "commits": 1,
          "avatar_url": "https://avatars.githubusercontent.com/u/80659081?v=4"
        }
      ],
      "contributors_sampled": 5,
      "top_contributor_share": 0.992
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "android.yml",
        "armhf-t64-htstamp.yml",
        "ci.yml",
        "fuzz.yml",
        "release.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "Cargo.lock",
        "go.sum",
        "package-lock.json"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 9,
            "reason": "binaries present in source code",
            "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": 9,
            "reason": "14 out of 15 merged PRs checked by a CI test -- score normalized to 9",
            "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": 1,
            "reason": "Found 3/16 approved changesets -- score normalized to 1",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 6,
            "reason": "project has 2 contributing companies or organizations -- score normalized to 6",
            "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": 10,
            "reason": "project is fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 1,
            "reason": "dependency not pinned by hash detected -- score normalized to 1",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 0,
            "reason": "33 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "6ac926a165d1ff61276be827d35dc162488cdda7",
        "ran_at": "2026-07-23T01:15:13Z",
        "aggregate_score": 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-07-22T10:17:12Z",
      "oldest_open_prs": [
        {
          "number": 98,
          "created_at": "2026-05-08T19:13:13Z",
          "last_comment_at": "2026-05-10T21:33:22Z",
          "last_comment_author": "SteveHawk"
        },
        {
          "number": 191,
          "created_at": "2026-05-24T16:57:51Z",
          "last_comment_at": "2026-05-28T04:38:20Z",
          "last_comment_author": "chrissnell"
        },
        {
          "number": 192,
          "created_at": "2026-05-27T04:01:00Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 345,
          "created_at": "2026-06-19T03:22:52Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 444,
          "created_at": "2026-07-02T13:38:03Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 449,
          "created_at": "2026-07-04T02:55:50Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 487,
          "created_at": "2026-07-18T18:01:12Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 491,
          "created_at": "2026-07-22T19:05:40Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ],
      "last_merged_pr_at": "2026-07-17T05:22:44Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 53,
          "created_at": "2026-05-02T03:36:56Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 143,
          "created_at": "2026-05-17T01:51:31Z",
          "last_comment_at": "2026-05-30T18:17:14Z",
          "last_comment_author": "pflarue"
        },
        {
          "number": 208,
          "created_at": "2026-05-30T14:48:16Z",
          "last_comment_at": "2026-06-16T23:18:22Z",
          "last_comment_author": "ropucyka"
        },
        {
          "number": 429,
          "created_at": "2026-06-28T16:36:06Z",
          "last_comment_at": "2026-07-06T18:28:45Z",
          "last_comment_author": "MaineWebDad"
        },
        {
          "number": 445,
          "created_at": "2026-07-02T13:52:35Z",
          "last_comment_at": "2026-07-06T21:04:47Z",
          "last_comment_author": "chrissnell"
        },
        {
          "number": 452,
          "created_at": "2026-07-06T05:11:10Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 456,
          "created_at": "2026-07-06T22:44:23Z",
          "last_comment_at": "2026-07-18T18:43:04Z",
          "last_comment_author": "chrissnell"
        },
        {
          "number": 479,
          "created_at": "2026-07-14T20:19:30Z",
          "last_comment_at": "2026-07-17T14:06:23Z",
          "last_comment_author": "ragincajun93"
        },
        {
          "number": 482,
          "created_at": "2026-07-15T15:09:40Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 488,
          "created_at": "2026-07-22T18:14:06Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 489,
          "created_at": "2026-07-22T18:14:07Z",
          "last_comment_at": "2026-07-22T19:26:09Z",
          "last_comment_author": "kotfu"
        },
        {
          "number": 490,
          "created_at": "2026-07-22T18:14:08Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/chrissnell/graywolf",
    "host": "github.com",
    "name": "graywolf",
    "owner": "chrissnell"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 62,
      "inputs": {
        "security": 40,
        "vitality": 82,
        "community": 52,
        "governance": 63,
        "engineering": 66
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 82,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 76,
            "inputs": {
              "commits_last_year": 1925,
              "human_commit_share": 1,
              "days_since_last_push": 4,
              "active_weeks_last_year": 17
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 4 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "17/52 weeks with commits",
                "points": 11.8,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 17
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "1925 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 1925
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 100,
              "latest_release_tag": "v0.14.11",
              "releases_from_tags": false,
              "days_since_latest_release": 5,
              "mean_days_between_releases": 3.3
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "100 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 100
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~3.3 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 3.3
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "moderate",
        "name": "Community & Adoption",
        "value": 52,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "moderate",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 53,
            "inputs": {
              "forks": 19,
              "stars": 177,
              "watchers": 12,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "177 stars",
                "points": 36.4,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 177
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "19 forks",
                "points": 10.5,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 19
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "12 watchers",
                "points": 5.8,
                "status": "partial",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 12
                    }
                  }
                ],
                "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 (GPL-2.0)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "GPL-2.0"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 63,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 22,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 5,
              "top_contributor_share": 0.992
            },
            "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 99% of commits",
                "points": 0.2,
                "status": "partial",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 99
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "5 contributors",
                "points": 6.8,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 5
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 2 contributing companies or organizations -- score normalized to 6",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 82,
            "inputs": {
              "merged_prs": 296,
              "open_issues": 12,
              "closed_issues": 162,
              "issue_closed_ratio": 0.931,
              "closed_unmerged_prs": 13
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "93% of issues closed",
                "points": 43.5,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 93
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "296/309 decided PRs merged",
                "points": 36.6,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 296,
                      "decided": 309
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 3/16 approved changesets -- score normalized to 1",
                "points": 1.5,
                "status": "partial",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 63,
            "inputs": {
              "followers": 129,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "chrissnell",
              "public_repos": 91,
              "account_age_days": 5417
            },
            "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": "129 followers of chrissnell",
                "points": 15.2,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 129,
                      "login": "chrissnell"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "91 public repos, account ~14 yr old",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 91
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 14
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "github.com/chrissnell/graywolf"
              ],
              "ecosystems": "go",
              "any_deprecated": false,
              "min_days_since_publish": 5
            },
            "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 5 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "119 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 119
                    }
                  }
                ],
                "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": 66,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 66,
            "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": "5 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 5
                    }
                  }
                ],
                "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": "14 out of 15 merged PRs checked by a CI test -- score normalized to 9",
                "points": 18,
                "status": "partial",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "moderate",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 65,
            "inputs": {
              "topics": [],
              "has_wiki": false,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": 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": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 40,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 4
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "binaries present in source code",
                "points": 6.8,
                "status": "partial",
                "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": "14 out of 15 merged PRs checked by a CI test -- score normalized to 9",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 3/16 approved changesets -- score normalized to 1",
                "points": 0.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 2 contributing companies or organizations -- score normalized to 6",
                "points": 1.5,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is fuzzed",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 1",
                "points": 0.5,
                "status": "partial",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "33 existing vulnerabilities detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 3
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "good",
        "name": "AI Readiness",
        "value": 80,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.93,
              "agent_instruction_files": [
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 4522
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "CLAUDE.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "CLAUDE.md"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "93 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 93,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "good",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 70,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "Cargo.lock",
                "go.sum",
                "package-lock.json"
              ],
              "has_dockerfile": true,
              "typed_language": true,
              "bootstrap_files": [
                "Makefile"
              ],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [
                "web/src/api/tsconfig.json"
              ],
              "agent_commit_share": 0.04,
              "toolchain_manifests": [
                "Cargo.toml",
                "android/app/build.gradle.kts",
                "android/build.gradle.kts",
                "go.mod",
                "graywolf-modem/Cargo.toml"
              ],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "Makefile",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Makefile"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "web/src/api/tsconfig.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "web/src/api/tsconfig.json"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "Dockerfile, lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Dockerfile, lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "4 of the last 100 commits agent-authored or agent-credited",
                "points": 8,
                "status": "partial",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 4,
                      "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 1",
                "points": 1,
                "status": "partial",
                "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": 117468,
              "source_files_sampled": 1099,
              "oversized_source_files": 6
            },
            "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": "6/1099 source files over 60KB",
                "points": 54.7,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 1099,
                      "oversized": 6
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "good",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 80,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": false,
              "api_schema_files": [
                "docs/handbook/openapi.json",
                "docs/handbook/openapi.yaml",
                "pkg/webapi/docs/gen/swagger.json",
                "pkg/webapi/docs/gen/swagger.yaml",
                "proto/graywolf.proto",
                "proto/platform.proto"
              ]
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": "docs/handbook/openapi.json, docs/handbook/openapi.yaml, pkg/webapi/docs/gen/swagger.json, pkg/webapi/docs/gen/swagger.yaml, proto/graywolf.proto, proto/platform.proto",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "docs/handbook/openapi.json, docs/handbook/openapi.yaml, pkg/webapi/docs/gen/swagger.json, pkg/webapi/docs/gen/swagger.yaml, proto/graywolf.proto, proto/platform.proto"
                    }
                  }
                ],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "Could not fetch crates package 'graywolf-demod' from its registry",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-23T01:15:32.333452Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/c/chrissnell/graywolf.svg",
  "full_name": "chrissnell/graywolf",
  "license_state": "standard",
  "license_spdx": "GPL-2.0"
}

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

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

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