The Retrospective Agent automates creation of Retrospective documents in Google Docs from Jira data, for Agile Product Managers and Development Leads.
- Fetches Jira issues for an epic
- Builds a timeline for executives
- Computes team insights (cycle time and throughput)
- Generates retrospective talking points:
- ✅ What went well (<= 5 day cycle time)
⚠️ What didn't go as planned (>5 day cycle time)- 🔁 What to try differently next time
- Writes everything into a formatted Google Doc in a shared Drive folder
- Stores generated document links in Google Cloud Storage
- Optional: Sends Slack notifications when docs are created
- Node.js (ESM) + TypeScript
- Express (with web UI)
- Jira REST API
- Google Docs + Drive API (via service account)
- Google OAuth2 (browser login via googleapis)
- Google Cloud Secret Manager
- Google Cloud Storage
- Google Cloud Run
- Slack Webhooks (optional)
- Chart.js (analytics)
The app uses Google OAuth2 for browser access. When a user visits the app, they are redirected to a Google login prompt. After signing in, their email is checked against an allowlist stored in Secret Manager (retrospective-allowed-emails). Only listed emails are granted access.
Sessions are maintained via a signed cookie (7-day expiry).
Note for developers: The session cookie uses
secure: falseintentionally. Cloud Run terminates TLS at the load balancer, so the app sees plain HTTP internally —secure: truewould prevent the cookie from being set. The connection is still encrypted end-to-end from the user's perspective.
The /health endpoint is exempt from authentication so Cloud Run can probe it freely.
Use scripts/allowlist.sh:
./scripts/allowlist.sh list # who has access today
./scripts/allowlist.sh add --dry-run jan@codev.com # preview the change
./scripts/allowlist.sh add jan@codev.com miguel@codev.com
./scripts/allowlist.sh remove someone@codev.com
./scripts/allowlist.sh redeploy # apply the change immediatelyThe script reads the current list, writes it back with your change, and verifies the
result. Run ./scripts/allowlist.sh --help for all options.
Why a script rather than a one-line gcloud command: the allowlist lives in the
retrospective-allowed-emails secret as one comma-separated string, and a new secret
version replaces the whole value — it does not append. Writing just the new address
would silently revoke everyone else's access. The script always reads the current value
first, and uses printf rather than echo, since a trailing newline would end up inside
the last email address.
Changes take effect on the next container startup. redeploy forces it immediately by
redeploying the image the service is already running — a restart, not a code change.
Images are tagged by commit SHA (see cloudbuild.yaml); there is no :latest tag, which
is why the script looks the image reference up rather than hardcoding it.
If you would rather run the steps by hand, scripts/allowlist.sh is short and the
gcloud invocations in it are the canonical ones.
The script needs these roles. Project Owner or Editor covers all of them — which is why it works for whoever set the project up, and why it is easy to hand a teammate a subset that only half works.
| To run | You need | Grant on |
|---|---|---|
list (and the read half of add/remove) |
roles/secretmanager.secretAccessor |
the secret |
add, remove (writing a new version) |
roles/secretmanager.secretVersionAdder |
the secret |
versions |
roles/secretmanager.viewer |
the secret |
redeploy |
roles/run.developer |
the project |
redeploy |
roles/iam.serviceAccountUser |
the runtime service account |
secretAccessor grants only secretmanager.versions.access — it does not allow
writing a new version or even listing versions. Someone with just that role can run
list and nothing else, so grant secretVersionAdder alongside it for anyone who
manages access.
redeploy needs iam.serviceAccountUser because deploying a Cloud Run service that runs
as a service account requires permission to act as that account. The service runs as
959872421018-compute@developer.gserviceaccount.com.
To grant someone the ability to manage the allowlist (scoped to the one secret, not the whole project):
PERSON="user:person@curiouslearning.org"
for ROLE in roles/secretmanager.secretAccessor \
roles/secretmanager.secretVersionAdder \
roles/secretmanager.viewer; do
gcloud secrets add-iam-policy-binding retrospective-allowed-emails \
--project=gdl-reader-dev --member="$PERSON" --role="$ROLE"
doneAnd to let them redeploy as well:
gcloud projects add-iam-policy-binding gdl-reader-dev \
--member="$PERSON" --role=roles/run.developer
gcloud iam service-accounts add-iam-policy-binding \
959872421018-compute@developer.gserviceaccount.com \
--project=gdl-reader-dev --member="$PERSON" --role=roles/iam.serviceAccountUserTo check what you have, just run ./scripts/allowlist.sh list — it names the missing role
when a call is denied.
Download and install Node.js 20+ from https://nodejs.org
Download and install from https://cloud.google.com/sdk/docs/install, then initialize:
gcloud initSign in with your Google account when prompted and select project gdl-reader-dev (option 33 in the project list).
Ask a project admin to grant your Google account access to gdl-reader-dev:
# Admin runs this — replace with the new dev's email
gcloud projects add-iam-policy-binding gdl-reader-dev \
--member="user:devname@example.com" \
--role="roles/secretmanager.secretAccessor"Required once per machine so the app can read secrets from Secret Manager:
gcloud auth application-default logingit clone https://github.com/curiouslearning/retrospective-agent.git
cd retrospective-agent
npm install
npm run devNavigate to http://localhost:8080 — you will be prompted to sign in with Google.
If you want the Google login flow to work locally, add http://localhost:8080/auth/callback as an authorized redirect URI in GCP Console → APIs & Services → Credentials → OAuth 2.0 Client ID. Without this, the app will still work if you hit the API endpoints directly, but visiting the UI will redirect you to Google and fail the callback.
- Select a Jira project to load its ongoing epics
- View progress bars and existing retrospective links
- Generate a retrospective doc with one click
- Click Analytics for cycle time trends and throughput metrics
curl -X POST http://localhost:8080/run \
-H "Content-Type: application/json" \
-d '{"board_name":"assessment js","epic_key":"AJ-581"}'All secrets and infrastructure live in the gdl-reader-dev GCP project.
| Secret Name | Description |
|---|---|
retrospective-service-account |
Service account JSON for Google Docs/Drive |
retrospective-jira-token |
Atlassian API token |
retrospective-jira-base-url |
e.g. https://your-org.atlassian.net |
retrospective-jira-email |
Email of the Jira user the token belongs to |
retrospective-drive-folder-id |
Google Drive folder ID for generated docs |
retrospective-drive-user |
Workspace user the service account impersonates for Docs/Drive |
retrospective-storage-bucket |
GCS bucket name for storing document links |
retrospective-slack-webhook |
Slack webhook URL (optional) |
retrospective-oauth-client-id |
Google OAuth2 client ID for browser login |
retrospective-oauth-client-secret |
Google OAuth2 client secret for browser login |
retrospective-session-secret |
Random string used to sign session cookies |
retrospective-allowed-emails |
Comma-separated list of emails permitted to access the app |
To update a secret:
echo -n 'new-value' | gcloud secrets versions add SECRET_NAME \
--project=gdl-reader-dev --data-file=-devops@gdl-reader-dev.iam.gserviceaccount.com
Generated docs are placed in:
https://drive.google.com/drive/folders/16KKP1VoD1gOfEjQ3I1zM8A351LaDY_gs
The service account must have Editor access to this folder.
Deployment is automated via Cloud Build. Any push to main triggers a build and deploy to Cloud Run.
To deploy manually:
gcloud run deploy retrospective-agent \
--image=us-east1-docker.pkg.dev/gdl-reader-dev/gdl-reader/retrospective-agent:COMMIT_SHA \
--region=us-east1 \
--platform=managed \
--allow-unauthenticated \
--project=gdl-reader-devReplace COMMIT_SHA with the commit you want to deploy — cloudbuild.yaml tags images by
commit SHA and never publishes a :latest tag. To list what has been built:
gcloud artifacts docker tags list \
us-east1-docker.pkg.dev/gdl-reader-dev/gdl-reader/retrospective-agent \
--project=gdl-reader-dev- Go to https://api.slack.com/apps → Create New App → From scratch
- Go to Incoming Webhooks → Toggle ON → Add New Webhook → select a channel
- Add the webhook URL to the
retrospective-slack-webhooksecret