公开记录
软件健康报告模式 0.26.0 · 指标 1.13.0 · 2026-07-22 02:41 UTC

tjcran / boardgame-go

Go-native port of boardgame.io

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

tjcran/boardgame-go 的健康指数为 100 分中的 54 分,处于「中等」区间。 其得分最高的类别是Vitality(72/100),最低的是Community & Adoption(24/100)。 最近一次更新在今天。 近期的大部分工作由 1 位贡献者完成。

54
总分 / 100
中等

软件健康指数

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

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

评分画像

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

所有权

Tyler Crandall个人账户
3 关注者10 个公开仓库始于 2014年3月

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

软件包生态系统

注册表软件包版本月下载量版本数最近发布
Gogithub.com/tjcran/boardgame-gov0.7.2-205 天前

按类别列示的指标

活力

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

72良好 · 占总体的 22%
评分方式
36/36推送新近度 — 最近一次推送于 0 天前
6.2/36提交节奏 — 52 周中有 9 周有提交
18/18提交量 — 最近一年 126 次提交
0/10OpenSSF Scorecard:Maintained — project was created within the last 90 days. Please review its contents carefully
所用输入
commits_last_year126
human_commit_share1
days_since_last_push0
active_weeks_last_year9

发布纪律

90优秀
评分方式
27/27有发布版本 — 已发布 19 个发布版本
36/36发布时效 — 最近一次发布版本于 4 天前
27/27发布节奏 — 约每 6.6 天发布一次
0/10OpenSSF Scorecard:Signed-Releases — Project has not signed or included provenance with any releases.
所用输入
releases_count19
latest_release_tagv0.7.2
releases_from_tags
days_since_latest_release4
mean_days_between_releases6.6

社区与采用

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

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

社区健康

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

可持续性与治理

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

52中等 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0/22.5提交分布 — 头号贡献者编写了 100% 的提交
1.4/13.5贡献者广度 — 1 位贡献者
0/10OpenSSF Scorecard:Contributors — project has 0 contributing companies or organizations -- score normalized to 0
所用输入
bus_factor1
contributors_sampled1
top_contributor_share1
评分方式
38.9/46.8议题解决 — 83% 的议题已关闭
36.8/38.3PR 接受 — 已裁定的 PR 中 76/79 已合并
0/15OpenSSF Scorecard:Code-Review — Found 0/14 approved changesets -- score normalized to 0
所用输入
merged_prs76
open_issues3
closed_issues15
issue_closed_ratio0.833
closed_unmerged_prs3
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
4.3/25所有者影响力 — tjcran 有 3 位关注者
19.6/25既往记录 — 10 个公开仓库,账户约 12 年
所用输入
followers3
owner_typeUser
is_verified
owner_logintjcran
public_repos10
account_age_days4,503
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — go 上有 1 个软件包
35/35发布时效 — 最近一次发布于 5 天前
20/20版本历史 — 20 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesgithub.com/tjcran/boardgame-go
ecosystemsgo
any_deprecated
min_days_since_publish5

工程质量

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

67中等 · 占总体的 20%

工程实践

68中等
评分方式
24/24CI 工作流 — 2 个工作流
24/24存在测试
0/16Linter 配置
0/9.6Pre-commit 钩子
0/6.4.editorconfig
20/20OpenSSF Scorecard:CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
所用输入
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

安全

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

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

安全态势

35存在风险
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
2.2/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/14 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
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
6.8/7.5Vulnerabilities — 1 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate3.5
评分方式
35/35直接依赖不含已知公告 — 没有直接依赖携带已知公告
0/25间接依赖不含已知公告 — 在此范围内,传递依赖集合无法与开发和测试依赖区分
0/40没有长期未处理的公告 — 没有公告带有发布日期
所用输入
sourceosv
advisories1
affected_packages1
assessed_packages35
unassessed_packages0
affected_by_severityunknown 1
direct_affected_packages0
已排除计分(无数据或不适用):间接依赖不含已知公告, 没有长期未处理的公告。 其余权重已重新归一化。 已将 35 个已解析依赖与 OSV 比对。 该仓库未发布任何索引可解析的软件包,因此改为评估仓库依赖图。该图将开发与测试版本固定同交付的依赖混在一起,因此仅对声明的运行时依赖计分;传递性发现仅作为背景信息列出,不计入评分。 未对可达性进行分析。

AI 就绪度

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

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

机器可读接口

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

关键数据

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

更多细节

OpenSSF Scorecard 3.5 / 10
3.5综合

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

10Binary-Artifactsno binaries found in the repo
3Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/14 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
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
9Vulnerabilities1 existing vulnerabilities detected
直接依赖 10
注册表软件包版本约束清单文件
Gogithub.com/alicebob/miniredis/v2v2.33.0go.mod
Gogithub.com/coder/websocketv1.8.12go.mod
Gogithub.com/jackc/pgx/v5v5.9.2go.mod
Gogithub.com/redis/go-redis/v9v9.7.3go.mod
Gogithub.com/wI2L/jsondiffv0.6.1go.mod
Gogolang.org/x/toolsv0.44.0go.mod
Gomodernc.org/sqlitev1.34.4go.mod
Gogithub.com/jackc/pgx/v5v5.9.2mcp/go.mod
Gogithub.com/tjcran/boardgame-gov0.0.0mcp/go.mod
Gogo.starlark.netv0.0.0-20240520160348-046347dcd104mcp/go.mod
全部依赖 35

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

注册表软件包版本关系
Gogithub.com/alicebob/miniredis/v2v2.33.0直接
Gogithub.com/coder/websocketv1.8.12直接
Gogithub.com/jackc/pgx/v5v5.9.2直接
Gogithub.com/redis/go-redis/v9v9.7.3直接
Gogithub.com/wi2l/jsondiffv0.6.1直接
Gogo.starlark.netv0.0.0-20240520160348-046347dcd104直接
Gogolang.org/x/toolsv0.44.0直接
Gomodernc.org/sqlitev1.34.4直接
Gogithub.com/alicebob/gopher-jsonv0.0.0-20200520072559-a9ecdc9d1d3a间接
Gogithub.com/cespare/xxhash/v2v2.2.0间接
Gogithub.com/dgryski/go-rendezvousv0.0.0-20200823014737-9f7001d12a5f间接
Gogithub.com/dustin/go-humanizev1.0.1间接
Gogithub.com/google/uuidv1.6.0间接
Gogithub.com/hashicorp/golang-lru/v2v2.0.7间接
Gogithub.com/jackc/pgpassfilev1.0.0间接
Gogithub.com/jackc/pgservicefilev0.0.0-20240606120523-5a60cdf6a761间接
Gogithub.com/jackc/puddle/v2v2.2.2间接
Gogithub.com/mattn/go-isattyv0.0.20间接
Gogithub.com/ncruces/go-strftimev0.1.9间接
Gogithub.com/remyoudompheng/bigfftv0.0.0-20230129092748-24d4a6f8daec间接
Gogithub.com/tidwall/gjsonv1.18.0间接
Gogithub.com/tidwall/matchv1.1.1间接
Gogithub.com/tidwall/prettyv1.2.1间接
Gogithub.com/tidwall/sjsonv1.2.5间接
Gogithub.com/yuin/gopher-luav1.1.1间接
Gogolang.org/x/modv0.35.0间接
Gogolang.org/x/syncv0.20.0间接
Gogolang.org/x/sysv0.45.0间接
Gogolang.org/x/textv0.37.0间接
Gomodernc.org/gc/v3v3.0.0-20240107210532-573471604cb6间接
Gomodernc.org/libcv1.55.3间接
Gomodernc.org/mathutilv1.6.0间接
Gomodernc.org/memoryv1.8.0间接
Gomodernc.org/strutilv1.2.0间接
Gomodernc.org/tokenv1.1.0间接
依赖安全公告 1

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

软件包版本关系严重程度公告数修复版本
golang.org/x/textv0.37.0间接未知10.39.0

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

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 1019,
      "has_wiki": false,
      "homepage": null,
      "languages": {
        "Go": 1485742,
        "Dockerfile": 2085
      },
      "pushed_at": "2026-07-22T01:43:20Z",
      "created_at": "2026-05-13T14:50:45Z",
      "owner_type": "User",
      "updated_at": "2026-07-18T15:51:08Z",
      "description": "Go-native port of boardgame.io",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "Go",
      "significant_languages": [
        "Go"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Tyler Crandall",
      "type": "User",
      "login": "tjcran",
      "company": null,
      "location": null,
      "followers": 3,
      "avatar_url": "https://avatars.githubusercontent.com/u/7042112?v=4",
      "created_at": "2014-03-24T00:35:18Z",
      "is_verified": null,
      "public_repos": 10,
      "account_age_days": 4503
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.7.2",
          "kind": "patch",
          "published_at": "2026-07-17T14:33:49Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2026-06-05T00:01:47Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-05-24T04:21:24Z"
        },
        {
          "tag": "v0.6.3",
          "kind": "patch",
          "published_at": "2026-05-22T20:24:21Z"
        },
        {
          "tag": "v0.6.2",
          "kind": "patch",
          "published_at": "2026-05-21T23:48:47Z"
        },
        {
          "tag": "v0.6.1",
          "kind": "patch",
          "published_at": "2026-05-21T23:21:24Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-05-21T21:47:14Z"
        },
        {
          "tag": "v0.5.6",
          "kind": "patch",
          "published_at": "2026-05-18T20:57:07Z"
        },
        {
          "tag": "v0.5.5",
          "kind": "patch",
          "published_at": "2026-05-18T20:27:37Z"
        },
        {
          "tag": "v0.5.4",
          "kind": "patch",
          "published_at": "2026-05-18T20:08:26Z"
        },
        {
          "tag": "v0.5.3",
          "kind": "patch",
          "published_at": "2026-05-18T19:48:16Z"
        },
        {
          "tag": "v0.5.2",
          "kind": "patch",
          "published_at": "2026-05-18T19:32:14Z"
        },
        {
          "tag": "v0.5.1",
          "kind": "patch",
          "published_at": "2026-05-18T18:48:36Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-05-18T16:12:20Z"
        },
        {
          "tag": "v0.4.1",
          "kind": "patch",
          "published_at": "2026-05-17T17:16:38Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-05-16T15:32:03Z"
        },
        {
          "tag": "v0.3.1",
          "kind": "patch",
          "published_at": "2026-05-15T18:54:27Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-05-15T16:54:14Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-05-15T15:47:11Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "6e0c0fb1ff9ca6e0181b7493904a917c8ac59b80",
          "body": "core: let a valid ResumeTag apply while other blocks remain",
          "is_bot": false,
          "headline": "Merge pull request #103 from tjcran/fix/resume-with-remaining-blocks",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-18T15:51:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1401f3e5d7249f943626981be8943b4d2c659373",
          "body": "A single apply can raise multiple simultaneous prompts (one cascade\nstep queueing several RequestTargets for the same seat). The reducer's\nblock gate ran after resume-tag consumption but keyed on 'any blocks\nremain', so answering one prompt was rejected with ErrBlocked and\nrolled back — a state with\n[…]\n. Prompts raised\ntogether are now answerable one at a time, in any order across distinct\ntags and positionally (first match) for duplicate tags.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core: let a valid ResumeTag apply while other blocks remain",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-18T15:36:08Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e90fd5d490bf84586446a3cd1d88c7dbd5747a9f",
          "body": "…bot alerts) (#102)\n\nResolves all 41 open Dependabot alerts across both modules:\n\n- github.com/jackc/pgx/v5 v5.7.1 -> v5.9.2 (root + mcp): critical\n  memory-safety fix (5.9.0) and SQL-injection-via-placeholder fix (5.9.2).\n- github.com/redis/go-redis/v9 v9.7.0 -> v9.7.3 (root): out-of-order\n  respon\n[…]\nd the CI matrix moves from\n1.23/1.24 to 1.25/1.26. This raises the minimum Go for downstream\nconsumers; no API, wire, or persisted-JSON changes.\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "deps: bump pgx to 5.9.2, go-redis to 9.7.3, drop x/crypto (41 Dependa…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T21:40:56Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "377555e8d7d7c0f1ea8b534f3520a9da2824a46d",
          "body": "* core/server: per-viewer block redaction hook (BlockView)\n\nPlayerView already redacts G, plugin private data, and Move.Redact log\nargs per seat, but State.Blocks rides to every viewer unredacted. In\nhidden-information games (card games, fog-of-war), a pending block's\nData/Target payload can itself \n[…]\nrView implementations elsewhere in the repo.\n\nDoc-only; no behavior change. go build/vet/test clean at root and\nin the mcp submodule.\n\n---------\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core/server: per-viewer block redaction hook (BlockView) (#101)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T14:33:24Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "bfd80e748bff4651cd5bb9a5a3b3604afb8eb11f",
          "body": "feat(games): stackduel — reference game for stack + priority + staged triggers",
          "is_bot": false,
          "headline": "Merge pull request #100 from tjcran/feat/games-stackduel",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:14:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f2796ecad6e8afc0fbe6bb97cffbd26f03230578",
          "body": null,
          "is_bot": false,
          "headline": "Merge remote-tracking branch 'origin/main' into feat/games-stackduel",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:12:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb383f15135d2306c2a91c969bcbf31c74eab088",
          "body": "fix(ccg): embedding *ccg.State no longer drops game fields on marshal",
          "is_bot": false,
          "headline": "Merge pull request #99 from tjcran/fix/ccg-embed-marshal",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:11:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "da11ab831d88e9cc262ddfd1b513ee03df4f3286",
          "body": "# Conflicts:\n#\tmodules/ccg/state.go",
          "is_bot": false,
          "headline": "Merge remote-tracking branch 'origin/main' into fix/ccg-embed-marshal",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:09:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "851254ec52a386b52699a114c911f062ed36bf54",
          "body": "feat(ccg): PriorityState — pass-loop protocol for response windows",
          "is_bot": false,
          "headline": "Merge pull request #98 from tjcran/feat/ccg-priority-state",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:08:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2b06a69c06e0b1c6c6c61bc9217f65aa11b37e12",
          "body": "feat(ccg): trigger staging — bridge event handlers to the effect queue",
          "is_bot": false,
          "headline": "Merge pull request #97 from tjcran/feat/ccg-staged-triggers",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:08:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7437cd6b84574f4411f8aff2ce3c4085b72f8ea7",
          "body": "feat(economy): Basket — atomic multi-kind cost payment",
          "is_bot": false,
          "headline": "Merge pull request #96 from tjcran/feat/economy-basket",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-17T00:08:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a1ea43287ef20cce36b68807f1c3280174da7b69",
          "body": "… triggers\n\nA two-player spell duel that is the first in-repo consumer of the ccg\neffect envelope end to end: spells are pending Effects on a PickBack\nstack, every cast opens a ccg.PriorityState window (engine-gated via\nthe respond stage's move table), an unbroken all-pass round resolves\nexactly one\n[…]\n/load through the\nrest of the exchange).\n\nAlso documents the rows/ranks naming-convention pattern for\npositional boards in the ccg package docs.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(games): stackduel — reference game for stack + priority + staged…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:43:00Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "264843775ba46f15efbf3242828c6f68dea457b0",
          "body": "…at/ccg-priority-state' and 'fix/ccg-embed-marshal' into feat/games-stackduel\n\n# Conflicts:\n#\tmodules/ccg/state.go",
          "is_bot": false,
          "headline": "Merge branches 'feat/economy-basket', 'feat/ccg-staged-triggers', 'fe…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:34:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f0b95066245d0cb98b6f3192d4d9434b89567a7",
          "body": "State carried custom MarshalJSON/UnmarshalJSON methods to persist its\nunexported ID counters. Go promotes those methods onto any struct that\nembeds *State — the exact pattern doc.go recommends and hearts /\nrisklike / threeprovinces use — so json.Marshal of a game's G invoked\nState's codec and silent\n[…]\ne / NewZone re-arm their map on the way in\n(AddModifier already did). Regression test covers the embedding\nround-trip that previously lost data.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(ccg): embedding *ccg.State no longer drops game fields on marshal",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:33:16Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e547ad2450b9018c2c71089f8417d436fad452fe",
          "body": "Stack games gate progress on consent: after an action, each player in\nsome order may respond before anything resolves. The protocol is\nalways the same shape — open a window over an ordered player set,\nrotate on pass, reset the consent count on action, close on\nconsecutive all-pass — and until now ev\n[…]\nase\nboundaries wipe ctx.ActivePlayers but not G, so the docs pin the\ninvariant (close before EndTurn, Reset in OnBegin after a forced\nboundary).\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(ccg): PriorityState — the pass-loop protocol for response windows",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:30:01Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c44ba2fc846a4b86d89672beafc8afdcbbae03a5",
          "body": "Event handlers run inside Publish with no MoveContext, so a triggered\nability that should produce a pending effect had no safe path onto the\nqueue: pushing mid-Publish makes EffectIDs depend on handler\nregistration order, which is per-process and would desync replays.\n\nStageTrigger buffers the effec\n[…]\nd by the\ngame. Handlers staging during a flush land in the next batch, so\nnested triggers wait their checkpoint and the flush always terminates.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(ccg): trigger staging — bridge event handlers to the effect queue",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:21:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "8fdc527139b6933b29c55157f6031e2ee6662739",
          "body": "A Cost is a kind→amount price; Basket groups Pools by kind and pays\nall-or-nothing: the whole cost is validated against current balances\nbefore any pool is touched, so a failed payment mutates nothing and\npublishes no counter events. Kinds spend in sorted order for a\ndeterministic counter_changed se\n[…]\ned first, then N generic units draw greedily from a\ncaller-supplied fallback order — the policy is the caller's, the\natomicity is the library's.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(economy): Basket — atomic multi-kind cost payment",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-16T23:17:35Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "d3bd511fbb00715fcc70cf49c6a4e81ada9424de",
          "body": "core: MoveContext.DropBlocksFor — timeout/forfeit moves clear stranded prompts",
          "is_bot": false,
          "headline": "Merge pull request #94 from tjcran/feat/drop-blocks",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T20:09:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fcf83c2050a4d95bfc46930a70788ef36ec6dd72",
          "body": "…ar stranded prompts\n\nA player who walks away mid-prompt holds the match hostage: their\nunanswered pending block gates every move (ErrBlocked), including the\nopponent's attempt to force their turn timeout — IgnoreBlocks lets the\ntimeout move RUN, but the block itself survived and kept gating\neveryth\n[…]\ns).\n\nContract test: IgnoreBlocks+AnyPlayer timeout move drops the current\nplayer's block, rotates the turn, leaves the pre-move state untouched.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(core): MoveContext.DropBlocksFor — timeout/forfeit moves can cle…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T20:09:01Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "7b28a18af403e9b1a13f5821b31266e057e8ea1e",
          "body": "core: AnyPlayer move flag — dispatchable regardless of turn ownership",
          "is_bot": false,
          "headline": "Merge pull request #93 from tjcran/feat/any-player-moves",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T19:00:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a7e99b5d234988df25d521ea50a44edf54a277cd",
          "body": "…ership\n\nConcede, forfeit, and forcing an AFK opponent's turn timeout are moves\nthat by nature come from the seat whose turn it ISN'T. The reducer's\nauthorization check rejected them with ErrWrongPlayer; games had no\nescape hatch short of stage gymnastics.\n\nMove.AnyPlayer (same class as IgnoreBlocks\n[…]\nve body reads mc.PlayerID to know who called. Contract\ntest: ordinary moves still reject cross-seat, AnyPlayer moves apply\nand can end the turn.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(core): AnyPlayer move flag — dispatchable regardless of turn own…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T19:00:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0503da1414a3a130cc21ca7582d27fc557a7c1e1",
          "body": "core/match: deterministic wall-clock for moves and hooks (ctx.NowMs)",
          "is_bot": false,
          "headline": "Merge pull request #92 from tjcran/feat/move-wall-clock",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T18:55:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "14207107c3ab4fa5c80d868929497c982f5e8473",
          "body": "…owMs)\n\nGames with turn timers need to stamp \"when did this turn start\" from\ninside a hook, but hooks must stay deterministic — calling time.Now in\ngame code breaks replays.\n\n- MoveRequest.NowMs (Unix ms): stamped by the match manager when the\n  caller leaves it zero (MoveReqCtx + DispatchServer, vi\n[…]\n value back, so time-reading games replay byte-identically.\n\nContract test covers move + hook visibility, log recording, and replay\ndeterminism.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(core,match): deterministic wall-clock for moves and hooks (ctx.N…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T18:55:06Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c3aec0da36d3c4cb6a2fad25ab03a81a7be49075",
          "body": "match: consume raw decode bytes once — cached stores rolled back G every move",
          "is_bot": false,
          "headline": "Merge pull request #91 from tjcran/fix/consume-once-raw-decode",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T18:24:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b50ed648c123c6cbd2bc5247450a876b9f94bbac",
          "body": "… G every move\n\nloadMigrated runs on every dispatch. After the first rehydration, a\ncached store hands back the same live State — which still carries the\nrawG (and raw plugin) bytes captured when the match was loaded from\ndisk. Every subsequent load re-ran DecodeG/PluginDecode against those\nstale by\n[…]\nves two moves through Cached(WriteBehind(FlatFile))\nafter a forced serialization round-trip; fails pre-fix (first move\nerased), passes post-fix.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(match): consume raw decode bytes once — cached stores rolled back…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T18:24:39Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "d4cd4ea80c87efe3f545325e8e7d33bc4589d9c2",
          "body": "core/match/random: rehydrate typed plugin data after persistence reload",
          "is_bot": false,
          "headline": "Merge pull request #90 from tjcran/feat/plugin-rehydration",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T17:40:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ffb35a480823fc4bd896fa85d211c5afe5a674fa",
          "body": "…e reload\n\nSerializing stores decode State.Plugins values as map[string]any; a\nplugin whose API hook type-asserts a concrete type panics on the first\nmove after a match reloads (the random plugin's `data.(*state)` —\n\"interface conversion: interface {} is map[string]interface{}, not\n*random.state\" — \n[…]\nt skip the manager pass.\n\nRegression tests: flatfile round-trip + move (the exact panic repro),\nexact-uint64 decode, non-panicking API fallback.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(core,match,random): rehydrate typed plugin data after persistenc…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-14T17:40:20Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f8d852a4c587747a4a2797929352ce361032d20f",
          "body": "server: carry resumeTag through the WS and REST move envelopes",
          "is_bot": false,
          "headline": "Merge pull request #89 from tjcran/feat/wire-resume-tag",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-13T22:40:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "56c21e73cd75f84fb86250f0060e15a76074b4b0",
          "body": "asNumber's type switch only covered the built-in numeric kinds, so a\ncandidate stored as a defined type (`type EntityID uint64`) never\ncompared equal to the plain float64 a JSON transport delivers —\ncontainsCandidate fell through to reflect.DeepEqual, which is false\nacross types, and every wire sele\n[…]\nd fallback for defined integer/float types, with a\nregression test covering float64-vs-defined, defined-vs-defined, and\nnon-candidate rejection.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(core): match defined numeric candidate types in ValidateSelection",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-13T21:55:23Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e0ab628fe3824cf1877958075ef37c458edfaa69",
          "body": "core.MoveRequest.ResumeTag is how a move resumes a pending interactive\nblock (core.State.Blocks), but neither the WebSocket inbound envelope\nnor the REST /move body exposed the field — both transports always\nsent an empty ResumeTag, so any match with a pending block rejected\nevery incoming move with\n[…]\n, and handleDryMove.\nCovered by a new block-game fixture proving ErrBlocked without the\ntag and successful resume with it, over both transports.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(server): carry resumeTag through the WS and REST move envelopes",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-07-13T21:39:53Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "1ac1960514cbafb55f1391f1c6ba2a917c4065e9",
          "body": null,
          "is_bot": false,
          "headline": "Remove web/README.md (revert WASM frontend)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:14:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a53d9c78649c3a6c7b3909c73b0f162178a09113",
          "body": null,
          "is_bot": false,
          "headline": "Remove web/wasm_exec.js (revert WASM frontend)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:14:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "169dc839faef0b4c7bb72d20ebdcbc1ebe3945ab",
          "body": null,
          "is_bot": false,
          "headline": "Remove web/app.js (revert WASM frontend)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:14:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9645c551e8481501ce394dc6c639cea4a810cc9e",
          "body": null,
          "is_bot": false,
          "headline": "Remove web/index.html (revert WASM frontend)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:14:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fff44a2a1e7009b0362da060dd456708f9b3437b",
          "body": null,
          "is_bot": false,
          "headline": "Remove cmd/wasm/main.go (revert WASM entrypoint)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:14:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c84a03e7fd7f4fe8cda68768035b1aea1c6aaf3a",
          "body": "Verbatim copy of misc/wasm/wasm_exec.js from the Go 1.23.4 source tree\n(matching go.mod's go 1.23.4). Loaded by web/index.html before app.js to\nprovide the global Go class that instantiates main.wasm.",
          "is_bot": false,
          "headline": "Add web/wasm_exec.js (official Go 1.23.4 wasm runtime glue)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:09:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "62e2dbfed7aa8e9b1f7843b76849573aa152d57d",
          "body": "cmd/wasm/main.go binds the core + bots packages via syscall/js and exposes\na global `simulateBattle` JS function. It mirrors the in-memory loop of\nbots.Simulate (core.NewMatchSeeded -> RandomBot.Play -> core.Apply, capped\nby MaxMoves, honouring ActivePlayers) while additionally recording a\ndetermini\n[…]\n battle log.\n\nweb/ is a static frontend (index.html, wasm_exec.js for Go 1.23.4, app.js)\nthat loads main.wasm, lets the user trigger a simulation with a seed, and\nrenders the deterministic battle log.",
          "is_bot": false,
          "headline": "Add Go WASM entrypoint (cmd/wasm) + static battle-simulator frontend",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-22T23:08:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d92d21fa69515c4d3e1c1877eb83d32be24563e6",
          "body": "Starlark games derived their RNG from hash(turn, numPlayers) — every\nmove in a turn shared one stream, and any client could predict it,\nbreaking hidden-information fairness (audit finding H1).\n\n- core.Ctx gains Seed (omitempty): persists with state so reloads and\n  replays reproduce streams; core.Pl\n[…]\nshuffle\n  sequences, asserts seeded replay is byte-identical and unseeded\n  replay of a seeded match diverges (proves the seed reaches the RNG).\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core+match: per-match secret seed, gated for legacy replay compat (#87)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-12T16:17:32Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "91c323e69cfab2195928b0319c5cfdd42ca0d076",
          "body": "…ery, visibility (#86)\n\nThe bridge exposed 8 of the ccg module's op surface (zones, entities,\ndraw, publish, shuffle); designed games could not touch modifiers,\ncounters, the effect queue, targeting, or visibility at all. Adds 19\nops bringing Starlark + MCP to parity with the Go API:\n\n- modifiers: a\n[…]\nte-persistence\nplan).\n\nEnd-to-end Starlark test drives the new ops through match.Manager\nand asserts replay byte-identity over the recorded log.\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "modulebridge: ccg op parity — modifiers, counters, effects, clone, qu…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-12T16:15:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "6d0cea1e57ae9aa1cdc56a2c91862ea3d71d4477",
          "body": "Two orthogonal additions to Entity that make face-down-in-play,\nmorph-style frames, peeks, and partial reveals expressible with the\nexisting redaction machinery:\n\n- RevealedTo: extra viewers who see the full entity regardless of\n  Visibility (overrides Hidden too). RevealTo / ConcealFrom helpers.\n- \n[…]\n a 2/2 frame, a\n  cost hint, or nothing at all. SetMask helper.\n\nBoth omitempty; entities that set neither marshal and redact exactly\nas before.\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: RevealedTo and Mask visibility extensions (#88)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-12T16:12:07Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "37b1eee345087fa03baf56dc633c276d069a9a90",
          "body": "* ccg: modifier lifetime links, Entity.Controller, State.Clone\n\nThree additive primitives from the CCG-support audit, all\nbackward-compatible (zero values preserve existing behavior and wire\nshape, guarded by golden-JSON tests):\n\n- Modifier.ExpiresWith + WhileIn tie a modifier's lifetime to an\n  ent\n[…]\nrget sweep), and\nSweepModifiers remains the explicit cleanup for moves/hooks.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: modifier lifetime links, Entity.Controller, State.Clone (#84)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-12T16:08:55Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0db8bf41d38890e3a8aec8d7e4be0140d4c4e0f0",
          "body": "…reate (#79) (#82)\n\nGame.PlayerCount returned the requested count unchanged whenever it was\npositive, so core.NewMatch and match.Manager.Create silently accepted\ncounts outside [MinPlayers, MaxPlayers]. Game logic that assumed the\ndeclared seat count (e.g. a phase ending only after every seat acts)\n\n[…]\nration instead of silently mutating caller intent.\n\nNumPlayers == 0 still means \"use the game's default\" — unchanged.\n\nCloses #79.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core,match: clamp PlayerCount and reject out-of-range NumPlayers on C…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-05T00:17:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e36b5b67da0f277da46da2a14080aeaf5bcb89f8",
          "body": "Code-backed games (G is a concrete Go struct) crashed with `interface\nconversion: ... is map[string]interface {}, not *MyState` the first time\na move or PlayerView ran after a serializing store reloaded the match —\nencoding/json decodes the engine's `G = any` field into a map and nothing\nin the load\n[…]\nRegister a one-liner DecodeG on tic-tac-toe so it doubles as the\nreference implementation for downstream native games.\n\nFixes #80.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core: optional Game.DecodeG hook for code-backed games (#81)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-04T23:35:10Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "2e0ba36b4e2939184e11cdc979877355ef1b2775",
          "body": "* regions: package skeleton + types\n\n* regions: NewMap with partition validation\n\n* regions: Of and Cells lookups with lazy reverse index\n\n* regions: Influence computes per-region per-player counts\n\n* regions: ByCCGOwner reads owner attr from ccg entities\n\n* regions: scoring types + ScoreRegion Plur\n[…]\ncompute scoring\nin-process via the regions package directly.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "regions: tabletop.Regions module + 5 MCP ops + 2 validation games (#78)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-02T04:41:02Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4e870a32eccc20c950f0d3cd30cdaf4fb4e233c5",
          "body": "Bite-sized TDD plan with 14 tasks: package skeleton + types, NewMap,\nOf/Cells, Influence, ByCCGOwner, scoring types + ScoreRegion across all\nthree rule shapes, ScoreAll, JSON round-trip, MCP bridge (5 ops +\nRehydrate), two validation games (threeprovinces, risklike), final\nverification.\n\nPlan code s\n[…]\nting tabletop.go and shop.go patterns.\n\nReady for subagent-driven or inline execution per the writing-plans\nskill's terminal step.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: implementation plan for tabletop.Regions module (#77)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-02T03:09:28Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "993ae1b5ece5f5a57485a4939486aa2eba2b5244",
          "body": "Standard-scope design for the one real gap identified by the module\ncoverage audit: named cell groups + per-player influence + plurality /\ntop-N / threshold scoring with three standard tie-break rules and a\ncustom callback.\n\nScope is intentionally bounded — static disjoint regions only, no\ndynamic m\n[…]\n, risklike) that\nexercise every scoring rule × tie-break combination plus the\nByCCGOwner convenience for the ccg+tabletop pairing.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: spec for tabletop.Regions area-control module (#76)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-02T02:55:23Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "abff4c7a1839fac50e9c0d40a392f9181e886dd3",
          "body": "…gs (#75)\n\ngames/hearts is a single-hand 4-player Hearts that validates the\ntrick-taking cookbook page. Tests drive a full 13-trick hand by\nfollowing the Enumerate-returned legal moves, and assert the total\npenalty pool is 26 (13 hearts + Q♠). The implementation surfaced three\nclarifications worth a\n[…]\nmitive bypass was needed: hands as ccg.Zone, follow-suit via\ncustom Go in playCard, trick zone for cards, scoring in resolveTrick.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "games: add Hearts reference, refine trick-taking cookbook from findin…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-02T02:26:48Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "d05ccd99625b77a53dccc8e5f0f82b1d246c8b48",
          "body": "…s (#74)\n\nThe module coverage audit identified that simultaneous/sealed actions,\ntrick-taking, and English/sealed auctions compose cleanly from existing\nprimitives (ActivePlayersAllOnce/InOrder, secret + StripSecrets,\nccg.Zone, economy.Pool) rather than needing new modules. This cookbook\ncaptures the four patterns so future game authors can compose them\nwithout rediscovering the recipe each time.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: add mechanics cookbook for sealed-reveal, trick-taking, auction…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-06-02T01:14:10Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "217cb1d151faf7b3490f77013cc9560a1bcc6dec",
          "body": "* tabletop: JSON round-trip for Board via kind discriminator\n\nAdd MarshalBoard/UnmarshalBoard to the tabletop package so that Board\ninterface values (SquareBoard, HexBoard) survive JSON serialization with\ntheir geometry intact. Uses a \"kind\" discriminator field. Needed by\nlater tasks that persist ta\n[…]\nns. Adds TestTerrainMapJSON_Deterministic\nto pin the ordering contract.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: module-backed designed games persist on serializing stores (#73)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-24T04:18:50Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e12f37551d83bca55c092ca60682c35772ff77b4",
          "body": "module_op asserted m.State.G.(*StarlarkG) and, on failure, blamed \"not a\ndesigned game\". But a designed game loaded from a serializing store\n(SQLite/Postgres) comes back as a bare map[string]any — core.G is `any`,\nso the JSON round-trip drops the concrete *StarlarkG and the live typed\nmodule states \n[…]\nre is a separate, larger effort (per-module rehydration, ccg id-counter\npersistence, hook re-registration) tracked as a follow-up.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: actionable module_op error for persisted matches (#72)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-22T20:21:49Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "5e22f9c13b3d45ef600d157ddf33afa2178cae57",
          "body": "… (#71)\n\nBoth recent module bugs came from the same smell: ~9 hand-rolled\nBridgeCtx literals across game.go/validate.go/tools.go/hooks.go, each\nindependently responsible for wiring modules and setting ReadOnly. One\nforgot Modules entirely (playtest_draft); the read-side ones each had to\nremember Rea\n[…]\n\nand seeded RNG stay caller-set — RNG deliberately so player_view keeps\nits no-random behavior. Pure refactor; no behavior change.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: centralize callback ctx construction into NewReadCtx/NewWriteCtx…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-22T16:30:49Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "92753e614137a797110c5a2f65ad7a9079543517",
          "body": "playtest_draft built its BridgeCtx without Modules and never created the\ndeclared module states, so any spec calling ctx.modules.* failed under\nplaytest with \"ctx struct has no .modules attribute\" — even though the\nsame spec validated and ran fine in a live match. Since playtest_draft is\nthe primary\n[…]\nve module map; the trace's end_if/legal_moves use a read-only ctx\nover the same states, mirroring the engine's read-only contract.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: instantiate modules in playtest_draft (#70)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-22T16:00:27Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "55856b84053c17edffc36417f07a1661102d8bc4",
          "body": "Module state was only reachable from setup, move-apply, and hooks — the\nspeculative read callbacks (legal_moves, end_if, player_view, phase\nend_if) built their BridgeCtx without Modules, so ctx.modules was absent\nthere. That blocked any module-driven game whose legal-move enumeration\nmust read board\n[…]\nalidate enforces the same contract at registration. describe_modules\nnow reports readOnly per op so agents know what's safe where.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: read-only module access in legal_moves/end_if/player_view (#69)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T23:46:27Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "372882a4a4e80eb18d6969d3f4d3f183b89bc6af",
          "body": "Adds modulebridge.AllModules() enumerator and a match-free\ndescribe_modules MCP tool that lists every engine module reachable\nvia module_op plus the op names each exposes. Lets an agent discover\nthe (module, op) surface up front instead of probing blind or relying\non out-of-band docs.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: describe_modules discovery tool for engine module ops (#68)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T22:35:45Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "aa693c136f54da23da019990306ba359a457113c",
          "body": "…rlarkG round-trip + stale prompt test) (#67)\n\n* storage: persist Player.Credentials so serializing stores can authenticate\n\nPlayer.Credentials was json:\"-\", so SQLite/Postgres stores (which\njson.Marshal the match) dropped it on save and reloaded it empty,\nmaking every move fail ErrBadCredentials af\n[…]\nrt the substring that's actually present and\nthe real capability bullets (Phases:/Stages:). These tests never ran in\nCI (mcp is a separate module CI builds but doesn't test), so the drift\nwent unseen.",
          "is_bot": false,
          "headline": "Fix the two latent mcp-package test failures (creds persistence + Sta…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T21:36:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7a6e1bc321798f23cff66f84e4780d9f6264d6cb",
          "body": "* docs: seeded-RNG / ccg.shuffle plan\n\n* modulebridge: thread move's seeded *core.Random into Op.Call\n\n* modulebridge: ccg.shuffle op (seeded, replay-deterministic)\n\n* starlarkgame: e2e ccg.shuffle determinism test + document shuffle",
          "is_bot": false,
          "headline": "mcp: seeded RNG for module ops + ccg.shuffle (#66)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T21:13:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a734300b0f427e1e82119d169793f46717604a17",
          "body": "* docs: phase-6 HOOKS design + plan\n\n* modulebridge: ccg.publish op + HookError for hook-failure propagation\n\n* starlarkgame: parse ordered HOOKS table; require ccg module\n\n* starlarkgame: register HOOKS on ccg event bus; fresh-thread handlers with depth guard\n\n* starlarkgame: e2e HOOKS (on-died draws) + replay determinism test\n\n* docs: document HOOKS event handlers for designed games",
          "is_bot": false,
          "headline": "mcp: HOOKS event-bus handlers for designed games (phase 6) (#65)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T21:00:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "47b42fb56bdae1aa2dfd29077b7475eee2161c29",
          "body": "* docs: phase-5 semantic arg types plan\n\n* starlarkgame: ArgDef.Zone + validateArgs for primitive/entity/hex arg types\n\n* starlarkgame: enforce declared arg schema at move dispatch\n\n* starlarkgame: render semantic arg types in skill skeleton + document",
          "is_bot": false,
          "headline": "mcp: semantic move-arg types + dispatch validation (phase 5) (#64)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T20:40:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dc7bb904ba2984f523a34b846411a3a9729467f6",
          "body": "… fix (#63)\n\n* docs: phase-4 TargetRequest module-bridge plan\n\n* starlarkgame: ctx.request_target/resuming_target/validate_selection bindings\n\n* mcp: make_move accepts resumeTag to dispatch resume moves\n\n* starlarkgame: e2e TargetRequest block/resume + replay determinism test\n\nThe new e2e test surfa\n[…]\nnding block hit ErrBlocked during replay. Thread\nResumeTag through so replay is byte-deterministic for block/resume games.\n\n* docs: document target selection (request_target/resume) for designed games",
          "is_bot": false,
          "headline": "mcp: bridge TargetRequest block/resume to Starlark (phase 4) + replay…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T20:17:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fd7fa811da817cf7d15411bed2b4f5df0d000c5a",
          "body": "* docs: phase-3 economy+shop module-bridge plan\n\n* modulebridge: Op.Call receives all live modules (enables composing modules)\n\n* modulebridge: economy op set (pools over ccg counters) + scaled helper\n\n* modulebridge: shop op set (refreshable market over ccg zones)\n\n* starlarkgame: whitelist economy/shop (require ccg) + integration test\n\n* docs: document economy + shop modules for designed games",
          "is_bot": false,
          "headline": "mcp: bridge economy + shop modules to Starlark (phase 3) (#62)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T18:42:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "93019ffc6b436a4ee09ba100e256c3e5d4b939f5",
          "body": "* docs: phase-2 tabletop module-bridge plan\n\n* modulebridge: tabletop bundle, factory, new_board + distance; whitelist tabletop\n\n* modulebridge: tabletop spatial/geometry/terrain/LOS op set\n\n* starlarkgame: tabletop integration + replay determinism test\n\n* docs: document the tabletop module for designed games",
          "is_bot": false,
          "headline": "mcp: bridge engine tabletop module to Starlark (phase 2) (#61)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T18:24:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a6cf90c6b1c17707513d0c7f97949193998b4bd3",
          "body": "…se 1) (#60)\n\n* docs: spec for exposing engine modules to Starlark + MCP\n\nDesign for bridging modules/{ccg,tabletop,economy,shop} and\ncore.TargetRequest into the Starlark game sandbox (runtime callables)\nand as design-time MCP tools, from one shared modulebridge registry.\nUnblocks complex game desig\n[…]\nme module_op tool sharing the modulebridge registry\n\n* starlarkgame: validate MODULES references + document ctx.modules\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: expose engine ccg module to Starlark + design-time MCP tool (pha…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T17:47:52Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "6bc95af26260dd445d030444a0cd9bfe353ad968",
          "body": "Adds rps as the reference game for the \"everyone submits a hidden choice,\nreveal atomically when all are in\" pattern, demonstrating that the engine\nsupports simultaneous actions today with no new primitives.\n\nThe pattern composes from existing pieces: Turn.ActivePlayers =\nActivePlayersAllOnce activa\n[…]\ntch or re-arms the\nnext round with events.SetActivePlayers.\n\nRegistered in boardgame-server alongside tic-tac-toe and Love Letter.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "games: rock-paper-scissors example for simultaneous actions (#59)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-21T15:24:58Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "715f41c386e81d2f0427e1686c7a6c9f1f015b2a",
          "body": "* typedgame: add SD type parameter for typed setup data (#50)\n\nGame[S any] → Game[S, SD any]; SetupFn and ValidateSetupDataFn are now\nparameterised on SD so callers get compiler-checked setup payloads instead\nof runtime any. Build() wraps both via direct assertion (panic on wrong\ntype — loud caller \n[…]\nw two-parameter typedgame.Game[S, SD] shape\nshipping in #50.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "typedgame: add SD type parameter for typed setup data (closes #50) (#58)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-20T00:07:28Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c99a5738a0690c637f2673187e96231ab82c83ba",
          "body": "…#57)\n\n* match: add Manager.DispatchServer + ServerOnly enforcement (#48)\n\n- Add ErrServerOnly sentinel for credentialed clients hitting a server-only move\n- Extract dispatchLocked helper (OCC loop + broadcast + lifecycle) shared by both ingress paths\n- MoveReqCtx checks Move.ServerOnly before deleg\n[…]\ningress and the asymmetric ServerOnly model shipping in #48.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "match: Manager.DispatchServer + ServerOnly enforcement (closes #48) (…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T23:48:41Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f1f2fd9c61d5ad9d3b7cf9542ece2181027d63c3",
          "body": "Adds core.TargetRequest{Kind, Candidates, Min, Max, Source, Data} as a\ntyped shape carried in BlockSpec.Target (new optional field), so the\ntransport, UI, and replay layers see a structured target-request\npayload instead of opaque BlockSpec.Data. Resume moves read what they\nresumed via the new MoveC\n[…]\ncked / ResumeTag flow is unchanged —\ntyped Target is additive. Plain Block calls keep producing BlockSpec\nentries with nil Target.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "core: typed TargetRequest protocol on top of Queue.Block (#56)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T23:38:12Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "3ad414a06ca54f1524f6496d7f6bd5e0b767eac2",
          "body": "* match: add LifecycleMatchMoveRejected event (#47)\n\n* docs: mention LifecycleMatchMoveRejected in README + PARITY\n\nExtend the Per-move observability row/bullet to cover rejections\nalongside the existing accepted-move primitives.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "match: LifecycleMatchMoveRejected event (server-side half of #47) (#55)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T23:25:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "cfe29a5c51e733108a788c60df6cf5df3c2f035b",
          "body": "…(#54)\n\n* core: stamp Stage onto LogEntry at move-write time (#49)\n\n* core: stamp Stage onto drain-step LogEntry too (#49 follow-up)\n\n* match: add Game.BeforePersist hook for transient-state scrubbing (#46)\n\nAdds optional BeforePersist func(State) State field to core.Game. When\nset, the hook is call\n[…]\nersist\nhook, Stage on LogEntry) shipping in #45 / #46 / #49.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "match/core: per-move observability primitives (closes #45, #46, #49) …",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T23:02:06Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "eba644d187060706f6f8e209206e3f8bd54bc608",
          "body": "Adds Effect (declarative pending-action shape) + PendingEffects queue\non *ccg.State, with Push/PushFront/InsertAt/Replace/Remove/Mutate\nprimitives, lifecycle events (effect_pushed / _resolving / _resolved /\n_removed / _replaced), and an optional ResolveNext dispatch helper\nparameterized by a Picker \n[…]\ntateEffect, and replace via ReplaceEffect on effect_pushed.\nEffect sits alongside core.MoveContext.Queue rather than replacing it.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: thin Effect envelope on top of existing event bus (#53)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T21:44:46Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "fc54844c8058a3d0a1c0afd5c43ac5ae30490ed0",
          "body": "Add parallel prose sections after \"CCG / TCG library\" describing the\nthree opt-in libraries that landed alongside ccg. Update the status\nblurb and intro to mention wargames + auto-battlers as supported game\nshapes, and refresh the ASCII layout diagram to show all four modules\nunder modules/ instead of just ccg.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: README + PARITY prose for tabletop, economy, shop modules (#52)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T17:06:23Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ea025cbd890ded54d97faee46b220a03c392c2ff",
          "body": "…raries (#51)\n\n* modules: relocate ccg/ → modules/ccg/\n\n* modules: relocate tabletop/ → modules/tabletop/\n\n* docs: update path references for modules/ relocation\n\nREADME.md and PARITY.md now reference modules/ccg/ and\nmodules/tabletop/ everywhere directory paths appear (markdown\nlinks, backtick pros\n[…]\nlag\n\n* shop: integration example with economy.Pool for cost-paying buy\n\n* docs: mention modules/economy and modules/shop in README\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "modules: relocate ccg/tabletop under modules/, add economy + shop lib…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-19T15:58:42Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f8080a70b66b787778de8199c6f04189e350546c",
          "body": "… (#38)\n\n* tabletop: package skeleton with Pos and UnitID types\n\n* tabletop: Board interface and LineOfSight free function\n\n* tabletop: SquareBoard with Chebyshev distance and Bresenham line\n\n* tabletop: HexBoard with axial coords and cube-round line\n\n* tabletop: TerrainMap with tag-based cells and \n[…]\n-BGIO comparison\ntable, and the \"engine never imports\" list.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "tabletop: add optional spatial + dice module for wargame-shaped games…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T23:27:38Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4e933c3709b87db1d66174b7fb93b9910d9c38a3",
          "body": "Third piece of BGIO state-machine surface for designed games. Specs\ndeclare a top-level STAGES dict; apply functions call\nctx.events.set_stage(\"stage_name\") to gate the current player into a\nsub-mode of their turn, and ctx.events.end_stage() to pop out. While\ngated, only the stage's moves are legal \n[…]\n the STAGES row, ctx.events docs, and (in\nimplement-a-known-game) the engine-limits callout that loveletter-\nclass games now work.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: stages — STAGES dict + ctx.events.set_stage / end_stage (#37)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T20:55:04Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e8371ca3b2aad9770748e60e106a9af7477599a5",
          "body": "Adds the second piece of BGIO state-machine surface to Starlark specs.\nA spec can now declare named PHASES with their own scoped move tables\nand end_if transitions; exactly one phase is marked start. While in a\nphase the engine restricts moves to that phase's table (matching the\nexisting core.PhaseC\n[…]\npecs continue to\n  produce a Game with Phases nil; no behavior change.\n\nBoth prompts updated with the PHASES row + ctx.phase note.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: phases — PHASES dict in spec maps to core.Game.Phases (#36)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T20:25:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "67bbea7ed8a9833f2f16201cfc3a0adbd86cad3b",
          "body": "Drops the global MaxMoves:1 in BuildCoreGame in favour of a per-move\nEndsTurn flag (default true, matching v0.4–v0.5.3 behaviour for every\nexisting spec). After a successful apply, the MoveFn closure enqueues\nan EndTurn event only when the move that just ran is marked terminal.\n\nThis unlocks Catan-s\n[…]\nthe historical \"every move ends the turn\"\n  behaviour, so every previously registered designed game continues\n  working unchanged.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: multi-action turns via per-move ends_turn flag (#35)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T20:06:26Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "d5eb50afc2571e731656e0a62c185e14d219f7c6",
          "body": "…et al.) (#34)\n\nA second design-time prompt that flips Claude's mode from interview to\nimplementation. When the user names a real-world board game (chess,\ncheckers, go, connect-4, mancala, dots-and-boxes, gomoku, isolation,\nhex, reversi, nim), the rules aren't a creative input — they're public\nand f\n[…]\nide RegisterDesignAGamePrompt;\nboth prompts are now exposed at startup. mcp/docs/designed-games.md\nupdated with both walkthroughs.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: implement-a-known-game prompt — one-shot canonical games (chess …",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T19:46:11Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c241fb6caf9a8f13b8857d68a1e36e2799927aac",
          "body": "… (#33)\n\nRoutes skills-on-disk Put() through starlarkgame.BuildSkillSkeleton so\nthe on-disk SKILL.md a designed game gets at registration time matches\nwhat export_game produces: YAML frontmatter, auto-rendered Moves table,\nDesigner's notes section with the llm_guide, Strategy placeholder.\n\nPreviousl\n[…]\ny doesn't start with a \"# \" heading, the\nbody is treated as the legacy llm_guide. Next register_game migrates\nthe layout in place.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: register_game now writes the rich SKILL.md (same as export_game)…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T19:30:17Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "5c8596f057f09bc9bcee5fd0a5ee9483d59beddf",
          "body": "…me (#32)\n\nAdds a tenth MCP tool that returns a skill-ready bundle for a game the\ncaller previously registered: a SKILL.md skeleton with YAML frontmatter\n+ an auto-rendered moves table + the designer's llm_guide, the Starlark\nspec source, and a structured manifest of metadata. Intended as the\nseed f\n[…]\np).\n- Cross-owner exports refused (registry-scoped lookup).\n- Output is structured fields — caller composes archive / file layout.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: export_game tool — emit a skill-shaped package for a designed ga…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T18:46:03Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "904843b4e1e1921817708247a6e977384af96401",
          "body": "Changes the Starlark spec contract from \"apply mutates state in place,\nreturn value ignored\" to \"apply receives a frozen state and MUST return\nthe new state dict; fail() rejects a move.\" Single, unambiguous style.\n\nMotivation: LLMs designing games default to functional/Redux style\n(build new dict, r\n[…]\nsion coverage in apply_return_contract_test.go: None return\nrejected, non-dict return rejected, mutation of frozen input rejected.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "v0.5.0 (breaking): apply returns the new state; mutation rejected (#31)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-18T16:09:58Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "7472b9e656a798445a72a22747e98fbd8b219928",
          "body": "…es key (#30)\n\nTwo bugs surfaced the first time a designed game was played end-to-end\nagainst a SQLite-backed match store (#28's stdio default when --db is\nset):\n\n1. core: Ctx.ActivePlayers nil <-> {} roundtrip rejects every move\n\n   BGIO compat requires Ctx.MarshalJSON emit `activePlayers: {}` for \n[…]\nssion coverage: new core/ctx_unmarshal_test.go + a SQLite-backed\nintegration test in mcp/ that would have caught the original bug.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "v0.4.1 fixes: SQLite-roundtrip move authorisation + adapter legal_mov…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-17T17:14:40Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4d9734d9a1c32f39e11745663f3412347bf9113a",
          "body": "Adds a third UserGameStore implementation that persists designed games\nas human-readable Claude skill directories on the local filesystem.\nEach game lives at <root>/<name>/{SKILL.md, spec.star}: the SKILL.md\nhas YAML frontmatter (name/owner/created_at) plus the llm_guide\nmarkdown, the spec.star hold\n[…]\nned games hand-editable, shareable by copy-paste, and\nremoves the \"must run Postgres for persistence\" requirement for local\nusers.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: skills-on-disk store for designed games (stdio default) (#29)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-16T15:19:19Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0078f864bbff9182249f94815df897dde8000a37",
          "body": "* docs: spec for design-a-game-on-the-fly MCP feature\n\nPre-implementation design doc for a Claude/user co-design flow that\nproduces a real, server-enforced game spec (open-ended via embedded\nStarlark) registered per-owner in Postgres and playable through the\nexisting MCP tools. Captures scope decisi\n[…]\n\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>\n\n* mcp: update tools-list test + comment to reflect 9 tools\n\n---------\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "mcp: design-a-game — Starlark-backed user-designed games (#28)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-16T15:03:08Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "db6caf788a8ace7aa0a6a1d18488702850f09ac9",
          "body": "…r (#27)\n\nWhen users add `-v boardgame-data:/data ... --db /data/matches.db` to\ntheir Claude Desktop config (per docs/local-mcp.md), the boardgame-mcp\ncontainer fails to start with:\n\n    open storage: sqlite ping: unable to open database file: out of memory (14)\n\nThat's SQLITE_CANTOPEN bubbled up by\n[…]\nain.go's openStorage to translate\nSQLITE_CANTOPEN into a clearer \"could not open or create database\nfile (check directory permissions)\" is a tight follow-up. Out of scope\nhere to keep the fix focused.",
          "is_bot": false,
          "headline": "mcp: fix Dockerfile so /data volume works for the nonroot runtime use…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-15T18:39:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "27f49594d9692a6d40c223554faa9b34717945bf",
          "body": "…binaries + ghcr.io) (#26)\n\n* mcp: docs cleanup + Claude Desktop config snippets + sub-module README\n\nCloses the discoverability gap for the \"I want to play a game with\nClaude on my laptop\" path. Three pieces:\n\n  - NEW mcp/README.md: the directory landing page on GitHub. Three\n    install paths (pre\n[…]\n-compile combination produces a ~14 MB\nstatic binary. The Dockerfile changes are inert for the host-arch\nsingle-build path (TARGETOS/TARGETARCH default to the host's values\nwhen buildx isn't driving).",
          "is_bot": false,
          "headline": "mcp: distribution — docs, Claude Desktop snippets, release workflow (…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-15T16:21:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "389dfb9c0ba01e3a82dab640c9fd9394f492cee9",
          "body": "dbDescriptor only inspected cfg.dbPath, so the startup log line read\ndb=in-memory even when --database-url pointed at a Postgres instance.\nMisleading rather than functionally broken — the Postgres path was\nwired correctly; only the log was wrong.\n\nNow threads the full serveFlags through and picks th\n[…]\n on purpose — credentials don't belong in logs\neven when the operator passed them on the command line.\n\nVerified by running the binary with each of the three modes and\ninspecting the startup log line.",
          "is_bot": false,
          "headline": "mcp: log db=postgres (not db=in-memory) when --database-url is set (#25)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-15T15:56:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9662fd0ac787363928b66e3cb7f674c8fff85863",
          "body": "…#24)\n\n* mcp: stabilize TestJWT_RejectsTamperedSignature\n\nThe test was flipping the LAST base64 char of the JWT signature, which\nencodes only 2 data bits plus 4 padding bits. About 25% of the\nalphabet shares those data bits with 'A' (the replacement), in which\ncase lenient RawURLEncoding ignores the\n[…]\nted-app.md gains a\n\"Persistence\" section documenting the precedence and recommending\nPostgres (Neon) for any multi-instance deployment. The previous\n\"limitations / Postgres deferred\" caveats are gone.",
          "is_bot": false,
          "headline": "mcp: Postgres persistence for hosted mode (match state + ownership) (…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-15T00:06:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c9e696c54a88598e2f399c22c126126084a2ad75",
          "body": "…rompts (#23)\n\n* mcp: add prompts surface (prompts/list, prompts/get) + play-tictactoe\n\nAdds the MCP prompts capability so hosted-mode clients (Claude.ai\nconnectors) can offer a /play-tictactoe slash command — the\nhosted-deployment equivalent of the Claude Code skill files under\nmcp/skills/. Per-gam\n[…]\ned-app.md is the operator + consumer guide for the\nhosted deployment: build, deploy, configure OAuth, register as a\nClaude.ai connector, and a troubleshooting section calling out the\ncommon 401 paths.",
          "is_bot": false,
          "headline": "mcp: hosted mode — HTTP transport, OAuth 2.1, multi-tenant scoping, p…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T23:52:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cbdc37288c14e8c2db4eef8469823cfa144c77cb",
          "body": "* tictactoe: add Game.Enumerate for bot/LLM play\n\nAdds an Enumerate function to the tic-tac-toe Game definition so bots\nand any LLM-driven player (RandomBot/MCTSBot via Game.Enumerate\nfallback; the new MCP plugin for external clients) can ask the engine\nfor the legal-move set instead of bringing the\n[…]\nus the JSON-RPC server (initialize, tools/list, tools/call,\n    error surfacing as isError content, unknown-method handling,\n    notifications)\n  - manual JSON-RPC stdio smoke against the built binary",
          "is_bot": false,
          "headline": "mcp: add optional MCP plugin (stdio mode) for LLM-driven gameplay (#22)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T23:35:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b524f7c46fd6321ffc3221ce43bf4f61ca2162d5",
          "body": "…sections (#21)\n\nPrep for OSS release. Repo had no license file; project mirrors\nboardgame.io's API surface explicitly, so attribute upstream verbatim\nin NOTICE (no source copied — Go is freshly authored — but the public\nshape is deliberately compatible). README gets Acknowledgments + License\nsections so the relationship is obvious to anyone landing on GitHub.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "licensing: add MIT LICENSE, NOTICE with upstream attribution, README …",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T22:13:22Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "a70925e9ab0ac2c70174e45d1962342e15fe90e0",
          "body": "New ccg/typed/ sub-package wraps ccg.Catalog with generics so games\ncan author card templates whose Attrs field is a typed struct (or\nany JSON-object-shaped type) instead of map[string]any.\n\nAPI:\n  - typed.NewCatalog[A]() *Catalog[A]\n  - (*Catalog[A]).Register(CardDef[A]) error\n  - (*Catalog[A]).Get\n[…]\ne with ErrNonObjectAttrs because their JSON form isn't\nan object and can't be flattened. Empty struct / map[string]X are\naccepted.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add typed sub-package with generic CardDef[A] / Catalog[A] (#19)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T20:26:18Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0ee36c00f3a19ab91ec3a68136a8624ef0c6c046",
          "body": "Closes the classic event-bus bug class where games must remember to\nUnsubscribe when a permanent leaves play. BindAbility(id, zones,\npredicate, handler) registers a normal Subscribe under the hood; the\nwrapped handler checks Entity.Zone on every fire and self-\nUnbindAbility's when the entity is dest\n[…]\ng in-memory subs table. No\nexisting exported types or methods change; games that never call\nBindAbility see zero behavioural diff.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add BindAbility (zones-only) for lifecycle-managed event subs (#20)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T20:22:40Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "8501be513ae0ce398c6dae49582e2ac213504ee0",
          "body": "New ccg/deckpile.go labels a (deck, discard) pair and bundles the\noperations every CCG re-implements: Draw, Discard, Mill,\nReshuffleDiscardIntoDeck, DeckSize / DiscardSize.\n\nDeckPile does not own its zones — it's a *label* that names two\nZoneNames already (or about-to-be) registered in State. Games \n[…]\names are DeckZone / DiscardZone (not Deck / Discard) so the\n\"Discard\" verb method doesn't collide. Mildly verbose but unambiguous.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add DeckPile composite for deck + discard zone pairs (#17)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T20:17:54Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "782b4d9f017b93bb53282efad47bdaca3fcf08b2",
          "body": "…ers (#16)\n\nPure new file ccg/helpers.go with six small helpers that reuse existing\nprimitives — no new state, no signature changes.\n\n- Transition(ids, to) is pre-flight all-or-nothing: validates every id\n  exists and that `to` has remaining capacity for ids not already in\n  it, *before* mutating an\n[…]\natch movers eliminates a class of \"rejected halfway\"\nstate corruption that callers were either tolerating or rolling back\nby hand.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add Transition / MoveAllTo / Top / Bottom / Mulligan / Deal help…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T20:14:12Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "8b330e76356d8b21887e8faa93ca904059abf783",
          "body": "Counter mutations land under a reserved Entity.Attrs key\n(CountersAttrKey = \"__counters\") as map[string]int, with five\nmethods on *State: AddCounter / RemoveCounter / Counters /\nAllCounters / TransferCounters. Every mutation publishes a\ncounter_changed event with {kind, delta, total_after}; total_af\n[…]\nwith no counters has Attrs identical to its pre-\n  counter shape.\n\nPurely additive — no existing types, methods, or fields change.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add typed Counter API on entities (#15)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-14T20:13:36Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f6e56dc6117245065dd47f95e31014c040248a46",
          "body": "…edaction (#14)\n\nAdds a per-entity Visibility enum (Public / OwnerOnly / Hidden) and\nState.RedactForViewer(viewer) that returns a deep-copied state with:\n\n- Hidden + non-owner OwnerOnly entities collapsed to {ID, Zone,\n  Visibility} stubs (Type / Owner / DefID / Attrs stripped — neither\n  identity n\n[…]\nlayerView (`return g.RedactForViewer(viewer)`)\ninstead of re-implementing the same opponent-hand-stripping dance\nacross every CCG.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add Visibility + State.RedactForViewer for one-shot PlayerView r…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T22:37:56Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "58bfbf7dc3319ca17e852f9cce0a187f2b508507",
          "body": "Adds a typed DeckList (map[DefID]int) with Total() and a\ndeterministically-ordered Cards() helper, plus a DeckValidator interface\nand five built-ins (MinSize / MaxSize / MaxCopies / RequireDefsExist /\nNonNegativeCounts) that Compose into a single validator.\n\nState.LoadDeckList(catalog, list, owner, \n[…]\nure, leaving the already-inserted\nentities in place — validate up front with MaxSize(zone.Capacity) to\nmake that path unreachable.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add DeckList + composable validators + State.LoadDeckList (#13)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T22:36:48Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c134183ff7024263f1355c59ac84796cdf139a2c",
          "body": "… (#12)\n\nAdds an opt-in Catalog of CardDef templates so games can stamp fresh\nEntities from a registry (Instantiate(catalog, defID, owner)) instead\nof hand-rolling per-card setup logic. Entity gains an optional DefID\nfield that links the runtime entity back to its template; the field is\nJSON-omitemp\n[…]\ne-compatible. BaseAttrs are\ncloned per instance and on registration so attr mutations never leak\nbetween def, instance, or caller.\n\nCo-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ccg: add CardDef catalog + State.Instantiate for typed card templates…",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T22:34:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "a80c275cc1696fbac46a101bccbc35dca818fd8d",
          "body": "isLocalhost in server/cors.go matched the Origin header via\nstrings.HasPrefix — \"http://localhost.attacker.com\" or\n\"https://127.0.0.1.attacker.com\" would slip through, because the\nmatcher anchored the start of the URL but had no terminator at the\nhostname boundary.\n\nThis is exploitable on the refere\n[…]\nWebSocket origin check now routes through the same\noriginAllowed helper, so this fix tightens both the REST and WS\npaths together.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "security: tighten localhost origin matcher (H3)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T21:41:26Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "184ba21b62c5d1c0c3d0b5ce08c0b1a0796e1078",
          "body": "Two issues addressed on the WebSocket handshake path:\n\n1. Cross-site WebSocket hijacking. `websocket.Accept` was invoked\n   with `InsecureSkipVerify: true`, so no Origin check ran. A page\n   on evil.com could open a WS to a known matchID and stream a named\n   seat's PlayerView (their hidden hand, re\n[…]\n (403), chat impersonation\nrejected. Existing tests (connected, deltastate) were updated to\nthread credentials through the WS URL.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "security: WebSocket origin + credential gating (H2)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T21:38:36Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b32680a7c904d1f257eeb5c46cb900e8b6f9b093",
          "body": "LogEntry.Args's doc says: \"this slice is nilified per-seat at the\nmoment of PlayerView.\" A `redactedLog` helper for that exists in\ncore/log.go and is unit-tested via TestRedactedLogHidesArgsFromOthers\n— but PlayerView never called it. Every move flagged `Move.Redact:\ntrue` leaked its args to every c\n[…]\ncted args nilified for spectators\n- non-redacted entries unaffected\n- the source slice is not mutated (redactedLog returns a copy)\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "security: PlayerView now redacts Log/Undone args (H1)",
          "author_name": "Tyler Crandall",
          "author_login": "tjcran",
          "committed_at": "2026-05-13T21:38:17Z",
          "body_truncated": true,
          "is_coding_agent": true
        }
      ],
      "releases_count": 19,
      "commits_last_year": 126,
      "latest_release_at": "2026-07-17T14:33:49Z",
      "latest_release_tag": "v0.7.2",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 9,
      "days_since_latest_release": 4,
      "mean_days_between_releases": 6.6
    },
    "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/tjcran/boardgame-go",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "go",
          "matches_repo": true,
          "registry_url": "https://pkg.go.dev/github.com/tjcran/boardgame-go",
          "is_deprecated": false,
          "latest_version": "v0.7.2",
          "repository_url": "https://github.com/tjcran/boardgame-go",
          "versions_count": 20,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": null,
          "latest_published_at": "2026-07-17T00:14:15Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        }
      ]
    },
    "popularity": {
      "forks": 1,
      "stars": 0,
      "watchers": 0,
      "fork_history": {
        "days": [
          {
            "date": "2026-06-06",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 1,
        "total_forks": 1
      },
      "star_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_stars": 0
      },
      "open_issues_and_prs": 4
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "cookbook",
        "example"
      ],
      "has_llms_txt": false,
      "has_dockerfile": true,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [
        "go.mod",
        "mcp/go.mod"
      ],
      "largest_source_bytes": 58446,
      "source_files_sampled": 316,
      "oversized_source_files": 0,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "go.mod",
        "mcp/go.mod"
      ],
      "advisories": {
        "error": null,
        "scope": "repository_graph",
        "source": "osv",
        "findings": [
          {
            "name": "golang.org/x/text",
            "direct": false,
            "version": "v0.37.0",
            "severity": "unknown",
            "ecosystem": "go",
            "cvss_score": null,
            "advisory_ids": [
              "GO-2026-5970"
            ],
            "fixed_version": "0.39.0",
            "advisory_count": 1,
            "oldest_advisory_days": 7
          }
        ],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {
          "unknown": 1
        },
        "advisory_count": 1,
        "affected_count": 1,
        "assessed_count": 35,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "go"
      ],
      "dependencies": [
        {
          "name": "github.com/alicebob/miniredis/v2",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v2.33.0"
        },
        {
          "name": "github.com/coder/websocket",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.8.12"
        },
        {
          "name": "github.com/jackc/pgx/v5",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v5.9.2"
        },
        {
          "name": "github.com/redis/go-redis/v9",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v9.7.3"
        },
        {
          "name": "github.com/wI2L/jsondiff",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.6.1"
        },
        {
          "name": "golang.org/x/tools",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.44.0"
        },
        {
          "name": "modernc.org/sqlite",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.34.4"
        },
        {
          "name": "github.com/jackc/pgx/v5",
          "manifest": "mcp/go.mod",
          "ecosystem": "go",
          "version_constraint": "v5.9.2"
        },
        {
          "name": "github.com/tjcran/boardgame-go",
          "manifest": "mcp/go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.0.0"
        },
        {
          "name": "go.starlark.net",
          "manifest": "mcp/go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.0.0-20240520160348-046347dcd104"
        }
      ],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "github.com/alicebob/miniredis/v2",
            "direct": true,
            "version": "v2.33.0",
            "ecosystem": "go"
          },
          {
            "name": "github.com/coder/websocket",
            "direct": true,
            "version": "v1.8.12",
            "ecosystem": "go"
          },
          {
            "name": "github.com/jackc/pgx/v5",
            "direct": true,
            "version": "v5.9.2",
            "ecosystem": "go"
          },
          {
            "name": "github.com/redis/go-redis/v9",
            "direct": true,
            "version": "v9.7.3",
            "ecosystem": "go"
          },
          {
            "name": "github.com/wi2l/jsondiff",
            "direct": true,
            "version": "v0.6.1",
            "ecosystem": "go"
          },
          {
            "name": "go.starlark.net",
            "direct": true,
            "version": "v0.0.0-20240520160348-046347dcd104",
            "ecosystem": "go"
          },
          {
            "name": "golang.org/x/tools",
            "direct": true,
            "version": "v0.44.0",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/sqlite",
            "direct": true,
            "version": "v1.34.4",
            "ecosystem": "go"
          },
          {
            "name": "github.com/alicebob/gopher-json",
            "direct": false,
            "version": "v0.0.0-20200520072559-a9ecdc9d1d3a",
            "ecosystem": "go"
          },
          {
            "name": "github.com/cespare/xxhash/v2",
            "direct": false,
            "version": "v2.2.0",
            "ecosystem": "go"
          },
          {
            "name": "github.com/dgryski/go-rendezvous",
            "direct": false,
            "version": "v0.0.0-20200823014737-9f7001d12a5f",
            "ecosystem": "go"
          },
          {
            "name": "github.com/dustin/go-humanize",
            "direct": false,
            "version": "v1.0.1",
            "ecosystem": "go"
          },
          {
            "name": "github.com/google/uuid",
            "direct": false,
            "version": "v1.6.0",
            "ecosystem": "go"
          },
          {
            "name": "github.com/hashicorp/golang-lru/v2",
            "direct": false,
            "version": "v2.0.7",
            "ecosystem": "go"
          },
          {
            "name": "github.com/jackc/pgpassfile",
            "direct": false,
            "version": "v1.0.0",
            "ecosystem": "go"
          },
          {
            "name": "github.com/jackc/pgservicefile",
            "direct": false,
            "version": "v0.0.0-20240606120523-5a60cdf6a761",
            "ecosystem": "go"
          },
          {
            "name": "github.com/jackc/puddle/v2",
            "direct": false,
            "version": "v2.2.2",
            "ecosystem": "go"
          },
          {
            "name": "github.com/mattn/go-isatty",
            "direct": false,
            "version": "v0.0.20",
            "ecosystem": "go"
          },
          {
            "name": "github.com/ncruces/go-strftime",
            "direct": false,
            "version": "v0.1.9",
            "ecosystem": "go"
          },
          {
            "name": "github.com/remyoudompheng/bigfft",
            "direct": false,
            "version": "v0.0.0-20230129092748-24d4a6f8daec",
            "ecosystem": "go"
          },
          {
            "name": "github.com/tidwall/gjson",
            "direct": false,
            "version": "v1.18.0",
            "ecosystem": "go"
          },
          {
            "name": "github.com/tidwall/match",
            "direct": false,
            "version": "v1.1.1",
            "ecosystem": "go"
          },
          {
            "name": "github.com/tidwall/pretty",
            "direct": false,
            "version": "v1.2.1",
            "ecosystem": "go"
          },
          {
            "name": "github.com/tidwall/sjson",
            "direct": false,
            "version": "v1.2.5",
            "ecosystem": "go"
          },
          {
            "name": "github.com/yuin/gopher-lua",
            "direct": false,
            "version": "v1.1.1",
            "ecosystem": "go"
          },
          {
            "name": "golang.org/x/mod",
            "direct": false,
            "version": "v0.35.0",
            "ecosystem": "go"
          },
          {
            "name": "golang.org/x/sync",
            "direct": false,
            "version": "v0.20.0",
            "ecosystem": "go"
          },
          {
            "name": "golang.org/x/sys",
            "direct": false,
            "version": "v0.45.0",
            "ecosystem": "go"
          },
          {
            "name": "golang.org/x/text",
            "direct": false,
            "version": "v0.37.0",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/gc/v3",
            "direct": false,
            "version": "v3.0.0-20240107210532-573471604cb6",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/libc",
            "direct": false,
            "version": "v1.55.3",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/mathutil",
            "direct": false,
            "version": "v1.6.0",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/memory",
            "direct": false,
            "version": "v1.8.0",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/strutil",
            "direct": false,
            "version": "v1.2.0",
            "ecosystem": "go"
          },
          {
            "name": "modernc.org/token",
            "direct": false,
            "version": "v1.1.0",
            "ecosystem": "go"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 35,
        "direct_count": 8,
        "indirect_count": 27
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 1,
        "merged_prs": 76,
        "open_issues": 3,
        "closed_ratio": 0.833,
        "closed_issues": 15,
        "closed_unmerged_prs": 3
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "tjcran",
          "commits": 126,
          "avatar_url": "https://avatars.githubusercontent.com/u/7042112?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "release.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "go.sum"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 3,
            "reason": "branch protection is not maximal on development and all release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/14 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 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": 9,
            "reason": "1 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "6e0c0fb1ff9ca6e0181b7493904a917c8ac59b80",
        "ran_at": "2026-07-22T02:41:19Z",
        "aggregate_score": 3.5,
        "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-18T15:52:47Z",
      "oldest_open_prs": [
        {
          "number": 104,
          "created_at": "2026-07-22T01:44:22Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ],
      "last_merged_pr_at": "2026-07-18T15:51:04Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 6,
          "created_at": "2026-05-13T17:13:47Z",
          "last_comment_at": "2026-05-19T00:27:59Z",
          "last_comment_author": "tjcran"
        },
        {
          "number": 47,
          "created_at": "2026-05-19T00:27:28Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 83,
          "created_at": "2026-06-05T13:51:08Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/tjcran/boardgame-go",
    "host": "github.com",
    "name": "boardgame-go",
    "owner": "tjcran"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 54,
      "inputs": {
        "security": 48,
        "vitality": 72,
        "community": 24,
        "governance": 52,
        "engineering": 67
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 72,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 60,
            "inputs": {
              "commits_last_year": 126,
              "human_commit_share": 1,
              "days_since_last_push": 0,
              "active_weeks_last_year": 9
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "9/52 weeks with commits",
                "points": 6.2,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 9
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "126 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 126
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 19,
              "latest_release_tag": "v0.7.2",
              "releases_from_tags": false,
              "days_since_latest_release": 4,
              "mean_days_between_releases": 6.6
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "19 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 19
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 4 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~6.6 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 6.6
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "critical",
        "name": "Community & Adoption",
        "value": 24,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 1,
              "stars": 0,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "0 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "1 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 52,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 76,
            "inputs": {
              "merged_prs": 76,
              "open_issues": 3,
              "closed_issues": 15,
              "issue_closed_ratio": 0.833,
              "closed_unmerged_prs": 3
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "83% of issues closed",
                "points": 38.9,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 83
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "76/79 decided PRs merged",
                "points": 36.8,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 76,
                      "decided": 79
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/14 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 42,
            "inputs": {
              "followers": 3,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "tjcran",
              "public_repos": 10,
              "account_age_days": 4503
            },
            "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": "3 followers of tjcran",
                "points": 4.3,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 3,
                      "login": "tjcran"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "10 public repos, account ~12 yr old",
                "points": 19.6,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 10
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 12
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "github.com/tjcran/boardgame-go"
              ],
              "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": "20 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 20
                    }
                  }
                ],
                "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": 67,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 68,
            "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": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "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 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "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": 48,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 35,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 3.5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection is not maximal on development and all release branches",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/14 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow 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 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file 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": "1 existing vulnerabilities detected",
                "points": 6.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): Indirect dependencies free of known advisories, No advisories left outstanding. Remaining weights renormalized. Matched 35 resolved dependencies against OSV. This repository publishes no package the index resolves, so the repository dependency graph was assessed instead. That graph mixes development and test pins with shipped dependencies, so only the declared runtime dependencies are scored; transitive findings are reported as context and excluded from the score. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "indirect_dependencies_free_of_known_advisories",
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_repository",
                "params": {
                  "assessed": 35
                }
              },
              {
                "code": "advisories_repo_graph_caveat",
                "params": {}
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 1,
              "affected_packages": 1,
              "assessed_packages": 35,
              "unassessed_packages": 0,
              "affected_by_severity": "unknown 1",
              "direct_affected_packages": 0
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "no direct dependency carries a known advisory",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "no_direct_advisories",
                    "params": {}
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "transitive set not separable from development and test dependencies in this scope",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_scope_not_separable",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "no advisory carries a publication date",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_no_publication_date",
                    "params": {}
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 35,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 59,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.93,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "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": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 66,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "go.sum"
              ],
              "has_dockerfile": true,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [],
              "agent_commit_share": 0.65,
              "toolchain_manifests": [
                "go.mod",
                "mcp/go.mod"
              ],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "go.mod, mcp/go.mod (toolchain convention, no task runner)",
                "points": 12.6,
                "status": "partial",
                "details": [
                  {
                    "code": "toolchain_convention",
                    "params": {
                      "files": "go.mod, mcp/go.mod"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "Go (statically typed)",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "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": "65 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 65,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "primary_language": "Go",
              "largest_source_bytes": 58446,
              "source_files_sampled": 316,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Go (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/316 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 316,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "at_risk",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "example_dirs": [
                "cookbook",
                "example"
              ],
              "has_mcp_signal": false,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "cookbook, example",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "cookbook, example"
                    }
                  }
                ],
                "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": [],
  "report_type": "repository",
  "generated_at": "2026-07-22T02:41:34.209133Z",
  "schema_version": "0.26.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/t/tjcran/boardgame-go.svg",
  "full_name": "tjcran/boardgame-go",
  "license_state": "standard",
  "license_spdx": "MIT"
}

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

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

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