@witchpot/gmc 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,42 @@
2
2
 
3
3
  All notable changes to the Game Market Copilot CLI will be recorded here.
4
4
 
5
- ## Unreleased
5
+ ## 0.4.0 - 2026-07-09
6
+
7
+ - gmc-analysis skill v0.4.0: new `references/validity.md` with the
8
+ statistical-validity checklist and claim-safety rules (NAK-82), and new
9
+ pitfall P9 documenting the single-assignment vs multi-assignment tag
10
+ counting bases (NAK-81). Both ship in `gmc skill install` and the
11
+ `gmc skill print --format agents-md` self-contained edition.
12
+ - Bundled chart-card generation into the gmc-analysis skill (v0.3.0,
13
+ NAK-89): new `references/charts.md` with attribution rules (fixed
14
+ `GMC database` source label, brand lockup, denominator + observation
15
+ window caption, no causal titles), a chart-type mapping from gmc output
16
+ shapes, and a self-contained HTML/Chart.js card template. The reference
17
+ is included in `gmc skill install` and in the
18
+ `gmc skill print --format agents-md` self-contained edition.
19
+
20
+ ## 0.3.0 - 2026-07-05
21
+
22
+ - Added `gmc tags` for tag taxonomy discovery: `gmc tags` lists the full
23
+ slug/name dictionary and `gmc tags --q <term> [--limit <number>]` searches
24
+ it. The endpoint is anonymous and unmetered, so agents can resolve a genre
25
+ or keyword to valid tag slugs before spending quota on `--tags` filters.
26
+ - Split shared, pure composition and analytics helpers (resolve/cohort/
27
+ showcase composers, deadline filtering) out of the CLI workflow layer and
28
+ into the new `@witchpot/gmc-core` package, now a published dependency of
29
+ this CLI. No command-facing behavior change.
30
+ - This release rides on server-side additions already live in the API:
31
+ - Tag filters on games search/count/aggregate/benchmark, review patterns,
32
+ and the marketing/creators/cohort/showcases workflows now validate and
33
+ canonicalize `--tags` slugs, rejecting unknown ones with a 400
34
+ `INVALID_TAG` error carrying `details.unknown_tags` and
35
+ `details.suggestions`. `gmc tags` is the recommended preflight for
36
+ resolving valid slugs before hitting these filters.
37
+ - The `market_aggregate` MCP tool gained `segment_filter`, which narrows
38
+ the base filter (AND) and reports `segment.matched`/`segment.ratio`
39
+ against the base cohort at no extra credit cost (not yet combinable
40
+ with `group_by`).
6
41
 
7
42
  ## 0.2.1 - 2026-07-04
8
43
 
package/README.md CHANGED
@@ -30,7 +30,9 @@ A fuller user guide lives in
30
30
 
31
31
  Closed-beta access is controlled by GMC API credentials, not npm package
32
32
  visibility. Installing the public package does not grant access to paid or beta
33
- data.
33
+ data. Authenticated access (login, API keys) requires a paid GMC plan
34
+ (Starter or Pro) — see https://gamemarketcopilot.com/plans. Without
35
+ credentials the CLI serves redacted public previews with a small quota.
34
36
 
35
37
  ## Auth
36
38
 
@@ -64,8 +66,10 @@ a non-secret prefix.
64
66
  ## Agent Skill
65
67
 
66
68
  The package bundles the `gmc-analysis` Agent Skill — analysis recipes, pivot
67
- heuristics, quota etiquette, and honesty rules for coding agents. The core
68
- analysis guidance is shared, but the registration step depends on the agent.
69
+ heuristics, quota etiquette, honesty rules, and branded chart-card generation
70
+ (publish-ready, correctly attributed charts from gmc output) for coding
71
+ agents. The core analysis guidance is shared, but the registration step
72
+ depends on the agent.
69
73
 
70
74
  For Claude Code, install the packaged skill directory:
71
75
 
@@ -91,6 +95,7 @@ Re-run install/print after `npm update` to pick up newer skill versions.
91
95
 
92
96
  ```bash
93
97
  gmc schema --json
98
+ gmc tags --q roguelike --json
94
99
  gmc update --json
95
100
  gmc games search --source steam --release-year 2025 --reviews-min 1000 --fields title,appid,rating,reviews --json
96
101
  gmc games aggregate --source steam --release-year 2025 --metric count,median_reviews --group-by release_year --reviews-min 1000 --json
@@ -107,6 +112,7 @@ when using output in agent answers.
107
112
  - `gmc status`
108
113
  - `gmc auth status`
109
114
  - `gmc schema`
115
+ - `gmc tags`
110
116
  - `gmc games search`
111
117
  - `gmc games count`
112
118
  - `gmc games aggregate`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@witchpot/gmc",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Command line tools for Game Market Copilot market research.",
6
6
  "license": "UNLICENSED",
@@ -27,6 +27,9 @@
27
27
  "engines": {
28
28
  "node": ">=20.9.0"
29
29
  },
30
+ "dependencies": {
31
+ "@witchpot/gmc-core": "^0.1.0"
32
+ },
30
33
  "files": [
31
34
  "bin",
32
35
  "src",
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawnSync } from "node:child_process";
3
4
  import { existsSync, readFileSync } from "node:fs";
4
5
  import path from "node:path";
5
6
 
@@ -16,7 +17,9 @@ const requiredFiles = [
16
17
  "skill/gmc-analysis/SKILL.md",
17
18
  "skill/gmc-analysis/references/recipes.md",
18
19
  "skill/gmc-analysis/references/pitfalls.md",
20
+ "skill/gmc-analysis/references/validity.md",
19
21
  "skill/gmc-analysis/references/quota.md",
22
+ "skill/gmc-analysis/references/charts.md",
20
23
  "skill/gmc-analysis/CHANGELOG.md",
21
24
  "README.md",
22
25
  "CHANGELOG.md"
@@ -119,6 +122,34 @@ for (const file of sourceFiles) {
119
122
  }
120
123
  }
121
124
 
125
+ // The public distribution repo (github.com/witchpot-studio/gmc-agent-skills)
126
+ // mirrors skill/gmc-analysis. When it is checked out as a workspace sibling,
127
+ // a drift between the two is a release blocker — MCP-only users install the
128
+ // skill from that repo and must get the same content the npm package ships.
129
+ const explicitAgentSkillsDir = process.env.GMC_AGENT_SKILLS_DIR;
130
+ const agentSkillsRepo =
131
+ explicitAgentSkillsDir ?? path.join(packageDir, "..", "..", "..", "gmc-agent-skills");
132
+ const agentSkillsSyncScript = path.join(agentSkillsRepo, "scripts", "sync-from-cli.mjs");
133
+ if (explicitAgentSkillsDir && !existsSync(agentSkillsSyncScript)) {
134
+ failures.push(
135
+ `GMC_AGENT_SKILLS_DIR is set but ${agentSkillsSyncScript} does not exist — fix the path or unset the variable.`
136
+ );
137
+ } else if (existsSync(agentSkillsSyncScript)) {
138
+ const drift = spawnSync(process.execPath, ["scripts/sync-from-cli.mjs", "--check"], {
139
+ cwd: agentSkillsRepo,
140
+ encoding: "utf8",
141
+ // Pin the canonical source to THIS checkout — the sync script's own
142
+ // default (../GMC relative to the skills repo) could silently compare
143
+ // against a different GMC checkout.
144
+ env: { ...process.env, GMC_REPO_DIR: path.join(packageDir, "..", "..") }
145
+ });
146
+ if (drift.status !== 0) {
147
+ failures.push(
148
+ `gmc-agent-skills repo has drifted from skill/gmc-analysis — run node scripts/sync-from-cli.mjs there.\n${(drift.stderr || "").trim()}`
149
+ );
150
+ }
151
+ }
152
+
122
153
  if (failures.length > 0) {
123
154
  process.stderr.write(`GMC CLI release readiness failed:\n${failures.map((failure) => `- ${failure}`).join("\n")}\n`);
124
155
  process.exit(1);
@@ -81,6 +81,9 @@ try {
81
81
  if (!skillPrintResult.stdout.includes("GMC Analysis") || !skillPrintResult.stdout.includes("Pitfalls")) {
82
82
  throw new Error("Packed gmc skill print did not emit the inlined agents-md section");
83
83
  }
84
+ if (!skillPrintResult.stdout.includes("Chart cards") || !skillPrintResult.stdout.includes("gmc-lockup.svg")) {
85
+ throw new Error("Packed gmc skill print did not inline the chart cards reference");
86
+ }
84
87
 
85
88
  // gmc is shadowed by Oh My Zsh's `git merge --continue` alias, so the
86
89
  // package ships alias binaries; make sure npm actually installed them.
@@ -1,5 +1,47 @@
1
1
  # gmc-analysis skill changelog
2
2
 
3
+ ## 0.4.0 - 2026-07-09
4
+
5
+ - Added `references/validity.md` (NAK-82): statistical-validity checklist
6
+ (denominator + N on every share, observation window, counting basis,
7
+ N<30 warning, effect size over bare significance, confound
8
+ stratification for publisher axis / price / release year, multiple-
9
+ comparison disclosure) and claim-safety rules (no causal verbs for
10
+ observational associations, external-estimate labeling, `not_collected`
11
+ means not collected), with a compliant output example. Wired into the
12
+ SKILL.md hard rules and the AGENTS.md edition.
13
+ - New pitfall P9 (NAK-81): single-assignment (`primary_market_tag`) vs
14
+ multi-assignment (`tag` grouping / `tags` filter) tag counting bases
15
+ diverge sharply — pick one basis per question, name it next to the
16
+ denominator, never mix or compare across bases.
17
+
18
+ ## 0.3.1 - 2026-07-08
19
+
20
+ - quota.md: updated the paid allowances to the resized uniform per-credit
21
+ pricing (Starter 2,500 / Pro 7,000 credits per month, NAK-110); reset and
22
+ gate-time charging semantics unchanged.
23
+
24
+ ## 0.3.0 - 2026-07-07
25
+
26
+ - Added `references/charts.md`: publish-ready branded chart cards from gmc
27
+ CLI / MCP outputs (NAK-89). Bakes attribution into the artifact — fixed
28
+ `GMC database` source label, brand lockup (icon + wordmark), denominator
29
+ + observation window caption, no causal titles — and maps gmc output
30
+ shapes (market aggregates, cohort evidence, coverage checks, single
31
+ KPIs) to chart types with a self-contained HTML/Chart.js template.
32
+ - SKILL.md: new "Chart cards" section wiring the reference into the
33
+ workflow and into the AGENTS.md edition.
34
+ - SKILL.md: new "MCP-only setups" section mapping the CLI workflow onto
35
+ the GMC MCP tools (market_aggregate, coverage_check, cohort_evidence,
36
+ game_profile, ...) so agents with only the MCP connection do not attempt
37
+ gmc commands (NAK-98).
38
+ - quota.md: corrected to the monthly credit model (Starter 5,000 / Pro
39
+ 25,000 credits per month, reset on the 1st at 00:00 UTC, gate-time
40
+ charging) — the per-UTC-day request caps it described were outdated.
41
+ - Documented the paid-plan requirement (Starter/Pro) for authenticated
42
+ access, and the `PLAN_REQUIRED` vs `QUOTA_EXCEEDED` distinction for
43
+ MCP tool errors (upgrade vs wait-for-reset).
44
+
3
45
  ## 0.2.0 - 2026-06-12
4
46
 
5
47
  - Folded in the five learnings from the first field test (2025 city
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: gmc-analysis
3
3
  description: >-
4
- Use when analyzing Steam game market data with the gmc CLI (Game Market
5
- Copilot) — cross-game cohort analysis (common complaints, marketing
6
- patterns, pricing), single-title deep dives, market sizing, or any question
7
- about Steam games, reviews, creators, showcases, or campaigns that gmc can
8
- answer.
4
+ Use when analyzing Steam game market data with Game Market Copilot (the
5
+ gmc CLI or the GMC MCP tools) — cross-game cohort analysis (common
6
+ complaints, marketing patterns, pricing), single-title deep dives, market
7
+ sizing, or any question about Steam games, reviews, creators, showcases,
8
+ or campaigns that GMC can answer — including turning results into
9
+ publish-ready branded chart cards.
9
10
  metadata:
10
- version: 0.2.0
11
+ version: 0.4.0
11
12
  ---
12
13
 
13
14
  # GMC Analysis
@@ -26,12 +27,49 @@ gmc auth status --json # anonymous => redacted previews + tiny quota
26
27
  gmc schema --json # the authoritative command/endpoint reference
27
28
  ```
28
29
 
30
+ Authenticated access (CLI login, API keys, MCP) requires a paid GMC plan
31
+ (Starter or Pro); Free workspaces cannot mint credentials. Anonymous CLI
32
+ use works but returns redacted previews with a tiny quota.
33
+
29
34
  Never guess flags: `gmc schema --json` is the source of truth for commands
30
35
  and parameters. Endpoint query params are camelCase and the matching CLI
31
36
  flag is the kebab-case equivalent (`priceMin` -> `--price-min`,
32
37
  `reviewsGt` -> `--reviews-gt`). Per-command `--help` currently prints one
33
38
  generic usage list, so trust the schema. Always pass `--json`.
34
39
 
40
+ ## MCP-only setups (no CLI installed)
41
+
42
+ If you have the GMC MCP connection instead of the CLI, do NOT attempt gmc
43
+ commands — use the MCP tools; every workflow and honesty rule in this skill
44
+ applies identically. Mechanics map as follows:
45
+
46
+ - Sizing and denominators (`gmc games count` / `games aggregate`) ->
47
+ `market_aggregate` (population-true; prefer it over paginating
48
+ `list_games`).
49
+ - Coverage pre-check -> `coverage_check` before fanning out `game_profile`
50
+ on more than 3 titles.
51
+ - Cohort evidence (`reviews patterns`, `cohort ...`) -> `cohort_evidence`,
52
+ `cohort_review_categories`, `search_review_claims`.
53
+ - Single-title drill-down (`games detail` / `games analysis`) ->
54
+ `game_profile`; name resolution -> `resolve`; showcases ->
55
+ `showcase_fit`.
56
+ - The response envelope differs from the CLI: MCP tool responses carry
57
+ `meta.quota` = `{ used, limit, remaining, resets_at }` (credits) plus
58
+ `meta.credits_charged`, `meta.basis`, `meta.denominator`,
59
+ `meta.coverage`, and `meta.warnings` — there is no `meta.entitlements`
60
+ wrapper. The read-before-you-spend and honesty rules apply unchanged.
61
+ - CLI-only mechanics (pagination flags, `--fields` projection, map-reduce
62
+ recipes) may have no MCP equivalent — stay within the tool contracts
63
+ instead of simulating them.
64
+ - A `PLAN_REQUIRED` tool error means the workspace plan has no API access
65
+ (e.g. Free) — retrying or waiting will not help; tell the user an
66
+ upgrade at gamemarketcopilot.com/plans is required. Only
67
+ `QUOTA_EXCEEDED` is the wait-for-monthly-reset condition.
68
+
69
+ References in this skill use CLI syntax; translate to the matching MCP
70
+ tool. Chart-card rules (references/charts.md) apply to both paths
71
+ unchanged.
72
+
35
73
  ## Core workflow for cohort questions
36
74
 
37
75
  1. **Size the cohort and its research coverage first** (1-2 requests):
@@ -69,6 +107,16 @@ unless you explicitly pass `--include-total`; for count-only questions use
69
107
  - **Bounded evidence, not market truth.** Carry the CLI's own caveats:
70
108
  evidence packs are sampled/returned-page evidence. Always state
71
109
  denominators ("87 of 107 analyzed titles") and preserve `warnings`.
110
+ - **Pass the validity checklist before handing off numbers.** Every
111
+ reported share carries its denominator, N, observation window, and tag
112
+ counting basis; small-N groups are labeled directional; group
113
+ comparisons are stratified for obvious confounds (publisher axis, price,
114
+ release year) or the unchecked confound is named; scanned-extreme
115
+ findings disclose the scan. Details in the validity reference.
116
+ - **Claim safety.** No causal verbs for observational associations; label
117
+ estimates not derived from gmc data as external with a source;
118
+ `not_collected` means not collected, never zero or absent. Details in
119
+ the validity reference.
72
120
  - **Separate API facts from your synthesis.** Quote theme labels and player
73
121
  counts as data; label your clustering and conclusions as your analysis.
74
122
  - **Pace your quota.** Read `meta.entitlements.quota` (`used`/`limit`)
@@ -76,12 +124,24 @@ unless you explicitly pass `--include-total`; for count-only questions use
76
124
  Details in the quota reference.
77
125
  - Preserve `meta.requestId` values for anything you may need to report.
78
126
 
127
+ ## Chart cards
128
+
129
+ When the user wants a shareable or publish-ready visual of gmc results,
130
+ follow the charts reference. Its rules are part of GMC attribution: a chart
131
+ under the GMC label must carry the fixed source label, the brand lockup,
132
+ and a denominator + observation window caption, and its title must not
133
+ make causal claims. Never emit a GMC-attributed chart that skips these.
134
+
79
135
  ## References
80
136
 
81
137
  - `references/recipes.md` — step-by-step playbooks (cohort complaints,
82
138
  full-cohort map-reduce, single-title deep dive, market sizing).
83
139
  - `references/pitfalls.md` — known failure modes and the correct pivots.
140
+ - `references/validity.md` — statistical-validity checklist and
141
+ claim-safety rules for any published number.
84
142
  - `references/quota.md` — quota model and cost estimation.
143
+ - `references/charts.md` — branded chart cards: attribution rules, chart
144
+ type mapping from gmc output shapes, self-contained HTML template.
85
145
 
86
146
  (In the AGENTS.md edition of this skill the references are inlined below.)
87
147
 
@@ -0,0 +1,236 @@
1
+ # Chart cards
2
+
3
+ Turn gmc CLI / MCP outputs into publish-ready, correctly attributed GMC
4
+ chart cards (self-contained HTML -> PNG). Follow this reference whenever a
5
+ chart, graph, or shareable visual is requested from GMC data. A chart that
6
+ violates the non-negotiables below must not carry GMC attribution.
7
+
8
+ ## Non-negotiables (every card)
9
+
10
+ 1. **Footer, left**: the fixed source label `GMC database` plus at most one
11
+ short factual note (collection method, cutoff, exclusions). Japanese
12
+ edition: `GMCデータベース`. Never change, translate loosely, or omit the
13
+ label.
14
+ 2. **Footer, right**: the brand lockup — icon + wordmark, one image:
15
+ `https://gamemarketcopilot.com/assets/gmc-lockup.svg`. Never the icon
16
+ alone, never a retyped wordmark, never someone else's logo.
17
+ 3. **Caption (top-left of the card)**: denominator + observation window,
18
+ e.g. `1,204 of 20,994 titles released 2025 / aggregated as of
19
+ 2026-07-06`. Take denominators from `gmc games count` (or the tool's own
20
+ `cohort.usableGames`), not from page sizes. The caption is the card's
21
+ denominator disclosure — exports must never crop it (or the footer) off.
22
+ 4. **Title**: state the observed fact — a level, difference, share, or
23
+ rank. No causal verbs (`drove`, `boosted`, `caused`, `led to`,
24
+ `because`); the data supports co-occurrence, not causation. No hype
25
+ metaphors. Correlation phrasing is fine.
26
+ 5. **Data honesty**: `not_collected` is never charted as zero. Locked or
27
+ `preview: true` payloads are never charted as full data. Small
28
+ denominators (n < 30) are presented as individual cases, not a trend
29
+ bar. `skippedGames` / `skippedReasons` go in the footnote when they
30
+ change the reading.
31
+ 6. **External estimates stay outside the label**: units multipliers,
32
+ wishlist folklore, or any non-GMC number never appear under the
33
+ `GMC database` label — prefer leaving them off a GMC-branded card
34
+ entirely. If one must appear, add a second, visually separate footnote
35
+ line: `External estimate: <source> — not GMC data`.
36
+ 7. **Opaque background** (`#fafaf7` page, `#ffffff` card). Never export
37
+ transparent PNGs. Recommended export: X 1200x675, LinkedIn 1200x627,
38
+ or the natural 720px card width at 2x.
39
+
40
+ ## Picking the chart type from gmc output shapes
41
+
42
+ | Output | Shape | Chart |
43
+ |---|---|---|
44
+ | `gmc games aggregate --group-by <dim>` / MCP `market_aggregate` | 6+ groups, rank matters | Horizontal bar, sorted desc |
45
+ | same | 2-5 groups | Vertical column |
46
+ | same, time buckets (release windows, months) | series over time | Line (single/few series) or column |
47
+ | MCP `market_aggregate` with `segment_filter` | segment vs base | Two big numbers + ratio, or grouped bar |
48
+ | `gmc reviews patterns` / MCP `cohort_evidence` / clustered `negativeThemes` | theme -> game counts | Horizontal bar; caption carries `x of N usable games` |
49
+ | MCP `coverage_check` / `gmc games count --group-by coverage` | composition, 2-4 segments | Donut with absolute counts printed, or one 100% stacked bar |
50
+ | single KPI (one count, one median, one share) | one number | Hero number card (no chart canvas) |
51
+
52
+ Rules of thumb: when unsure, use a horizontal bar. One message per card.
53
+ Never: 3D, dual axes, cropped bar baselines, pie with 5+ slices, line
54
+ charts for non-time data.
55
+
56
+ ## Color
57
+
58
+ Neutrals (fixed): page `#fafaf7`, card `#ffffff`, ink `#1a1d20`, muted
59
+ `#3e454d`, faint `#818d9a`, grid `rgba(26,29,32,0.06)`.
60
+
61
+ - **Single series / ranking**: accent ladder only — Ember `#EC7336` at
62
+ alpha 0.85 (lead), 0.55, 0.30, rest `rgba(26,29,32,0.08)`.
63
+ - **2-3 categories**: Ember `#EC7336` + Seagrass `#1AA29D` (+ Inkwell
64
+ `#4057C7`). Never distinguish categories by alpha alone.
65
+ - **4-5 categories**: add Moss `#3E9C5F`, Mulberry `#9B4ABF`. Max 5 colors;
66
+ gray out non-focus series with `rgba(26,29,32,0.12)`. 6+ categories:
67
+ split the chart or bucket into "other".
68
+ - Direct labels beat legends: print values at bar ends / line ends.
69
+
70
+ ## Card template (self-contained)
71
+
72
+ Fill the `{{...}}` slots, adjust the dataset + type per the table above,
73
+ open in a browser, capture the `.card` element as PNG. The template needs
74
+ network access for fonts, Chart.js CDN, and the brand lockup.
75
+
76
+ ```html
77
+ <!DOCTYPE html>
78
+ <html lang="en">
79
+ <head>
80
+ <meta charset="utf-8">
81
+ <meta name="viewport" content="width=device-width, initial-scale=1">
82
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=Instrument+Serif&display=swap" rel="stylesheet">
83
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
84
+ <style>
85
+ :root {
86
+ --bg: #fafaf7; --card: #ffffff;
87
+ --ink: #1a1d20; --ink-muted: #3e454d; --ink-faint: #818d9a;
88
+ --rule: #e8e5de; --accent: #ec7336;
89
+ --sans: 'Inter', system-ui, sans-serif;
90
+ --mono: 'JetBrains Mono', ui-monospace, monospace;
91
+ --serif: 'Instrument Serif', Georgia, serif;
92
+ }
93
+ * { margin: 0; padding: 0; box-sizing: border-box; }
94
+ body { background: var(--bg); font-family: var(--sans); color: var(--ink);
95
+ display: flex; justify-content: center; padding: 40px 16px; }
96
+ .card { width: 720px; max-width: 100%; background: var(--card);
97
+ border: 1px solid var(--rule); display: flex; flex-direction: column; }
98
+ .card-head { display: flex; justify-content: space-between; gap: 12px;
99
+ padding: 18px 20px 12px; border-bottom: 1px solid var(--rule); }
100
+ .card-num { font-family: var(--mono); font-size: 12.5px; font-weight: 500;
101
+ color: var(--accent); max-width: 76%; }
102
+ .card-cat { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em;
103
+ text-transform: uppercase; color: var(--ink-faint); white-space: nowrap; }
104
+ .card-title { padding: 26px 20px 8px; font-family: var(--serif);
105
+ font-size: 34px; line-height: 1.12; font-weight: 400; }
106
+ .chart-wrap { position: relative; margin: 18px 20px 8px;
107
+ min-height: 470px; height: 470px; }
108
+ .card-foot { display: flex; justify-content: space-between; align-items: center;
109
+ gap: 16px; border-top: 1px solid var(--rule);
110
+ padding: 14px 20px; margin-top: 12px; }
111
+ .foot-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
112
+ .foot-source { font-family: var(--mono); font-size: 13px; color: var(--ink-muted); }
113
+ .foot-note { font-size: 11.5px; line-height: 1.45; color: var(--ink-faint); }
114
+ .brand-lockup { flex: 0 0 auto; height: 24px; width: auto; display: block; }
115
+ </style>
116
+ </head>
117
+ <body>
118
+ <article class="card">
119
+ <div class="card-head">
120
+ <div class="card-num">{{DENOMINATOR AND WINDOW, e.g. 1,204 of 20,994 titles released 2025 / as of 2026-07-06}}</div>
121
+ <div class="card-cat">{{CHART TYPE, e.g. RANKED BAR}}</div>
122
+ </div>
123
+ <h2 class="card-title">{{FACTUAL TITLE — observed level/difference/share, no causal verbs}}</h2>
124
+ <div class="chart-wrap"><canvas id="chart"></canvas></div>
125
+ <div class="card-foot">
126
+ <div class="foot-meta">
127
+ <div class="foot-source">GMC database</div>
128
+ <div class="foot-note">{{ONE-LINE NOTE: method, cutoff, exclusions, skipped counts}}</div>
129
+ </div>
130
+ <img class="brand-lockup" src="https://gamemarketcopilot.com/assets/gmc-lockup.svg" alt="Game Market Copilot">
131
+ </div>
132
+ </article>
133
+ <script>
134
+ const ink = (a) => `rgba(26, 29, 32, ${a})`;
135
+ const accent = (a) => `rgba(236, 115, 54, ${a})`;
136
+ Chart.defaults.font.family = "'Inter', system-ui, sans-serif";
137
+ Chart.defaults.font.size = 13;
138
+ Chart.defaults.color = '#3e454d';
139
+
140
+ // Direct value labels at bar ends (skip for line charts; use point labels there).
141
+ const valueLabels = {
142
+ id: 'valueLabels',
143
+ afterDatasetsDraw(chart) {
144
+ const { ctx } = chart;
145
+ const horizontal = chart.options.indexAxis === 'y';
146
+ chart.data.datasets.forEach((ds, di) => {
147
+ chart.getDatasetMeta(di).data.forEach((el, i) => {
148
+ const v = ds.data[i];
149
+ if (v == null) return;
150
+ ctx.save();
151
+ ctx.font = "500 12px 'JetBrains Mono', monospace";
152
+ ctx.fillStyle = '#3e454d';
153
+ ctx.textBaseline = 'middle';
154
+ if (horizontal) { ctx.textAlign = 'left'; ctx.fillText(v.toLocaleString(), el.x + 8, el.y); }
155
+ else { ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; ctx.fillText(v.toLocaleString(), el.x, el.y - 6); }
156
+ ctx.restore();
157
+ });
158
+ });
159
+ }
160
+ };
161
+
162
+ const labels = ['{{GROUP A}}', '{{GROUP B}}', '{{GROUP C}}']; // sorted desc for rankings
163
+ const values = [0, 0, 0]; // real aggregates only
164
+ new Chart(document.getElementById('chart'), {
165
+ type: 'bar', // ranked bar. Column: drop indexAxis. Line: type 'line'.
166
+ data: { labels, datasets: [{
167
+ data: values,
168
+ // Ranking ladder — lead bar strongest, tail neutral:
169
+ backgroundColor: values.map((_, i) => [accent(0.85), accent(0.55), accent(0.30)][i] ?? ink(0.08)),
170
+ borderRadius: 2, barPercentage: 0.72
171
+ }]},
172
+ options: {
173
+ indexAxis: 'y', responsive: true, maintainAspectRatio: false,
174
+ layout: { padding: { right: 64 } }, // room for end-of-bar labels
175
+ plugins: { legend: { display: false }, tooltip: { enabled: false } },
176
+ scales: {
177
+ x: { beginAtZero: true, grid: { color: ink(0.06) }, border: { color: ink(0.18) } },
178
+ y: { grid: { display: false }, border: { color: ink(0.18) } }
179
+ },
180
+ animation: { onComplete: () => { window.__animComplete = true; } }
181
+ },
182
+ plugins: [valueLabels]
183
+ });
184
+ </script>
185
+ </body>
186
+ </html>
187
+ ```
188
+
189
+ Variants:
190
+
191
+ - **Column** (2-5 groups): remove `indexAxis`, move padding to `top`,
192
+ labels render above bars automatically via the plugin.
193
+ - **Line** (time buckets): `type: 'line'`, `borderColor: accent(0.85)`,
194
+ `pointRadius: 3`, `fill: false` (always false when more than one dataset
195
+ or when combined with bars), label only first/last/inflection points.
196
+ - **Donut** (coverage split, max 4 segments): `type: 'doughnut'`,
197
+ `cutout: '62%'`, colors Ember/Seagrass/Inkwell + gray; print
198
+ `label — count (share%)` per segment as direct labels or a compact list
199
+ under the chart; absolute counts are mandatory, share alone is not
200
+ enough.
201
+ - **Hero number** (single KPI): replace `.chart-wrap` with
202
+ `<div style="padding:56px 20px 40px; text-align:center;">
203
+ <div style="font-size:96px; font-weight:600; letter-spacing:-0.04em;
204
+ color:var(--accent);">{{VALUE}}</div>
205
+ <div style="font-size:15px; color:var(--ink-muted); margin-top:8px;">
206
+ {{ONE-LINE QUALIFIER}}</div></div>` and delete the chart script. The
207
+ caption and footer rules still apply unchanged.
208
+
209
+ Japanese edition: add `&family=Noto+Sans+JP:wght@400;500;600` to the fonts
210
+ URL, append `'Noto Sans JP'` to `--sans`, set `<html lang="ja">`, and use
211
+ `GMCデータベース` as the source label. Steam tags in JA charts:
212
+ `English Tag(日本語タグ名)` on first appearance.
213
+
214
+ ## Capture
215
+
216
+ Any browser screenshot of the `.card` element works. Headless example:
217
+
218
+ ```bash
219
+ npx playwright screenshot --wait-for-timeout 2500 --full-page card.html card.png
220
+ ```
221
+
222
+ then crop to the card, or capture the element via a driver that waits for
223
+ `window.__animComplete === true`. Verify before publishing: bars/labels not
224
+ clipped, text legible at mobile size (12px+ effective), background opaque.
225
+
226
+ ## Pre-publish checklist
227
+
228
+ - [ ] Title is factual, no causal verbs, no hype
229
+ - [ ] Caption has denominator AND observation window
230
+ - [ ] `GMC database` label + brand lockup both present, unmodified
231
+ - [ ] Every number comes from a real gmc/MCP response in this session
232
+ (no remembered or estimated values); re-run the query if stale
233
+ - [ ] `not_collected` / locked data not charted; skipped counts disclosed
234
+ when they change the reading
235
+ - [ ] External estimates (if any) credited separately in the footnote
236
+ - [ ] Colors follow the tier rules; max 5 colors; opaque background
@@ -72,3 +72,53 @@ single-request server aggregates. Reserve pagination loops for when you
72
72
  actually need the rows (recipe R2). `gmc games search` is optimized for rows
73
73
  and may omit `pagination.total`; pass `--include-total` only when a row page
74
74
  also needs an exact count.
75
+
76
+ ## P8. Wide aggregations can time out (2026-07)
77
+
78
+ Aggregate calls that request many metrics and/or two `group_by` dimensions
79
+ over the full catalog can exceed the server-side query timeout. Over MCP this
80
+ surfaces as an `UPSTREAM_TIMEOUT` tool error (older builds: a generic
81
+ `UPSTREAM_ERROR`); via the CLI as a failed request. A timeout is a load
82
+ limit, not a data problem — the cohort itself is fine, and retrying the same
83
+ call unchanged will usually time out again.
84
+
85
+ **Rule**: split the request instead of retrying as-is — fewer metrics per
86
+ call, one `group_by` dimension at a time, or a narrower filter (e.g. a
87
+ shorter release window) — then combine the results. Splits are safe: each
88
+ call returns population-true numbers for its slice, so recombined totals stay
89
+ consistent. Note the substitution in your analysis log so reviewers know the
90
+ numbers came from split calls.
91
+
92
+ ## P9. Two tag counting bases exist — never mix them (2026-07)
93
+
94
+ Tag shares can be computed on two different bases, and the numbers diverge
95
+ sharply (observed in a field comparison: the same cohort's genre shares
96
+ disagreed between two reports because each used a different basis):
97
+
98
+ - **Single-assignment** — `group_by primary_market_tag` (field
99
+ `primaryMarketTag`, derived from `tagRanks[0]`). Each game is counted at
100
+ most once, under its top-ranked tag. Rows are mutually exclusive and sum
101
+ to the TAGGED subset of the cohort — games without tags are omitted from
102
+ these rows, so compare the row total against the plain cohort count and
103
+ use "of tagged titles" as the denominator when the two differ.
104
+ - **Multi-assignment** — `group_by tag`, or separate per-tag filtered
105
+ counts compared or summed across tags (fields `tags` / `tagRanks` /
106
+ `facets.marketTags`). Each game is counted under every tag it carries, so
107
+ one game contributes to many rows and shares sum well past 100%. The
108
+ server marks `group_by tag` with `summary.methods.groupMembership:
109
+ "multi"` and a multi-membership caveat. (A single `tags`-filtered count
110
+ by itself still counts each matching title once — the double-counting
111
+ appears when per-tag numbers are set side by side.)
112
+
113
+ Neither basis is wrong — they answer different questions:
114
+
115
+ - "What is the market composed of?" / "which segment is a game in?" /
116
+ portfolio-style shares -> single-assignment (`primary_market_tag`).
117
+ - "How many games carry tag X?" / tag reach, co-occurrence, feature
118
+ adoption -> multi-assignment (`tag` grouping or `tags`-filtered counts).
119
+
120
+ **Rule**: pick one basis per question, name it next to the denominator
121
+ ("single-assignment by primary_market_tag, N=1,204"), and never compare or
122
+ combine numbers across bases — not within a report, and not against an
123
+ earlier report computed on the other basis. If a prior number's basis is
124
+ unknown, recompute it rather than citing it.
@@ -1,14 +1,19 @@
1
1
  # Quota etiquette
2
2
 
3
- Quotas are per workspace, per UTC day, enforced for bearer credentials
4
- (API keys / CLI credentials). As of 2026-06: starter 1,000/day, pro
5
- 10,000/day; anonymous public preview is far smaller. Treat these numbers as
6
- indicative — the response metadata is the source of truth.
3
+ Quotas are monthly, workspace-level credit allowances enforced for bearer
4
+ credentials (API keys / CLI credentials / MCP tokens). As of 2026-07:
5
+ Starter 2,500 credits/month, Pro 7,000; the counter resets at 00:00 UTC on
6
+ the first of each month. Anonymous public preview is far smaller. Credits
7
+ are charged when a request is accepted at the gate, so requests that later
8
+ fail validation or 404 still consume — validate appids before batch
9
+ fan-outs. Treat these numbers as indicative — the response metadata is the
10
+ source of truth.
7
11
 
8
12
  ## Rules
9
13
 
10
14
  1. **Read before you spend.** Every bearer response carries
11
- `meta.entitlements.quota` = `{ used, limit }`. Check it at the start of a
15
+ `meta.entitlements.quota` = `{ used, limit, remaining, exceeded,
16
+ resetsAt }` in credits. Check it at the start of a
12
17
  session and after each fan-out. Exception: CLI-composed evidence packs
13
18
  (`cohort ...`, `reviews patterns`, `games benchmark`, ...) expose quota
14
19
  per upstream call instead — read the last entry of
@@ -21,8 +26,10 @@ indicative — the response metadata is the source of truth.
21
26
  `--coverage full,partial` and tighter filters so requests are not spent on
22
27
  titles that would be skipped anyway.
23
28
  4. **On HTTP 429 (`QUOTA_EXCEEDED`)**: stop fanning out, respect the
24
- `Retry-After` header / `error.retryAfterSeconds` (the window resets at UTC
25
- midnight), and tell the user how much of the analysis completed.
29
+ `Retry-After` header / `error.retryAfterSeconds` (the allowance resets at
30
+ 00:00 UTC on the first of the next month; a short `Retry-After` means the
31
+ separate burst backstop), and tell the user how much of the analysis
32
+ completed.
26
33
  5. **Prefer server aggregates** (`count`, `aggregate`) over row pagination —
27
34
  they answer sizing questions in one request.
28
35
  6. **Keep totals explicit.** Search commands prioritize fast row pages and