| name | sheet-sync |
| description | Use when driving development from a Google Sheet of bugs — pull open rows into the forge task board, work them, and push fix status/version/notes back to the sheet without clobbering the owner's cells. |
sheet-sync
Two-way sync between a client-owned Google Sheet of bugs and the forge task
board. The sheet owns bug content and triage (Title, Description, Severity,
Priority, MVP flag, early/terminal Status); the board owns dev progress
(middle Status values, our notes). Never write outside that split.
One-time setup
- Copy
crates/sheet-sync/examples/spoton-qa.toml (or author your own) to
.forge/sheets/<alias>.toml — this path is git-ignored, so it's local
config, not a commit target.
- Edit
spreadsheet_id, tab, and the [columns] map (sheet header string
→ canonical field) to match the client's actual sheet. Keep writable,
dev_writable_status, and terminal_status ordered before
[columns]/[status_map] — TOML nests a bare array under whatever table
most recently opened, so putting it after breaks the file silently.
- Pick an auth path — service account (below) for any agent/headless
run, or interactive OAuth if a human is driving from a laptop:
Headless setup (service account — recommended for agents)
This is the easy path and the only one that works unattended: no browser,
no interactive consent, and frg sheet auth becomes a no-op once it's
configured. Use this for any autonomous/CI/agent run — the interactive
OAuth flow below opens a browser and blocks forever in a headless
environment.
- Run the provisioning helper (requires the
gcloud CLI, already logged
in via gcloud auth login):
crates/sheet-sync/examples/provision-service-account.sh
Or point it at a project you already own: PROJECT=my-project crates/sheet-sync/examples/provision-service-account.sh. Either way,
note the service-account email it prints at the end (looks like
sheet-sync@<project>.iam.gserviceaccount.com).
- Share the client's Google Sheet with that SA email as Editor. This
is the step that actually grants access — a fresh service account can
read/write nothing until you do this, regardless of how the key was
minted.
- Point forge at the downloaded key, either:
export FORGE_GOOGLE_SERVICE_ACCOUNT=/path/to/sa-key.json
or in .forge/config.toml:
[google]
service_account_path = "/path/to/sa-key.json"
- Go straight to
frg sheet pull <alias> / frg sheet push — do not
run frg sheet auth; with a service account configured it's a no-op
(there's nothing to consent to).
- Confirm the CQL task board host is also set —
FORGE_CQL_HOST env or
cql_host in .forge/config.toml — since pull/push upsert into
that board, not just the sheet.
Interactive setup (OAuth — for a human on a laptop)
Only needed if you're not using a service account. Blocks on a browser
consent screen, so it's unsuitable for headless/agent runs.
- Set
FORGE_GOOGLE_OAUTH_CLIENT to the path of a Google Desktop OAuth
client_secret.json.
- Run
frg sheet auth <alias> once. This opens a browser consent flow and
caches a refresh token — one-time per alias/machine.
Pull
frg sheet pull <alias> --dry-run # preview first
frg sheet pull <alias>
- Upserts every open (non-
terminal_status) row into the forge task
board, keyed by the sheet's row id.
- Idempotent: re-running never duplicates rows and never moves a task
backward — if the board task is already in a dev-owned state, pull
refreshes content/priority but leaves status alone.
- Duplicate ids in the sheet are refused, not merged. If two rows share
an id (e.g. two
QA-005), pull reports both and skips them for import
and future push. That needs a human to clean up the sheet — do not try
to resolve it programmatically.
Work a bug
Pick an open task (frg task-board / mcp__forge__task_*), fix it TDD-style
as usual.
Push status back
frg sheet push <alias> <row_id> --status "<sheet status value>" \
--fix-ver "<value>" --notes "<what was done>" --dry-run
Always --dry-run first — it prints the exact A1:cell → old ⇒ new diff and
writes nothing. Drop --dry-run once the diff looks right.
- Status handoff: you may only advance the sheet's Status to a
dev-owned value —
In Progress, In Review, Fixed - Needs Verification. The sync refuses to overwrite client-owned/terminal states
(New, Triaged, Verified/Closed, Won't Fix, Duplicate); if the new
value isn't dev-writable, or the sheet is already in a client-owned/
terminal state, status is silently skipped but fix_ver/notes still
write.
fix_ver value: on a demo fix, set it to the git build hash of the
PR that carries the fix. When that fix is promoted to prod, run push
again to update fix_ver to the prod version tag.
fix_ver and resolution_notes are always written regardless of status
handoff.
Safety
- Push only ever writes the three configured
writable columns (status,
fix_ver, resolution_notes), each located by header name at write time
— everything else in the client's sheet is read-only from this tool.
- It never deletes rows.
- Missing header → hard error, no partial write.