Raw JSON report machine-readable
{
"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"
}