{"id":50609449,"url":"https://github.com/koborin-ai/site","last_synced_at":"2026-07-05T03:00:22.711Z","repository":{"id":325578644,"uuid":"1096948010","full_name":"koborin-ai/site","owner":"koborin-ai","description":"🧪 Personal site and technical proving ground on GCP","archived":false,"fork":false,"pushed_at":"2026-06-28T07:17:39.000Z","size":56923,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-28T07:18:16.276Z","etag":null,"topics":["agentic","astro","claude-code","cloud-run","cursor","gcp","iac","llms-txt","mdx","pulumi","pulumi-go","starlight"],"latest_commit_sha":null,"homepage":"https://koborin.ai","language":"MDX","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/koborin-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-11-15T08:40:21.000Z","updated_at":"2026-06-28T02:52:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/koborin-ai/site","commit_stats":null,"previous_names":["nozomi-koborinai/koborin-ai","koborin-ai/site"],"tags_count":99,"template":false,"template_full_name":null,"purl":"pkg:github/koborin-ai/site","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koborin-ai%2Fsite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koborin-ai%2Fsite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koborin-ai%2Fsite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koborin-ai%2Fsite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koborin-ai","download_url":"https://codeload.github.com/koborin-ai/site/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koborin-ai%2Fsite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35141966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic","astro","claude-code","cloud-run","cursor","gcp","iac","llms-txt","mdx","pulumi","pulumi-go","starlight"],"created_at":"2026-06-06T02:01:43.353Z","updated_at":"2026-07-05T03:00:22.637Z","avatar_url":"https://github.com/koborin-ai.png","language":"MDX","funding_links":[],"categories":[],"sub_categories":[],"readme":"# koborin.ai\n\n![koborin-ai](./app/src/assets/_shared/koborin-ai-header-light.svg)\n\nTechnical proving ground for exploring AI, cloud architecture, and continuous learning.\n\nAstro ( [![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) ) runs on Cloud Run behind a global HTTPS load balancer, and the entire stack (app + infra) lives in this monorepo with TerraDart (Dart → Terraform).\n\n## Architecture\n\nDev/Prod share the same HTTPS load balancer and Artifact Registry; only Cloud Run scaling/access policies differ.\n\n```mermaid\n---\ntitle: \"Google Cloud Project\"\n---\nflowchart LR\n    subgraph TF_STATE[\"Terraform Backend State - GCS\"]\n        STATE_SHARED[\"shared\"]\n        STATE_DEV[\"dev\"]\n        STATE_PROD[\"prod\"]\n    end\n    \n    subgraph SHARED[\"Shared Resources\"]\n        APIS[\"APIs\"]\n        ARTIFACT[\"Artifact Registry\"]\n        WIF[\"Workload Identity\u003cbr/\u003ePool/Provider\"]\n    end\n    \n    subgraph DNS[\"Cloudflare DNS (manual)\"]\n        DEV_DOMAIN[\"dev.koborin.ai\u003cbr/\u003eA record\"]\n        PROD_DOMAIN[\"koborin.ai\u003cbr/\u003eA record\"]\n    end\n    \n    subgraph LB[\"Unified HTTPS LB - shared\"]\n        STATIC_IP[\"Static IP\u003cbr/\u003ePREMIUM tier\"]\n        SSL_CERT[\"Managed SSL Cert\u003cbr/\u003emulti-domain\"]\n        URL_MAP[\"URL Map\u003cbr/\u003ehost routing\"]\n        HTTPS_PROXY[\"HTTPS Proxy\"]\n    end\n    \n    subgraph DEV_BACKEND[\"Dev Backend\"]\n        DEV_IAP[\"IAP\u003cbr/\u003e+ X-Robots-Tag\"]\n        DEV_NEG[\"Serverless NEG\"]\n        DEV_CR[\"Cloud Run\u003cbr/\u003ekoborin-ai-web-dev (Astro)\u003cbr/\u003e(LB-only ingress)\"]\n    end\n    \n    subgraph PROD_BACKEND[\"Prod Backend\"]\n        PROD_NEG[\"Serverless NEG\"]\n        PROD_CR[\"Cloud Run\u003cbr/\u003ekoborin-ai-web-prod (Astro)\u003cbr/\u003e(LB-only ingress)\"]\n    end\n    \n    subgraph CICD[\"GitHub Actions\"]\n        GH_WIF[\"Workload Identity\u003cbr/\u003eFederation\"]\n        DEPLOYER_SA[\"Deployer SA\"]\n    end\n    \n    STATE_SHARED -.-\u003e SHARED\n    STATE_SHARED -.-\u003e LB\n    STATE_DEV -.-\u003e DEV_BACKEND\n    STATE_PROD -.-\u003e PROD_BACKEND\n    \n    ARTIFACT -.-\u003e|\"Container Image\"| DEV_CR\n    ARTIFACT -.-\u003e|\"Container Image\"| PROD_CR\n    \n    DEV_DOMAIN --\u003e STATIC_IP\n    PROD_DOMAIN --\u003e STATIC_IP\n    STATIC_IP --\u003e SSL_CERT\n    SSL_CERT --\u003e HTTPS_PROXY\n    HTTPS_PROXY --\u003e URL_MAP\n    \n    URL_MAP --\u003e|\"dev host\"| DEV_IAP\n    URL_MAP --\u003e|\"prod host\"| PROD_NEG\n    \n    DEV_IAP --\u003e DEV_NEG\n    DEV_NEG --\u003e DEV_CR\n    PROD_NEG --\u003e PROD_CR\n    \n    GH_WIF -.-\u003e|\"Authenticate\"| WIF\n    WIF -.-\u003e|\"Impersonate\"| DEPLOYER_SA\n    DEPLOYER_SA -.-\u003e|\"plan/apply\"| TF_STATE\n    \n    style TF_STATE fill:#E8E8E8,color:#000,stroke:#666,stroke-width:2px\n    style STATE_SHARED fill:#F5F5F5,color:#000\n    style STATE_DEV fill:#F5F5F5,color:#000\n    style STATE_PROD fill:#F5F5F5,color:#000\n    style APIS fill:#9E9E9E,color:#fff\n    style ARTIFACT fill:#9E9E9E,color:#fff\n    style WIF fill:#9E9E9E,color:#fff\n    style STATIC_IP fill:#00B6AC,color:#fff\n    style SSL_CERT fill:#00B6AC,color:#fff\n    style URL_MAP fill:#00B6AC,color:#fff\n    style HTTPS_PROXY fill:#00B6AC,color:#fff\n    style DEV_IAP fill:#F76560,color:#fff\n    style DEV_CR fill:#4A90E2,color:#fff\n    style PROD_CR fill:#4A90E2,color:#fff\n    style GH_WIF fill:#FFB74D,color:#000\n    style DEPLOYER_SA fill:#FFB74D,color:#000\n```\n\n\u003e DNS is hosted in Cloudflare. Terraform does **not** manage DNS records; add/update `koborin.ai` / `dev.koborin.ai` A records manually whenever the load balancer IP changes.\n\n### Environment matrix\n\n| Layer | Dev (`dev.koborin.ai`) | Prod (`koborin.ai`) |\n| --- | --- | --- |\n| Runtime | Cloud Run (`koborin-ai-web-dev`) | Cloud Run (`koborin-ai-web-prod`) |\n| Access | IAP allow list + `X-Robots-Tag: noindex` | Public (no IAP) |\n| Ingress | `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` | `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` |\n| Scaling | Min: 0, Max: 1 | Min: 0, Max: 10 |\n| Env Vars | `NODE_ENV=development`, `NEXT_PUBLIC_ENV=dev` | `NODE_ENV=production`, `NEXT_PUBLIC_ENV=prod` |\n| Content | Same MDX content (no env-specific filtering) | Same MDX content (no env-specific filtering) |\n| Analytics | GA4 (debug view) + optional server events | GA4 + server events + Cloud Monitoring |\n\n## CI/CD\n\nInfrastructure and application deploys are each handled by dedicated GitHub Actions workflows using Workload Identity Federation.\n\n```mermaid\nflowchart LR\n    subgraph GitHub Actions\n        planInfra[plan-infra.yml]\n        releaseInfra[release-infra.yml]\n        appCI[app-ci.yml]\n        appRelease[app-release.yml]\n        starsDigest[stars-digest.yml]\n    end\n\n    subgraph Cloud Build\n        buildApp[Docker Build\\n+ Artifact Registry]\n    end\n\n    subgraph Terraform\n        sharedStack[Shared Stack]\n        envStacks[Dev/Prod Stacks]\n    end\n\n    starsDigest --\u003e|daily cron| genEdition[Generate edition\\nDart + Genkit + Gemini]\n\n    planInfra --\u003e sharedStack\n    releaseInfra --\u003e sharedStack\n    releaseInfra --\u003e envStacks\n    appCI --\u003e|PR validation| GitHub\n    genEdition --\u003e|commit /stars + workflow_call| appRelease\n    appRelease --\u003e buildApp --\u003e envStacks\n```\n\n| Workflow | Trigger | Purpose | Notes |\n| --- | --- | --- | --- |\n| `plan-infra.yml` | PRs touching infra | `dart analyze` + `terraform plan` for shared/dev/prod stacks | No apply; reviewers inspect plan output |\n| `release-infra.yml` | `infra-v*` tags or manual dispatch | Applies shared/dev/prod stacks via Terraform | Workload Identity SA has infra IAM roles |\n| `app-ci.yml` | PRs touching `app/` or `content/` | Runs Astro lint/typecheck/test/build | Blocks merges that break the app |\n| `app-release.yml` | Merge to `main`, `app-v*` tags, or `workflow_call` | Builds + pushes Docker image (tag = `${GITHUB_SHA}-${GITHUB_RUN_ID}-${TARGET_ENV}`) and applies Terraform to update Cloud Run | Reusable via `workflow_call` (used by `stars-digest.yml`); Cloud Build runs asynchronously |\n| `stars-digest.yml` | Daily cron (03:30 JST) or manual dispatch | Generates the `/stars` OSS newsletter, commits it, then deploys dev → prod | Calls `app-release.yml` via `workflow_call`; publishes a daily GitHub release |\n| `plugin-ci.yml` | PRs touching `plugins/` or `.claude-plugin/` | Validates plugin structure, JSON schemas, marketplace consistency | Blocks merges with invalid plugin packages |\n| `claude.yml` | `@claude` mention in issues/PR comments/reviews | Runs Claude Code Action to respond in-thread | Reads CI results on PRs; gated on the mention string |\n\n## Tech Stack\n\n- **Frontend**: Astro with Starlight (documentation theme), TypeScript.\n- **Content Management**: MDX stored under `app/src/content/docs/` within git. Frontmatter is validated via Zod schemas (from Starlight) to keep metadata type-safe. Drafts can be marked with `draft: true` in frontmatter.\n- **Analytics \u0026 o11y**:\n  - Google Analytics 4 for baseline PV/engagement.\n  - Optional custom `/api/track` endpoint writing to Cloud Logging → BigQuery for privacy-friendly metrics.\n  - Cloud Monitoring dashboards + alert policies for Cloud Run metrics.\n- **Infrastructure**: TerraDart (Dart) targeting Google Cloud via Terraform.\n- **Stars Digest**: Dart CLI built on Genkit (`genkit` + `genkit_vertexai`) that calls Gemini on Vertex AI to generate the daily `/stars` OSS newsletter. See [Stars Digest](#stars-digest).\n- **CI/CD**: GitHub Actions with Workload Identity. `plan-infra.yml` / `release-infra.yml` drive infra, `app-ci.yml` / `app-release.yml` handle the Astro app, and `stars-digest.yml` runs the daily newsletter.\n- **Testing**: Vitest for app tests, `dart analyze` for infra, Playwright for Mermaid rendering in production builds.\n- **LLM Context**: Machine-readable `llms.txt` files for AI assistants. Auto-generated at build time.\n\n## Stars Digest\n\n`tools/stars-digest/` is a Dart package that generates the daily [`/stars`](https://koborin.ai/stars) personalized OSS newsletter. It reads the starred-repos data from a local clone of the private [`koborin-ai/stars`](https://github.com/koborin-ai/stars) repo, picks one main deep-dive plus up to five new arrivals, and writes structured Japanese prose via Gemini (on Vertex AI) through [Genkit](https://genkit.dev). A deterministic stub keeps `--dry-run` fully offline.\n\n```bash\ncd tools/stars-digest\ndart pub get\ndart run build_runner build\n\n# Generate today's edition (Asia/Tokyo); needs GEMINI_API_KEY / GOOGLE_API_KEY\ndart run bin/generate.dart --stars-dir \u003cpath/to/stars\u003e --site-dir \u003crepo-root\u003e\n\n# Offline dry-run (no Gemini call)\ndart run bin/generate.dart --stars-dir \u003cpath/to/stars\u003e --site-dir \u003crepo-root\u003e --dry-run\n```\n\nEach edition is written to `app/src/content/docs/stars/\u003cYYYY-MM-DD\u003e.md` and surfaced on the `/stars` index page and its RSS feed (`/stars/rss.xml`). In CI, `stars-digest.yml` runs the generator on a daily cron, commits the new edition, and deploys it through `app-release.yml`. For interactive prompt iteration, the Genkit Developer UI runs locally via `genkit start -o -- dart run bin/dev.dart` (details in `tools/stars-digest/README.md`).\n\n## LLM Context Files (llms.txt)\n\nThe site provides structured context files for LLMs at `https://koborin.ai/llms.txt`.\n\n| File | Content |\n| --- | --- |\n| `/llms.txt` | Index with links to all variants |\n| `/llms-full.txt` | All English articles (full Markdown) |\n| `/llms-ja-full.txt` | All Japanese articles (full Markdown) |\n| `/llms-tech.txt` | English tech articles only |\n| `/llms-ja-tech.txt` | Japanese tech articles only |\n| `/llms-life.txt` | English life articles only |\n| `/llms-ja-life.txt` | Japanese life articles only |\n\nThese files are **auto-generated** at build time from Content Collections. Articles with `draft: true` are excluded. No runtime overhead.\n\n## Repository Layout\n\n```text\n.\n├── .gcloudignore                  # Excludes files from Cloud Build upload\n├── app/                           # Astro + Starlight application (Static, nginx)\n│   ├── cloudbuild.yaml            # Cloud Build config for Docker build\n│   ├── src/\n│   │   ├── assets/               # Images organized by category\n│   │   │   ├── _shared/          # Common assets (header logo)\n│   │   │   ├── tech/             # Tech article images\n│   │   │   ├── life/             # Life article images\n│   │   │   └── og/               # Open Graph images\n│   │   ├── content/\n│   │   │   └── docs/             # MDX pages: tech/, life/, stars/, ja/ (Starlight)\n│   │   ├── content.config.ts    # Content Collections schema (extends docsSchema)\n│   │   ├── utils/\n│   │   │   └── llms.ts           # Shared logic for llms.txt generation\n│   │   ├── pages/                # Astro endpoints (llms.txt, RSS, /stars/rss.xml)\n│   │   └── styles/\n│   │       └── custom.css        # Custom CSS overrides (logo sizing, etc.)\n│   ├── public/\n│   │   ├── favicon.png           # Browser tab icon\n│   │   └── robots.txt\n│   ├── nginx/\n│   │   └── nginx.conf            # nginx configuration for static serving\n│   ├── Dockerfile                # Multi-stage build (node → nginx:alpine)\n│   └── astro.config.mjs          # Starlight integration config\n├── docs/                          # Architecture notes, contact-flow specs, etc.\n├── infra/                         # TerraDart stacks (shared/dev/prod)\n│   ├── bin/synth.dart            # Synth entry point → tf-out/\u003cstack\u003e/main.tf.json\n│   ├── lib/                      # Stack definitions\n│   │   ├── shared_stack.dart     # Shared resources (LB, APIs, WIF)\n│   │   ├── dev_stack.dart        # Dev Cloud Run\n│   │   └── prod_stack.dart       # Prod Cloud Run\n│   └── pubspec.yaml              # TerraDart dependencies\n├── tools/\n│   └── stars-digest/              # Dart + Genkit CLI for the daily /stars newsletter\n├── .claude-plugin/                # Plugin marketplace manifest\n│   └── marketplace.json           # Registry of published plugins\n├── plugins/                       # Published Claude Code plugins\n│   ├── agent-team-fullstack/      # Agent Team orchestration plugin\n│   └── mermaid-diagram/           # Mermaid diagram workflow plugin\n├── .github/workflows/             # CI pipelines (infra plan/apply, app deploy, stars digest, plugin CI, Claude)\n├── README.md                      # This file\n└── AGENTS.md                      # English operations guide for collaborators\n```\n\n### Brand Assets\n\n| Asset | Location | Usage | Notes |\n| --- | --- | --- | --- |\n| Favicon | `app/public/favicon.png` | Browser tab icon | PNG format, transparent background recommended |\n| Header Logo | `app/src/assets/_shared/koborin-ai-header-light.svg` / `-dark.svg` | Site header (replaces title text) | SVG, separate light/dark variants |\n| Hero Image | `app/public/og/koborin-ai-hero.png` | Landing page hero section | 16:9 aspect ratio recommended |\n\nLogo sizing is customized via `app/src/styles/custom.css` (`.site-title img` selector).\n\n### Image Optimization (Automatic)\n\nFor performance, images are automatically converted to WebP format. **Authors can use PNG/JPEG normally** - optimization happens during build/deploy.\n\n| Image Type | Location | What You Do | What Happens Automatically |\n| --- | --- | --- | --- |\n| OG images | `app/public/og/` | Place PNG/JPEG, reference as `.png` | CI converts to WebP, nginx serves WebP |\n| Blog images | `app/src/assets/{category}/{article}/` | Place PNG/JPEG in article folder | Astro optimizes to WebP |\n\n**Example workflow for OG images**:\n\n1. Place image: `app/public/og/my-article.png`\n2. Frontmatter: `ogImage: /og/my-article.png`\n3. Article display: `![](/og/my-article.png)`\n\nThat's it! The CI pipeline (`app/scripts/optimize-og-images.sh`) generates WebP versions, and nginx automatically serves them.\n\n## Workflow Overview\n\n1. **Infra changes**: edit TerraDart stacks → `dart analyze` → open PR → GitHub Actions runs `terraform plan` → reviewer approves → merge triggers apply on the right environment.\n2. **App changes**: edit Astro/MDX → `npm run lint \u0026\u0026 npm run test \u0026\u0026 npm run typecheck \u0026\u0026 npm run check-images \u0026\u0026 npm run build` → PR triggers `app-ci.yml` → merge to `main` (or tag `app-v*`) triggers `app-release.yml` which builds the container, pushes to Artifact Registry, and applies the new image via Terraform.\n3. **Content-only updates**: modify MDX under `app/src/content/docs/`, update frontmatter (`title`, `description`), run `npm run lint`, open PR. Mark drafts with `draft: true` in frontmatter to exclude from production builds.\n\n### Adding New Content\n\nTo add a new article or page:\n\n1. **Create MDX file** under `app/src/content/docs/` (or subdirectory for categories):\n\n   ```bash\n   # Single page\n   app/src/content/docs/my-article.mdx\n\n   # Categorized page\n   app/src/content/docs/blog/my-post.mdx\n   ```\n\n2. **Add frontmatter** with required fields:\n\n   ```yaml\n   ---\n   title: My Article Title\n   description: Brief description of the article\n   publishedAt: 2025-01-02\n   ---\n   ```\n\n   - `publishedAt`: Set the publish date manually (YYYY-MM-DD format).\n   - Updated date is automatically extracted from Git history at build time.\n\n3. **Update sidebar** in `app/src/sidebar.ts`:\n\n   ```typescript\n   export const sidebar = [\n     // ... existing items\n     {\n       label: \"Blog\",\n       items: [\n         { label: \"My Post\", slug: \"blog/my-post\" },\n       ],\n     },\n   ];\n   ```\n\n4. **Build and test** locally before pushing.\n\n## Release Strategy\n\n- Infra applies use `infra-v*` tags to trigger `release-infra.yml`. Tag the repo after merging infra PRs even if app work is still ongoing; this ensures the latest load balancer/stateful resources are deployed before app images roll out.\n- App deploys use `app-v*` tags to drive `app-release.yml`. Tagging after a successful `main` merge guarantees that the latest container image is built and the Cloud Run service is updated via Terraform.\n- GitHub release notes are generated via `.github/release.yml`. Label each PR with `app`, `infra`, `feature`, `bug`, or `doc` so the notes stay segmented by domain; apply the `ignore` label to omit a PR entirely.\n\n## Local Setup (once the app repo is initialized)\n\n```bash\n# Node.js \u003e= 20, npm 10 recommended\nnpm install\n\n# Run Astro dev server (app directory)\nnpm run dev --prefix app\n\n# Analyze infrastructure (infra directory)\ncd infra \u0026\u0026 dart analyze\n```\n\n## Infrastructure Dev Notes\n\n- TerraDart stacks are located in `infra/lib/`.\n- Synth emits Terraform JSON via `dart run bin/synth.dart \u003cstack\u003e`.\n- Each stack uses a GCS backend at `gs://\u003cBUCKET_NAME\u003e/terraform/\u003cstack\u003e`.\n\n### Shared Stack\n\n- **API enablement**: Run, Compute, IAM, Artifact Registry, IAP, Monitoring, Logging, Certificate Manager.\n- **Artifact Registry**: Container images repository (`koborin-ai-web`).\n- **Global static IP**: PREMIUM tier for HTTPS load balancer.\n- **Managed SSL Certificate**: Multi-domain (`koborin.ai`, `dev.koborin.ai`).\n- **HTTPS Load Balancer**:\n  - Serverless NEGs (dev/prod) referencing Cloud Run services by name.\n  - Backend Services:\n    - Dev: IAP enabled + `X-Robots-Tag: noindex, nofollow` header.\n    - Prod: No IAP, logging enabled.\n  - URL Map (host-based routing).\n  - Target HTTPS Proxy.\n  - Global Forwarding Rule.\n- **Workload Identity Federation**:\n  - Pool: `github-actions-pool`.\n  - Provider: `actions-firebase-provider` (OIDC issuer: `https://token.actions.githubusercontent.com`).\n  - Service Account: `github-actions-service@{project}.iam.gserviceaccount.com`.\n  - IAM binding: Subject-based binding for repository `koborin-ai/site`.\n  - Project IAM roles (Artifact Registry, Run, Compute, IAM, etc.) granted to the deployer service account.\n- **DNS**: Records live in Cloudflare and are managed manually (A records point to the LB IP).\n\n### Dev Stack\n\n- **Cloud Run Service**: `koborin-ai-web-dev`.\n  - Ingress: `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` (LB-only access).\n  - Execution Environment: Gen2.\n  - Environment Variables:\n    - `NODE_ENV=development` (runtime mode).\n    - `NEXT_PUBLIC_ENV=dev` (client-side environment identifier).\n  - Scaling: Min 0, Max 1.\n\n### Prod Stack\n\n- **Cloud Run Service**: `koborin-ai-web-prod`.\n  - Ingress: `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` (LB-only access).\n  - Execution Environment: Gen2.\n  - Environment Variables:\n    - `NODE_ENV=production` (runtime mode).\n    - `NEXT_PUBLIC_ENV=prod` (client-side environment identifier).\n  - Scaling: Min 0, Max 10.\n\n## Contact \u0026 Analytics Design (planned)\n\n- Contact form will post to `/api/contact` (Astro API Route) with:\n  - Payload validation (Zod), reCAPTCHA enforcement, structured logging to Cloud Logging.\n  - Notification via SendGrid or Gmail API (configured via Secret Manager).\n- `/api/track` endpoint will receive custom events and forward to Cloud Logging/BigQuery.\n- GA4 integration via gtag.js (prod only, injected at build time via `PUBLIC_GA_MEASUREMENT_ID`).\n\n## Environment Variables\n\n| Variable | Purpose | Where to Set |\n| --- | --- | --- |\n| `GA_MEASUREMENT_ID` | GA4 Measurement ID (e.g., `G-XXXXXXXXXX`) | GitHub Secrets |\n| `GISCUS_REPO_ID` | Giscus repository ID for the comments widget | GitHub Secrets |\n| `GISCUS_CATEGORY_ID` | Giscus discussion category ID | GitHub Secrets |\n\n`app-release.yml` writes these into `app/.env` at build time: `PUBLIC_GA_MEASUREMENT_ID` for **prod** builds only, and `PUBLIC_GISCUS_REPO_ID` / `PUBLIC_GISCUS_CATEGORY_ID` for both dev and prod. Separately, `stars-digest.yml` authenticates to Vertex AI via Workload Identity (`STARS_DIGEST_SERVICE_ACCOUNT`) to call Gemini — no API key is stored.\n\n## Documentation\n\n- `README.md`: quickstart + architectural highlights (this file).\n- `AGENTS.md`: contribution workflow, review checklist, release rules, IaC philosophy.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoborin-ai%2Fsite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoborin-ai%2Fsite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoborin-ai%2Fsite/lists"}