feat(ceo): pull GSC on-demand into the digest, keyed on agent.json .seo.domain #299

Merged
joshtronic merged 1 commit from feat/ceo-gsc-ondemand into master 2026-06-30 16:28:20 +00:00
Collaborator

PR 2 of the agent-ification work. Makes the CEO read its own scoreboard — directly answers the CEO's board question #37 ("zero GSC metrics, I can't assess delta").

What

The CEO digest read live numbers only from agent.json .ceo.metrics_url. A GSC-scoreboard repo like vps-showdown (mandate names impressions/clicks/CTR/position as the scoreboard) has no metrics_url, so the digest came up empty and the CEO filed #37 asking for the data.

Now the CEO reads Google Search Console itself, keyed on the .seo.domain field PR #298 established — and it does not wait for the monthly SEO pass. It's the boss; it reads the scoreboard whenever it runs.

ceo_read_gsc(repo):

  • reads agent.json .seo.domain (no domain → clean no-op, non-SEO repos unchanged);
  • fetches the current 28-day window AND the prior 28 days in one call (reuses seo_window + the existing GSC client), so the trend is self-contained — no persisted state;
  • aggregates clicks / impressions / CTR / impression-weighted avg position;
  • leads the digest inputs with a numbers-first table + the delta.

Unconfigured or failed GSC fetch → a "scoreboard dark, do NOT guess" note, never a crash. Wired into do_ceo_tick ahead of ceo_read_metrics.

Tests

New ceo_read_gsc section in test-ceo.sh: no-domain no-op, unconfigured note, aggregated totals + weighted position, token-fail note. make test green (check-sync + all units).

Activation

This goes live for a repo once it carries agent.json .seo.domain — which is PR 3 (vps-showdown first). After that merges, the next vps-showdown CEO digest leads with real GSC numbers, and you can answer #37.

**PR 2 of the agent-ification work.** Makes the CEO read its own scoreboard — directly answers the CEO's board question #37 ("zero GSC metrics, I can't assess delta"). ## What The CEO digest read live numbers only from `agent.json .ceo.metrics_url`. A GSC-scoreboard repo like **vps-showdown** (mandate names impressions/clicks/CTR/position as the scoreboard) has no metrics_url, so the digest came up empty and the CEO filed #37 asking for the data. Now the CEO reads **Google Search Console itself**, keyed on the `.seo.domain` field PR #298 established — and it does **not** wait for the monthly SEO pass. It's the boss; it reads the scoreboard whenever it runs. `ceo_read_gsc(repo)`: - reads `agent.json .seo.domain` (no domain → clean no-op, non-SEO repos unchanged); - fetches the **current 28-day window AND the prior 28 days in one call** (reuses `seo_window` + the existing GSC client), so the trend is self-contained — no persisted state; - aggregates clicks / impressions / CTR / **impression-weighted** avg position; - leads the digest inputs with a numbers-first table + the delta. Unconfigured or failed GSC fetch → a *"scoreboard dark, do NOT guess"* note, never a crash. Wired into `do_ceo_tick` ahead of `ceo_read_metrics`. ## Tests New `ceo_read_gsc` section in `test-ceo.sh`: no-domain no-op, unconfigured note, aggregated totals + weighted position, token-fail note. `make test` green (check-sync + all units). ## Activation This goes live for a repo once it carries `agent.json .seo.domain` — which is **PR 3** (vps-showdown first). After that merges, the next vps-showdown CEO digest leads with real GSC numbers, and you can answer #37.
feat(ceo): pull GSC on-demand into the digest, keyed on agent.json .seo.domain
All checks were successful
Lint / check-sync (push) Successful in 4s
Lint / check-sync (pull_request) Successful in 4s
8f8bc99bca
The CEO digest read live metrics only from agent.json `.ceo.metrics_url`. For a
GSC-scoreboard repo (vps-showdown's mandate names impressions/clicks/CTR/position
as the scoreboard) that left the digest with zero numbers -- it has no
metrics_url, so the CEO was "assessing direction, not delta" and raised a board
question about it (#37).

Now the CEO reads Google Search Console itself, keyed on the `.seo.domain` field
PR #298 established -- and it does NOT wait for the monthly SEO pass; the boss
reads the scoreboard whenever it runs. ceo_read_gsc fetches the current 28-day
window AND the prior 28 days in one call (reusing seo_window + the GSC client),
aggregates clicks / impressions / CTR / impression-weighted avg position, and
leads the digest inputs with a numbers-first table + the trend. No persisted
state -- both windows come from the same call. A missing domain is a clean
no-op; an unconfigured or failed GSC fetch is a "scoreboard dark, do NOT guess"
note, never a crash.

Wired into the do_ceo_tick activity assembly ahead of ceo_read_metrics. Adds a
ceo_read_gsc section to test-ceo.sh (no-domain no-op, unconfigured note,
aggregated totals + weighted position, token-fail note). make test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KXPafSYJPY6XkWHHk1Lfs
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for 8f8bc99b: success

Adds ceo_read_gsc (+ helper _ceo_gsc_totals) to pull Google Search Console on-demand into the CEO digest, keyed on agent.json .seo.domain, and wires it ahead of ceo_read_metrics in do_ceo_tick. Focused (~90 lines incl. tests), CI green, no CI-config or out-of-scope changes. Tests trace cleanly to the code. One behavioral gap and one thing I can't verify from the diff keep me off APPROVE.

Findings

  • lib/ceo.sh (ceo_read_gsc, query step) — failed fetch falls through to a false-zero table, contradicting the description's contract. The PR claims "Unconfigured or failed GSC fetch → a scoreboard dark, do NOT guess note." That's true for the unconfigured and token-refresh paths, but a query failure is handled by gsc_query ... || printf '{"rows":[]}', which yields empty rows → _ceo_gsc_totals returns all zeros → the digest prints a real-looking clicks 0 / impressions 0 table with a delta. For a feature whose entire purpose is the CEO assessing delta, a transient API/HTTP failure surfacing as "traffic crashed to zero" is exactly the false signal the dark-note framing exists to prevent. Consider: if the current-window gsc_query fails (or returns no .rows), emit the same "scoreboard dark, do NOT guess" note instead of a zeros table. Note this path is also untested (only the token-refresh failure is covered).

  • Runtime availability of seo_window / gsc_query / gsc_access_token — can't confirm from the diff. ceo_read_gsc calls these three functions, which the description says are "existing" (presumably in another lib). The tests mock all three, so green CI does not prove they're actually sourced into the do_ceo_tick runtime context. If lib/ceo.sh/tick.sh doesn't already source whatever defines them, this is a production-only failure the tests can't catch. Worth a quick confirmation they're in scope where do_ceo_tick runs.

Smaller notes (non-blocking)

  • read -r start end pstart pend <<<"$(seo_window)" assumes seo_window always emits exactly four space-separated tokens; an empty/short return would print a malformed ** -> ** window line and pass empty dates to gsc_query. Mirrors the failure mode above.
  • _ceo_gsc_totals math and rounding check out: test rows give clicks 15, impressions 200, CTR 7.5%, impression-weighted position 10, and the jq add // 0 / weighted-average logic handles empty and null fields without erroring. No correctness objection there.

Nothing here is a hard blocker, but the false-zero-on-fetch-failure gap is a reservation I'd want a human to weigh before merge — flagging rather than rubber-stamping.


Independent review by the harness on claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `8f8bc99b`: **success** Adds `ceo_read_gsc` (+ helper `_ceo_gsc_totals`) to pull Google Search Console on-demand into the CEO digest, keyed on `agent.json .seo.domain`, and wires it ahead of `ceo_read_metrics` in `do_ceo_tick`. Focused (~90 lines incl. tests), CI green, no CI-config or out-of-scope changes. Tests trace cleanly to the code. One behavioral gap and one thing I can't verify from the diff keep me off APPROVE. **Findings** - **`lib/ceo.sh` (ceo_read_gsc, query step) — failed fetch falls through to a false-zero table, contradicting the description's contract.** The PR claims "Unconfigured or failed GSC fetch → a *scoreboard dark, do NOT guess* note." That's true for the unconfigured and token-refresh paths, but a *query* failure is handled by `gsc_query ... || printf '{"rows":[]}'`, which yields empty rows → `_ceo_gsc_totals` returns all zeros → the digest prints a real-looking `clicks 0 / impressions 0` table with a delta. For a feature whose entire purpose is the CEO assessing delta, a transient API/HTTP failure surfacing as "traffic crashed to zero" is exactly the false signal the dark-note framing exists to prevent. Consider: if the current-window `gsc_query` fails (or returns no `.rows`), emit the same "scoreboard dark, do NOT guess" note instead of a zeros table. Note this path is also untested (only the token-refresh failure is covered). - **Runtime availability of `seo_window` / `gsc_query` / `gsc_access_token` — can't confirm from the diff.** `ceo_read_gsc` calls these three functions, which the description says are "existing" (presumably in another lib). The tests mock all three, so green CI does *not* prove they're actually sourced into the `do_ceo_tick` runtime context. If `lib/ceo.sh`/`tick.sh` doesn't already source whatever defines them, this is a production-only failure the tests can't catch. Worth a quick confirmation they're in scope where `do_ceo_tick` runs. **Smaller notes (non-blocking)** - `read -r start end pstart pend <<<"$(seo_window)"` assumes `seo_window` always emits exactly four space-separated tokens; an empty/short return would print a malformed `** -> **` window line and pass empty dates to `gsc_query`. Mirrors the failure mode above. - `_ceo_gsc_totals` math and rounding check out: test rows give clicks 15, impressions 200, CTR 7.5%, impression-weighted position 10, and the jq `add // 0` / weighted-average logic handles empty and null fields without erroring. No correctness objection there. Nothing here is a hard blocker, but the false-zero-on-fetch-failure gap is a reservation I'd want a human to weigh before merge — flagging rather than rubber-stamping. --- <sub>Independent review by the harness on `claude-opus-4-8`. The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=8f8bc99bca41ef786683d56406b76bc575aa2321 verdict=COMMENT ci=success -->
igor added spent time 2026-06-30 16:24:21 +00:00
1 minute 21 seconds
joshtronic approved these changes 2026-06-30 16:28:15 +00:00
joshtronic deleted branch feat/ceo-gsc-ondemand 2026-06-30 16:28:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
2 participants
Notifications
Total time spent: 1 minute 21 seconds
igor
1 minute 21 seconds
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor!299
No description provided.