Metadata-Version: 2.4
Name: build-q
Version: 0.1.47
Summary: Simple Docker Buildx CLI for local builds
Author: ngen contributors
License: MIT
Project-URL: Homepage, https://github.com/mamatnurahmat/ngen-tools
Keywords: cli,docker,buildx,devops
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31
Requires-Dist: rich>=13.0
Provides-Extra: anomaly
Requires-Dist: pyyaml>=6.0; extra == "anomaly"
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == "mcp"
Provides-Extra: all
Requires-Dist: pyyaml>=6.0; extra == "all"
Requires-Dist: mcp>=1.0; extra == "all"

# build-q (bq) 🚀

**build-q** (dibaca *bq*) adalah CLI Python zero-dependency untuk operasi `docker buildx` lokal & remote yang selaras dengan pipeline CI/CD (Jenkins X). Satu perintah pendek `bq` menggantikan `docker buildx build` yang panjang, dengan auto-detect Git, `cicd/cicd.json`, secret `netrc`, resource limit, dan idempotency check terhadap registry.

Selain build, `bq` menyediakan:
- **Scaffolding CI/CD** (`--init-jx`, `--init-legacy`)
- **CI/CD Trigger** (`--gh-action-init`, `--init-secrets`, `--cicd-trigger`, `--cicd-webhook`)
- **Build Ops** (`--fix-dockerfile`, `--init`, `--compose`)
- **GitOps** (`--bootstrap-k8s`, `--gitops-set-image`, `--set-image`, `--is-match-image`)
- **Verifikasi** (`--check`, `--repo-check`, `--doctor`)
- **Fix** (`--pr-fix` repo legacy template)
- **Interactive** (`--tui` Jev Agent Planner)
- **MCP Server** (`--mcp` — 18 tools untuk AI assistant via Model Context Protocol)
- **Credentials** PocketBase IDP (`--pb-login`, `--pb-status`, dll)

Sejak v0.1.17 operasi GitHub berjalan **native** (tanpa `gh` CLI). Sejak v0.1.30 mendukung **PocketBase fallback** untuk `cicd.json`.

---

## 📚 Panduan Cepat untuk Pemula

Bagian ini untuk yang baru pertama kali pakai `bq`. Kalau sudah familiar, lanjut ke [Instalasi](#-instalasi).

### Apa saja yang perlu di-install?

Sebelum pakai `bq`, siapkan tools berikut di laptop:

| Tools | Wajib? | Kegunaan | Cara install (macOS via Homebrew) |
| ------- | -------- | ---------- | ----------------------------------- |
| **Python 3.7+** | ✅ Wajib | Runtime `bq` | `brew install python` (biasanya sudah ada) |
| **pipx** | ✅ Rekomendasi | Install CLI Python secara terisolasi | `brew install pipx && pipx ensurepath` |
| **Docker Desktop** / **Colima** | ✅ Wajib | Docker engine + Buildx plugin | `brew install --cask docker` atau `brew install colima docker docker-buildx` |
| **Git** | ✅ Wajib | Auto-detect repo/branch | `brew install git` (biasanya sudah ada) |
| **GitHub CLI (`gh`)** | ⚠️ Wajib untuk `--clone`, `--remote`, `--gh-auth`, `--init-secrets`, `--gh-action-init` | Autentikasi GitHub, fetch token, clone repo | `brew install gh && gh auth login` |
| **kubectl** | ⚙️ Opsional | Fetch webhook token otomatis dari k8s (`--init-secrets`) | `brew install kubectl` |

Cek versi setelah install:

```bash
python3 --version        # >= 3.7
pipx --version
docker --version
docker buildx version
git --version
gh --version && gh auth status
kubectl version --client  # opsional
```

### Install `bq` (pemula, langkah demi langkah)

```bash
# 1. Install pipx (kalau belum ada)
brew install pipx
pipx ensurepath              # tambahkan pipx ke PATH — restart terminal setelah ini

# 2. Install build-q
pipx install build-q

# 3. Verifikasi
bq --version                 # harus menampilkan versi terpasang
build-q --version            # alias — sama saja

# 4. Bootstrap awal (buat config + Docker buildx builder)
bq --init
```

Setelah `bq --init` selesai, file config akan ada di `~/.build-q/.env` dan Docker Buildx builder siap dipakai.

### Upgrade ke versi terbaru

```bash
# Rekomendasi (pipx)
pipx upgrade build-q

# Kalau pakai pip biasa
pip install --upgrade build-q

# Pin versi tertentu (opsional)
pipx install --force build-q==0.1.11

# Cek versi yang sedang terpasang
bq --version
```

Tips: jika `pipx upgrade` bilang "already at latest" tapi kamu yakin ada rilis baru, refresh cache PyPI:

```bash
pipx install --force build-q     # reinstall dari PyPI terbaru
```

### Uninstall

```bash
pipx uninstall build-q            # kalau install via pipx
pip uninstall build-q             # kalau install via pip
rm -rf ~/.build-q                 # hapus config (opsional)
```

### Alur 5 menit pertama (pemula)

```bash
# 1. Masuk ke folder service Go/.NET yang sudah ada Dockerfile
cd my-service

# 2. Preview command yang akan dijalankan (aman — tidak eksekusi)
bq --dry-run

# 3. Build tanpa push ke registry
bq --no-push

# 4. Build + push ke registry (mode default)
bq
```

Kalau muncul error `builder not found` → jalankan `bq --init` sekali dulu.
Kalau muncul error `authentication required` saat push → login ke Docker Hub / registry: `docker login`.

---

## 🛠 Fitur

### Build & Push

- **Zero-dependency**: hanya Python 3.7+ standard library.
- **Git auto-detection**: nama repo & ref (branch/tag/short-SHA) dari Git.
- **Mode sumber kode**: build lokal, `--clone` (via `gh` CLI), atau `--remote` (Buildx Git context).
- **Registry idempotency**: cek image di registry sebelum build → skip bila sudah ada (bypass `--rebuild`).
- **Smart env mapping (ref → env)** — **INLINE dengan pipeline Tekton** di `~/jenkins-x/pipeline` (lighthouse `triggers.yaml` + `webhook-server.py`). `bq` (`bq --clone` / `bq --compose`) menyimpulkan environment otomatis dari nama ref, sama persis dengan case shell di pipeline. Nilai dipakai sebagai `--build-arg BRANCH=` (buildx) dan `ENV=` (compose):

  | Ref (branch/tag) | ENV | IMAGE_TAG di pipeline |
  |------------------|-----|----------------------|
  | `v1.2.3`, `refs/tags/v*` | `production` | `v1.2.3` (basename ref) |
  | `develop` | `develop` | short SHA |
  | `staging` | `staging` | short SHA |
  | `sandbox` | `sandbox` | short SHA |
  | `main`, `master`, unknown, kosong | **`staging`** (fallback Tekton `*)`) | short SHA |

  Perhatikan: push ke `main`/`master` **TIDAK** memicu production di JX — tag `v*` yang memicu. Override manual: `--build-arg BRANCH=custom` (buildx).
- **Default netrc secret**: `--secret id=netrc,src=$HOME/.netrc` otomatis.
- **Resource limit default**: memory & CPU aman untuk laptop.
- **CI/CD alignment**: baca `PORT/PORT2/PROJECT/IMAGE` dari `cicd/cicd.json`.
- **Dry run**: pratinjau command tanpa eksekusi.
- **GitHub org shorthand**: set `GITHUB_ORG=...` di config → cukup `bq <repo>` bukan `bq <owner>/<repo>`.
- **`--remote` fallback HTTPS**: bila `SSH_AUTH_SOCK` tidak tersetel, `bq` otomatis pindah ke HTTPS + `GIT_AUTH_TOKEN` (via `gh auth token`) untuk Buildx Git context — tidak perlu ssh-agent.
- **`--compose` mode**: jalankan `make build ENV=...` + `make release ENV=...` sebagai alternatif `docker buildx` (cocok untuk repo yang alur build-nya via Makefile + docker compose).

### Setup & Automation

- **`--init`**: buat file config `~/.build-q/.env` **dan** auto-create Docker Buildx builder (bootstrap).
- **Template terpusat di gist**: sejak `v0.1.14`, `--init-jx`, `--init-legacy`, dan `--gh-action-init` fetch template dari [gist mamatnurahmat](https://gist.github.com/mamatnurahmat/35cc4c36e7c7c2d236a1b5149cdbcfd9) (6 file). Cache lokal di `~/.build-q/templates/` (TTL 1 jam). Fallback ke bundled snapshot bila offline. **Update / perbaikan template harus dilakukan di gist** (via `gh gist edit`) — semua user `bq` otomatis dapat versi terbaru pada scaffold berikutnya.
- **`--init-jx`**: scaffold `Makefile` + `compose.yaml` + `Dockerfile` (modern secret mount) + `.github/workflows/trigger-ci.yml` dari `cicd/cicd.json`. Auto-panggil `--init-secrets` bila git remote GitHub terdeteksi.
- **`--init-legacy`**: scaffold `Makefile` + `compose.yaml` untuk pola **legacy** (Dockerfile pakai `ARG GITHUB_USER/GITHUB_TOKEN` — bukan BuildKit secret). Makefile auto-ambil `gh auth token` untuk local dev. **Dockerfile tidak di-overwrite** — cocok untuk repo lama yang belum bisa migrasi ke secret mount.
- **`--gh-action-init`**: bootstrap standar `.github/workflows/trigger-ci.yml` sebagai **satu-satunya** workflow — hapus semua workflow YAML lain di `.github/workflows/`, tulis ulang trigger-ci.yml, lalu set webhook secrets (`WEBHOOK_TRIGGER_URL`, `WEBHOOK_TRIGGER_TOKEN`).
- **`--init-secrets`**: set GitHub Actions secrets (`WEBHOOK_TRIGGER_URL`, `WEBHOOK_TRIGGER_TOKEN`) — auto-fetch token dari k8s secret `webhook-trigger-token` di namespace `jenkins-x`, auto-detect target repo dari `git remote`.
- **`--fix-dockerfile`**: migrasi Dockerfile legacy menjadi modern. Deteksi & auto-fix:
  - `FROM ... as ...` → `AS` (uppercase)
  - Hapus `ARG GITHUB_USER` / `ARG GITHUB_TOKEN`
  - `RUN echo "machine github.com ..." > ~/.netrc && chmod ... && <cmd>` → `RUN --mount=type=secret,id=netrc,target=/root/.netrc \ ...`
  - **Baru**: standalone `RUN echo ... > ~/.netrc` (tanpa `&& chmod && ...`) → dihapus, lalu mount secret otomatis ditambahkan ke RUN line yang berisi `go mod tidy/download` atau `go get`.
  - **Baru**: `MAINTAINER foo` (deprecated) → `LABEL maintainer="foo"`
  - **Baru**: `ENV KEY value` (legacy) → `ENV KEY=value`
  - Backup asli disimpan ke `Dockerfile.bak`.
- **`--gh-auth`**: injeksi `--build-arg GITHUB_USER` + `GITHUB_TOKEN` dari `gh` CLI (workaround untuk Dockerfile legacy yang belum dimigrasi).
- **Auto-recover builder stale**: `ensure_builder` deteksi endpoint rusak (misal socket Colima lama) dan recreate otomatis.

---

## 📦 Instalasi

```bash
pipx install build-q       # rekomendasi (CLI global, virtualenv terisolasi)
# atau
pip install build-q
```

Dua entry point tersedia: `build-q` dan shorthand `bq`.

**Upgrade ke versi terbaru:**

```bash
pipx upgrade build-q
# atau
pip install --upgrade build-q
```

---

## ⚙️ Konfigurasi Awal (sekali saja)

```bash
bq --init         # buat ~/.build-q/.env + create Docker buildx builder
bq --init --force # recreate config; builder tetap aman (cek dulu, buat bila tidak ada)
bq --config       # tampilkan konfigurasi aktif
```

### `~/.build-q/.env`

| Variable | Default | Keterangan |
| ---------- | --------- | ----------- |
| `BUILDER_NAME` | `mybuilder` | Nama Docker Buildx builder |
| `REGISTRY_URL` | `loyaltolpi` | Docker registry (Qoin default: `loyaltolpi` — Docker Hub org) |
| `DEFAULT_MEMORY` | `4g` | Memory limit build |
| `DEFAULT_CPU_PERIOD` | `100000` | CPU period |
| `DEFAULT_CPU_QUOTA` | `200000` | CPU quota |
| `GIT_SSH_PREFIX` | `git@github.com:` | Prefix SSH untuk `--remote` |
| `GITHUB_ORG` | (kosong) | Default org untuk shorthand `bq <repo>` |
| `WEBHOOK_TRIGGER_URL` | `https://cicd-hw.qoin.id/trigger` | Endpoint webhook Jenkins X |
| `JX_KUBE_CONTEXT` | (kosong = current) | kubectl context untuk fetch token |
| `JX_KUBE_NAMESPACE` | `jenkins-x` | Namespace secret |
| `JX_TOKEN_SECRET` | `webhook-trigger-token` | Nama k8s secret berisi token |
| `GH_CLI` | `false` (sejak v0.1.22) | `false` = jalur native (REST + `git`). `true` = fallback ke `gh` CLI (legacy) |
| `GITHUB_USER` | (kosong) | Username GitHub. Dipakai bila `GH_CLI=false`; bila kosong, di-fetch dari `/user` |
| `GITHUB_TOKEN` | (kosong) | Personal Access Token — WAJIB bila `GH_CLI=false`. Scope: `repo`, `read:user`, `actions:write` (untuk `--init-secrets`) |
| `DOCKERHUB_USER` | (kosong) | Username DockerHub (persiapan future `docker login` auto) |
| `DOCKERHUB_ORG` | (kosong) | Prefix image untuk rollout suggestion (mis. `loyaltolpi`). Fallback ke `REGISTRY_URL` bila kosong |
| `DOCKERHUB_TOKEN` | (kosong) | PAT DockerHub (untuk pemakaian manual) |
| `GITOPS_REPO` | `Qoin-Digital-Indonesia/gitops` | Repo GitOps untuk suggestion `gitops-set-image` |
| `GITOPS_BRANCH` | `main` | Branch target di repo GitOps |
| `GITOPS_INFRA` | `cce` | `cce` (Huawei CCE) atau `k8s` (SLS). Override per-run: `--infra` |
| `NS_SUFFIX` | `qoin` | Fallback global: `ns = <env>-<suffix>` bila `--ns` tidak diberikan **dan** `cicd.PROJECT` kosong. Sejak v0.1.22 `cicd.PROJECT` menang. |
| `PB_API_URL` | (kosong) | Endpoint PocketBase IDP (mis. `https://cicd-hw.qoin.id/devops`) |
| `PB_API_USER` | (kosong) | Username PocketBase |
| `PB_API_PASS` | (kosong) | Password PocketBase |
| `PB_API` | `false` | Set `true` untuk fetch secrets / cicd dari PocketBase |

#### Toggle native (tanpa `gh` CLI)

Default `GH_CLI=true` — `bq` tetap memakai `gh` CLI persis seperti sebelumnya, tidak ada breaking change. Untuk mengaktifkan jalur native (stdlib `urllib` + `git`):

```env
GH_CLI=false
GITHUB_USER=your-login
GITHUB_TOKEN=ghp_xxx
```

Fitur yang dialihkan: fetch `cicd.json`, resolve commit SHA, ambil auth token, `--gh-auth`, `--clone`, `--init-secrets`. Untuk `--init-secrets` di mode native, install extra: `pip install pynacl` (dibutuhkan enkripsi libsodium sebelum PUT ke API).

> Bila `~/.build-q/.env` sudah ada dari versi sebelumnya, tambahkan tiga baris di atas secara manual — atau jalankan `bq --init --force` untuk regenerate (perhatikan config lain akan direset ke default).

#### 🚀 Rollout suggestions pasca-build

Sejak `v0.1.18`, setelah build sukses (atau `--dry-run`), `bq` menampilkan dua perintah siap copy-paste yang membungkus tools eksternal [`set-image`](~/.agents/bin/set-image) (imperative — `kubectl set image` + `rollout status`) dan [`gitops-set-image`](~/.agents/bin/gitops-set-image) (declarative — patch YAML di repo GitOps lalu push).

Contoh output:

```
✅ Build completed successfully.

📤 Rollout suggestions (copy-paste ke terminal):

   # 1) Hot-patch cluster (imperative, cepat)
   set-image staging-qoin qoinhub-mono-webadmin 76e23f3

   # 2) Via GitOps (declarative, ArgoCD sync)
   gitops-set-image Qoin-Digital-Indonesia/gitops main \
     cce/staging-qoin/qoinhub-mono-webadmin_deployment.yaml \
     loyaltolpi/qoinhub-mono-webadmin:76e23f3

   ℹ️  ns fallback: staging-qoin — override dengan --ns <name>
   Tip: --ns <name>  |  --infra {cce|k8s}  |  --gitops-path <path>
```

Flag override runtime:

| Flag | Default | Keterangan |
|---|---|---|
| `--ns NAME` | `<env>-<NS_SUFFIX>` | Namespace target. `env` dihitung dari branch (`staging`→`staging`, `main`→`staging` fallback, tag `v*`→`production`) |
| `--infra {cce,k8s}` | `GITOPS_INFRA` di `.env` | Path template jadi `{infra}/{ns}/{app}_deployment.yaml` |
| `--gitops-path PATH` | (auto) | Bypass template — path YAML eksplisit |

Suggestion **hanya print**, tidak mengeksekusi. Cocok untuk review sebelum apply.

#### 🔎 `bq --check` — verifikasi kesiapan repo (sejak v0.1.22)

Cek repo remote tanpa clone: apakah artifact CI/CD, image registry, dan file GitOps sudah sesuai ekspektasi.

```bash
bq --check ngenwal-be-saasidentityserver4client-manager staging --remote
```

Output:

```
🔍 Checking Qoin-Digital-Indonesia/ngenwal-…-manager @ staging

📦 Repository access:
   ✅ ref 'staging' → e82836bcad4c

🧩 CICD config:
   ✅ cicd/cicd.json (212 bytes)
        • IMAGE      = ngenwal-…-manager
        • PROJECT    = ngenwal
        • PORT       = 6770

🧱 Init artifacts (jx-init):
   ✅ Makefile
   ✅ compose.yaml
   ✅ Dockerfile
   ✅ .github/workflows/trigger-ci.yml

📦 Registry:
   ⚠️  loyaltolpi/ngenwal-…:e82836b — NOT BUILT

🎯 GitOps:
   ✅ Qoin-Digital-Indonesia/gitops@main:cce/staging-ngenwal/ngenwal-…_deployment.yaml

📊 Summary: 2/2 wajib lulus, 1 warning
```

Exit code `0` bila semua wajib lulus (repo/ref + cicd config), `1` bila ada yang miss. Warning (missing artifact / image belum ada) tidak menggagalkan.

Bila ditemukan artifact OUTDATED, `--check` mencetak suggestion `bq --pr-fix …` di akhir output.

#### 🩹 `bq --pr-fix <repo> <ref>` — one-shot fix (sejak v0.1.24)

Untuk repo yang init-nya OUTDATED (deteksi via `--check`), satu perintah menyelesaikan seluruh siklus fix:

```bash
bq --pr-fix ngenwal-be-snapconvert-manager master
# Preflight → clone → branch fix/jx-init-<ts> → regenerate 4 file →
# commit → push → set 2 repo secrets → open PR → cleanup
```

**Prasyarat di `~/.build-q/.env`:**
- `GH_CLI=false` + `GITHUB_TOKEN` (scope: `repo`, **`workflow`**, `actions:write`, `read:user`). Tanpa `workflow`, push ke branch yang mengubah `.github/workflows/*` akan ditolak GitHub.
- `WEBHOOK_TRIGGER_URL` + `WEBHOOK_TRIGGER_TOKEN` (auto-fetched dari k8s pada preflight bila kosong; disimpan untuk run selanjutnya)
- Package `pynacl` (untuk enkripsi repo secret): `pip install pynacl`

**Flag override:**

| Flag | Default | Keterangan |
|---|---|---|
| `--pr-branch NAME` | `fix/jx-init-<timestamp>` | Nama branch fix eksplisit |
| `--keep-workdir` | (delete) | Jangan hapus `/tmp/bq-pr-fix-…` setelah selesai |
| `--dry-run` | (execute) | Stop sebelum push/set-secret/open-PR. Workdir dipertahankan untuk inspeksi. |

**Deduplikasi:** bila PR untuk branch fix sudah ada (open), print URL PR eksisting alih-alih buat baru.

Contoh minimal untuk Qoin:

```env
BUILDER_NAME=cloud-loyaltolpi
REGISTRY_URL=loyaltolpi
GITHUB_ORG=Qoin-Digital-Indonesia
WEBHOOK_TRIGGER_URL=https://cicd-hw.qoin.id/trigger
```

---

## 🚀 Panduan Penggunaan

### 1. Build dari direktori lokal

Auto-detect nama repo & branch dari `git`:

```bash
cd my-service
bq                                    # build + push (default)
bq --no-push                          # build tanpa push
bq --dry-run                          # preview command saja
bq my-service staging                 # eksplisit repo/ref
```

### 2. Build dari repo remote tanpa clone

```bash
# Dengan GITHUB_ORG di config → shorthand
bq plus-be-paymentlink-manager staging --remote

# Tanpa GITHUB_ORG → sertakan owner
bq Qoin-Digital-Indonesia/plus-be-paymentlink-manager staging --remote

# URL SSH/HTTPS penuh juga OK
bq git@github.com:owner/repo.git v1.0.0 --remote
```

> **Catatan `--remote`:** bila `SSH_AUTH_SOCK` tidak tersetel (tidak jalanin `ssh-agent`), `bq` otomatis fallback ke HTTPS + `GIT_AUTH_TOKEN` secret (via `gh auth token`) supaya Buildx Git context tetap bisa jalan tanpa perlu bootstrap ssh-agent.

### 3. Build dengan clone via `gh` CLI

```bash
bq --clone plus-be-paymentlink-manager staging
bq --clone plus-be-paymentlink-manager staging --clean   # hapus folder setelah build
```

### 4. Preview command (dry-run)

```bash
bq --dry-run
bq plus-be-paymentlink-manager staging --remote --no-image-check --dry-run
```

### 5. Paksa rebuild (bypass image check)

```bash
bq plus-be-paymentlink-manager staging --remote --rebuild
```

> **Build di host remote (`DOCKER_CONTEXT`)?** Lihat [VERIFY-REMOTE-BUILD.md](./VERIFY-REMOTE-BUILD.md) untuk cara membuktikan bahwa `docker build` benar-benar dieksekusi di VM remote (via `ssh://`), bukan di laptop lokal.

### 6. Bootstrap CI/CD service baru (Jenkins X — modern, secret mount)

```bash
mkdir my-new-service && cd my-new-service
mkdir cicd && cat > cicd/cicd.json <<EOF
{"IMAGE":"my-new-service","PROJECT":"qoinplus","PORT":"8080"}
EOF
bq --init-jx                 # scaffold Makefile/compose/Dockerfile/workflow
git init && git remote add origin git@github.com:Qoin-Digital-Indonesia/my-new-service.git
bq --init-secrets            # set webhook secrets di GitHub (auto-detect repo)
git add . && git commit -m "chore: bootstrap CI/CD"
git push -u origin main      # trigger Jenkins X pipeline via GitHub Actions
```

### 7. Bootstrap CI/CD service **legacy** (Dockerfile pakai ARG GITHUB_USER/TOKEN)

Kalau Dockerfile di repo lama belum bisa dimigrasi ke `--mount=type=secret`, pakai `--init-legacy`. Makefile-nya akan auto-ambil `gh auth token` untuk local dev.

```bash
cd my-old-service              # Dockerfile-nya masih pakai ARG GITHUB_USER/TOKEN
bq --init-legacy               # scaffold Makefile + compose.yaml (Dockerfile TIDAK ditimpa)
gh auth login                  # sekali saja
make build ENV=staging         # token auto-diambil dari `gh auth token`
make release ENV=staging
```

### 8. Bootstrap GitHub Actions trigger sebagai satu-satunya workflow

Berguna kalau repo punya banyak workflow lama yang tidak dipakai lagi:

```bash
cd existing-repo
bq --gh-action-init            # hapus workflow lain + tulis trigger-ci.yml + set webhook secrets
bq --gh-action-init --token xxxx   # skip kubectl fetch, pakai token eksplisit
```

### 9. Migrasi Dockerfile legacy (ARG-based netrc → secret mount)

```bash
cd my-old-service
bq --fix-dockerfile                # migrasi ./Dockerfile (backup ke .bak)
bq --fix-dockerfile path/to/Dockerfile
bq --no-push --rebuild             # test build hasil migrasi
```

Yang di-fix otomatis:

- `FROM x as y` → `FROM x AS y`
- `ARG GITHUB_USER/TOKEN` dihapus
- `RUN echo "machine github.com ..." > ~/.netrc && chmod ... && <cmd>` → `RUN --mount=type=secret,id=netrc,...`
- Standalone `RUN echo ... > ~/.netrc` → dihapus, secret mount otomatis nempel di `RUN go mod tidy/download/get`
- `MAINTAINER foo` → `LABEL maintainer="foo"`
- `ENV KEY value` → `ENV KEY=value`

### 10. Workaround Dockerfile legacy tanpa migrasi (--gh-auth)

```bash
bq plus-be-service staging --remote --rebuild --gh-auth
# → inject GITHUB_USER dari `gh api user` + GITHUB_TOKEN dari `gh auth token`
```

### 11. Setup secrets Jenkins X untuk repo existing

```bash
cd existing-repo
bq --init-secrets                                        # auto-detect dari git
bq --init-secrets Qoin-Digital-Indonesia/foo-service     # eksplisit
bq --init-secrets foo-service --token xxxx               # skip kubectl
```

### 12. Mode `--compose` (build via `make build && make release`)

Untuk repo yang alur build-nya sudah pakai Makefile + docker compose (mis. hasil `--init-jx` / `--init-legacy`). `ENV=` disimpulkan dari ref — lihat tabel **Smart env mapping** di atas:

```bash
# auto-detect: repo & ref dari git di direktori aktif
bq --compose                                # branch `develop` → ENV=develop
bq --compose my-service staging             # ENV=staging
bq --compose my-service v1.2.3              # ENV=production
bq --compose my-service main                # ENV=production
bq --compose --dry-run                      # preview command

# gabung dengan --clone (clone dulu, cd, lalu make build+release)
bq --clone Qoin-Digital-Indonesia/foo staging --compose   # ENV=staging
bq --clone Qoin-Digital-Indonesia/foo v1.2.3 --compose    # ENV=production
```

**Registry idempotency di `--compose`:** tag yang di-probe mengikuti aturan Makefile — `git describe --tags --exact-match || git rev-parse --short HEAD`. Jadi kalau HEAD di tag `v1.2.3`, `bq` cek `registry/image:v1.2.3` (bukan short commit) — sama dengan tag yang di-push `make release`, sehingga early skip berfungsi benar.

### 13. Bootstrap manifest K8s ke repo GitOps (`--bootstrap-k8s`, sejak v0.1.29)

Satu perintah untuk **bootstrap service baru** ke Kubernetes lewat GitOps:
fetch `cicd/cicd.json` dari repo source (atau **PocketBase** sebagai fallback) → deteksi stack (dotnet vs default) →
render 3 file YAML (Secret, Deployment, Service) sesuai standar Qoin CCE →
update `kustomization.yaml` → commit → push → open PR ke repo GitOps (deduplicated).
Opsional apply Secret langsung ke cluster + auto-recreate Deployment saat
selector drift.

```bash
bq --bootstrap-k8s <repo> <ref> \
    --gitops-repo Qoin-Digital-Indonesia/gitops \
    --gitops-branch develop \
    --path-yaml cce/develop-qoin \
    --apply-secret --kube-context hw-dev
```

#### Workflow

```mermaid
flowchart TD
    A[bq --bootstrap-k8s repo ref] --> B{Preflight<br/>GH_CLI=false<br/>GITHUB_TOKEN}
    B -->|OK| C[Derive env dari ref<br/>develop/staging/production<br/>+ namespace dari path-yaml]
    B -->|Missing| Z1[Abort]
    C --> D[Fetch cicd/cicd.json<br/>dari source@ref via REST]
    D --> E{Detect stack}
    E -->|Probe appsettings.Env.json| F1[stack=dotnet]
    E -->|Probe .env.env| F2[stack=default]
    E -->|Probe .env| F2
    E -->|Probe .env.example| F3[stack=default<br/>WARNING: template]
    E -->|None| Z2[Abort: no config file]
    F1 --> G[Build render ctx:<br/>APP, ENV, NAMESPACE, PROJECT,<br/>ROLE, NODEPOOL, IMAGE_FULL, PORT,<br/>IMAGE_PULL_SECRET, CONFIG_B64]
    F2 --> G
    F3 --> G
    G --> H[Clone gitops shallow<br/>gitops@gitops-branch]
    H --> I[Create branch<br/>bootstrap/app-env-ts]
    I --> J[Render 3 YAML<br/>via templates gist + ctx]
    J --> K[Update kustomization.yaml<br/>append 2 entries idempotent]
    K --> L{--apply-secret?}
    L -->|Yes + non-prod| M[kubectl apply Secret<br/>hanya jika belum ada]
    L -->|Yes + production| M2[SKIP: production PR-only]
    L -->|No| N
    M --> N{--force-recreate-deploy?}
    M2 --> N
    N -->|Yes + selector drift| O[kubectl delete deploy<br/>ArgoCD selfHeal re-create]
    N -->|No / no drift| P
    O --> P{Git diff?}
    P -->|Empty| Q1[Skip PR:<br/>Already up-to-date]
    P -->|Has changes| Q2[Commit + Push + Open PR]
    Q2 --> R[Return PR URL]
```

#### File yang di-generate

Semua file masuk ke `{path-yaml}/` di repo GitOps:

| File | Isi | Selector consistency |
|---|---|---|
| `file-config-{app}_secret_sops.yaml` | `Secret` **SOPS-encrypted** (age) — data key `.env` (default) atau `appsettings.{env}.json` (dotnet), base64 dari source config. Nama file match `.gitignore` exception `!**/*_secret_sops.yaml` di repo gitops. | — |
| `{app}_deployment.yaml` | `Deployment` — labels 4-tuple `{app, env, project, role}`, RollingUpdate, `imagePullSecrets: regcred`, `nodeSelector: cce.cloud.com/cce-nodepool={nodepool}`, tz-config volume, mount secret `file-config-{app}` ke `/.env` atau `/app/appsettings.{env}.json` | `matchLabels` = template.labels (4-tuple exact) |
| `{app}_services.yaml` | `Service` — `ClusterIP`, `targetPort: http`, port dari `cicd.PORT` | `selector.app = {app}` (subset — match pod 4-tuple) |
| `kustomization.yaml` | Append 3 entries (`file-config-{app}_secret_sops.yaml` + `_services.yaml` + `_deployment.yaml`) di `resources:` list | — |

**Selector Deployment ↔ Service** dijamin match — placeholder `{{APP}}` tunggal di
template, satu render → tidak mungkin drift by construction.

#### Deteksi CICD Config (Waterfall)

Cek data CICD berurutan (bila gagal lanjut ke langkah berikutnya):
1. `cicd/cicd.json` di source repo via GitHub API.
2. `cicd.json` di root source repo.
3. **PocketBase collection `repo`** (sejak v0.1.30). Repo *tanpa* `cicd.json` bisa di-bootstrap selama terdaftar di PocketBase dengan data CICD.

#### Deteksi stack (fallback berurutan)

Cek berurutan dari repo source `{repo}@{ref}` via GitHub REST:

1. `appsettings.{DotnetEnv}.json` (mis. `appsettings.Development.json`) → **dotnet**
2. `.env.{env}` (mis. `.env.staging`) → **default** (Go/Node/Rust)
3. `.env` → **default**
4. `.env.example` → **default** (⚠️ template placeholder — value dummy)

Override via `--stack dotnet` atau `--stack default`.

#### Env derivation & guardrail production

Env otomatis dari `<ref>`:

| Ref | Env | Dotnet ASPNETCORE_ENVIRONMENT |
|---|---|---|
| `develop` | `develop` | `development` |
| `staging` | `staging` | `staging` |
| `sandbox` | `sandbox` | `sandbox` |
| `main` / `master` / tag `v*` | `production` | `production` |

Override via `--env NAME` (contoh: `--env staging` walau source ref `main`).

**Guardrail `env=production`:**
- `--apply-secret` → **REFUSE** (production wajib GitOps PR-only)
- `--force-recreate-deploy` → **REFUSE** (production wajib manual blue-green/canary)

#### Flag lengkap

| Flag | Default | Keterangan |
|---|---|---|
| `--gitops-repo OWNER/REPO` | (wajib) | Target repo GitOps |
| `--gitops-branch BRANCH` | (wajib) | Base branch untuk PR |
| `--path-yaml PATH` | (wajib) | Folder tujuan di GitOps (mis. `cce/develop-qoin`). Segmen terakhir default jadi namespace. |
| `--namespace NAME` | (dari path-yaml) | Override K8s namespace di manifest (decouple dari path-yaml — untuk cross-env: gitops di `cce/staging-qoin/` tapi apply ke `production-qoin`) |
| `--nodepool NAME` | `{namespace}-{service\|manager}` | Override `cce-nodepool` selector (mis. `production-nodepool-service`) |
| `--env NAME` | dari ref | Override env label (bypass derivation) |
| `--stack {dotnet,default}` | auto-detect | Paksa stack pilihan |
| `--replicas N` | `2` | Replicas Deployment |
| `--image-pull-secret NAME` | `regcred` | Nama imagePullSecret di Deployment |
| `--apply-secret` | off | kubectl apply Secret ke cluster bila belum ada (opt-in). Refuse env=production. |
| `--kube-context NAME` | current | kubectl context untuk `--apply-secret` / `--force-recreate-deploy` |
| `--force-recreate-deploy` | off | Delete Deployment lama bila `selector.matchLabels` drift (K8s selector IMMUTABLE). Butuh `--kube-context`. Refuse env=production. |
| `--dry-run` | (execute) | Skip push/PR — workdir dipertahankan |
| `--keep-workdir` | (delete) | Jangan hapus workdir `/tmp/bq-bootstrap-…` |

#### Idempotency

Semua step aman di-run berulang:
- Secret `--apply-secret` → skip bila sudah ada di cluster
- YAML render → identical content = 0 git diff = 0 commit
- Kustomization update → skip bila 2 entry sudah include
- Force-recreate → skip bila selector match / Deployment belum ada
- Bila tidak ada diff sama sekali → print "already up-to-date" dan exit 0 tanpa PR
- PR dedup: bila branch fix sudah ada PR-nya (same `head.ref`), cetak PR eksisting.

#### Contoh use case

**A. Bootstrap develop-qoin (standar single-target):**

```bash
bq --bootstrap-k8s pay-be-topup-manager develop \
    --gitops-repo gitops --gitops-branch develop \
    --path-yaml cce/develop-qoin \
    --apply-secret --kube-context hw-dev
```

**B. Bootstrap dengan source ref berbeda dari env (mis. sandbox → develop namespace):**

```bash
bq --bootstrap-k8s plus-be-rusttransaction-module sandbox \
    --gitops-repo gitops --gitops-branch develop \
    --path-yaml cce/develop-qoin --env develop \
    --apply-secret --kube-context hw-dev
```

**C. Cross-env special case: gitops di `staging-qoin/` tapi target ns `production-qoin`:**

```bash
bq --bootstrap-k8s plus-be-rustcreateordersnap-manager sandbox \
    --gitops-repo gitops --gitops-branch staging \
    --path-yaml cce/staging-qoin \
    --namespace production-qoin \
    --nodepool production-nodepool-service \
    --env staging \
    --apply-secret --kube-context hw-pro-q
```

**D. Recreate saat migrasi label schema (selector drift):**

```bash
bq --bootstrap-k8s foo develop \
    --gitops-repo gitops --gitops-branch develop \
    --path-yaml cce/develop-qoin \
    --kube-context hw-dev \
    --force-recreate-deploy
# → detect selector 1-tuple → delete deploy → ArgoCD selfHeal apply spec baru
```

#### Failure modes (dari incident nyata)

| Gejala | Penyebab | Fix |
|---|---|---|
| Pod `ImagePullBackOff` — `unexpected media type text/html` | Anonymous Docker Hub pull kena rate limit karena tidak ada `imagePullSecrets` | Sejak v0.1.29 template sudah include `regcred`; override via `--image-pull-secret NAME` |
| ArgoCD `sync=OutOfSync` — `spec.selector: field is immutable` | Deployment awal punya selector 1-tuple, template baru 4-tuple | Pakai `--force-recreate-deploy` (non-prod). Prod: manual blue-green |
| Pod crash saat startup — `Environment variable X is required` | Source config `.env.example` = template placeholder | Patch Secret manual dgn value real: `kubectl edit secret file-config-<app>-<env>`, lalu `kubectl rollout restart deploy/<app>` |
| PR konflik `kustomization.yaml` | PR lain merged duluan menambah baris di posisi sama | Close PR + re-run `bq --bootstrap-k8s` (idempotent — akan render dari staging HEAD terbaru) |
| `cicd config tidak ada di repo maupun PocketBase` | Repo tidak ada cicd.json dan belum terdaftar di PocketBase | Tambah data di PocketBase via UI admin atau API PATCH. |

### 14. Fitur-fitur Tambahan

**A. PocketBase IDP (`--pb-*`, v0.1.27+)**
- `--pb-login`: Authentikasi ke PocketBase API, fetch credentials, cache lokal (15m). Zero-local-config workflow.
- `--pb-status`, `--pb-pull`, `--pb-logout` untuk manajemen session.

**B. Manual Webhook Trigger & Verify**
- `--cicd-trigger <repo> <ref>`: POST synthetic payload dengan HMAC signature (fetch webhook token via `kubectl`). Bypass manual GitHub UI.
- `--cicd-webhook <repo>`: Verifikasi webhook `cicd-hw.qoin.id/hook` terpasang di repo GitHub (REST API).

**C. Kesiapan Repo & Tools**
- `--check`: Cek 7 aspek repo sebelum CI dijalankan (cicd.json, github webhook, github secrets, artifacts).
- `--repo-check <repo> <ref>`: Cek config CICD khusus dari PocketBase IDP (verifikasi data sebelum `--bootstrap-k8s`).
- `--doctor`: Diagnostics local pre-flight (cek binaries `docker buildx`, `gh`, `git`, token validity, dan kubectl context).

**D. GitOps & Live Patching**
- `--gitops-set-image <gitops> <branch> <yaml> <image>`: Declarative update image tag di file deployment YAML dan commit+push otomatis.
- `--set-image <ns> <deploy> <image>`: Imperative hot-patch K8s live deployment + `--watch`.
- `--is-match-image <ns> <deploy> <gitops> <branch> <yaml>`: Drift check antara image live cluster vs spec di GitOps repo.

**E. TUI Planner (Jev System One)**
- `--tui`: Buka Jev Agent Planner — katalog tool/provider/pattern dibaca **dari PocketBase** (`build_q_tools`, `build_q_providers`, `build_q_patterns`), cache lokal TTL 1 jam di `~/.build-q/.tui-cache.json`. Fallback bootstrap minimum bila PB down + cache kosong.
- `--tui-pull`: Refresh cache katalog dari PocketBase (manual).
- `--tui-sync <seed.json>`: Upload katalog seed JSON ke PocketBase (upsert by unique field).
- `--tui-ls`: Tampilkan ringkasan katalog aktif (tools per-category, providers, patterns).
- Di dalam TUI: `/e2e REPO REF` (analisa CI/CD end-to-end, lihat bagian 16), `/e2e --sim [SKENARIO]`, `/refresh`, `/set KEY` (input tersembunyi). Riwayat input tersimpan, satu error tidak lagi mematikan sesi, dan setiap eksekusi tercatat di `~/.build-q/.tui-audit.log`.

### 15. MCP Server — AI Assistant Integration (`--mcp`, sejak v0.1.44)

Sejak v0.1.46, `bq` menyediakan **MCP (Model Context Protocol) server** yang meng-expose seluruh kapabilitas CLI sebagai tools terstruktur untuk AI assistant (Claude Code, Claude Desktop, atau MCP client lain). AI bisa langsung memanggil Dockerfile scanning, K8s anomaly detection, pipeline trigger, GitOps operations, dan lainnya — tanpa perlu construct command string atau parse stdout.

#### Instalasi MCP

```bash
# Baru install
pipx install 'build-q[mcp]'

# Sudah punya bq, tambahkan MCP dependency
pipx inject build-q mcp
```

#### Quick Start — Claude Code

Tambahkan konfigurasi MCP server di settings Claude Code:

```json
// .claude/settings.json
{
  "mcpServers": {
    "bq": {
      "command": "bq",
      "args": ["--mcp"]
    }
  }
}
```

Setelah ini, Claude Code akan otomatis mendeteksi 18 tools dari `bq` dan bisa memanggilnya saat user bertanya tentang Dockerfile, K8s manifest, build image, dan sebagainya.

#### Quick Start — Claude Desktop

```json
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
  "mcpServers": {
    "bq": {
      "command": "bq",
      "args": ["--mcp"]
    }
  }
}
```

#### Transport Modes

| Mode | Flag | Kegunaan |
|------|------|----------|
| **stdio** (default) | `bq --mcp` | Claude Code, Claude Desktop — komunikasi via stdin/stdout |
| **SSE** | `bq --mcp --mcp-transport sse --mcp-port 3001` | Remote access via HTTP Server-Sent Events |

```bash
# Stdio (default, untuk Claude Code / Desktop)
bq --mcp

# SSE (untuk remote / web client)
bq --mcp --mcp-transport sse --mcp-port 3001

# SSE expose ke network (hati-hati — akses terbuka)
bq --mcp --mcp-transport sse --mcp-port 3001 --mcp-host 0.0.0.0
```

#### Daftar Lengkap MCP Tools (18)

**Tier 1 — Read-only (aman, tanpa side effect)**

| Tool | CLI Equivalent | Deskripsi |
|------|---------------|-----------|
| `dockerfile_scan` | `bq --fix-dockerfile --scan-only` | Scan Dockerfile lokal (22+ rules: security, performance, compliance) |
| `dockerfile_scan_remote` | `bq --fix-dockerfile --remote-repo X` | Scan Dockerfile dari GitHub repo tanpa clone |
| `k8s_anomaly_scan` | `bq --anomaly-scan` | Scan K8s manifest (10+ checks: replicas, imagePullSecret, resource limits, securityContext) |
| `build_doctor` | `bq --doctor` | Preflight check (Docker, buildx, Git, credentials, builder) |
| `config_show` | `bq --config` | Tampilkan konfigurasi aktif (sensitive values di-mask) |
| `pipeline_check` | `bq --check` | Verifikasi repo readiness (ref, cicd.json, artifacts, image, GitOps) |
| `gitops_match_check` | `bq --is-match-image` | Compare image live K8s vs GitOps YAML (drift detection) |
| `image_check` | — | Cek apakah image:tag sudah ada di Docker Hub |
| `webhook_status` | `bq --cicd-webhook` | Cek apakah webhook CI/CD terinstall di repo |
| `docker_build_preview` | `bq --dry-run` | Preview build command tanpa eksekusi |

**Tier 2 — Local Action (modifikasi file lokal, reversible)**

| Tool | CLI Equivalent | Deskripsi |
|------|---------------|-----------|
| `dockerfile_fix` | `bq --fix-dockerfile` | Auto-fix Dockerfile issues (backup ke .bak) |
| `sops_encrypt` | `bq --sops-encrypt` | Encrypt file dengan SOPS + age |
| `sops_decrypt` | `bq --sops-decrypt` | Decrypt file SOPS |

**Tier 3 — External Action (modifikasi state eksternal)**

| Tool | CLI Equivalent | Deskripsi |
|------|---------------|-----------|
| `docker_build` | `bq repo ref` | Build image via buildx + optional push ke registry |
| `pipeline_trigger` | `bq --cicd-trigger` | Trigger Jenkins X pipeline via webhook |
| `gitops_set_image` | `bq --gitops-set-image` | Update image tag di GitOps YAML + push |
| `gitops_bootstrap` | `bq --bootstrap-k8s` | Bootstrap K8s manifest (Secret/Deployment/Service) + PR |
| `k8s_set_image` | `bq --set-image` | Hot-patch deployment live di cluster |

#### MCP Resources (4)

Resources adalah konteks read-only yang AI bisa akses kapan saja:

| URI | Deskripsi |
|-----|-----------|
| `bq://version` | Versi bq + daftar semua capabilities |
| `bq://config` | Konfigurasi aktif (token/password di-mask) |
| `bq://doctor` | Hasil preflight check |
| `bq://catalog` | Jev tool catalog (tools, providers, patterns) |

#### MCP Prompts (3)

Prompts adalah workflow templates yang membimbing AI menjalankan serangkaian tools:

| Prompt | Arguments | Workflow |
|--------|-----------|----------|
| `deploy-new-service` | `repo`, `ref`, `env` | doctor → check → scan → build → bootstrap → verify |
| `scan-and-fix` | `repo`, `ref` | scan Dockerfile remote → fix → scan K8s manifest → report |
| `rollout-update` | `repo`, `ref`, `namespace` | image check → build → set-image (imperative/declarative) → verify |

#### Contoh Interaksi via Claude Code

```
User: "scan Dockerfile di repo pay-be-topup-manager untuk security issues"

Claude: (memanggil tool dockerfile_scan_remote)
  → repo: "Qoin-Digital-Indonesia/pay-be-topup-manager"
  → ref: "main"
  → Result: {exit_code: 1, issues: [{rule: "DF-SEC-01", ...}]}

User: "cek apakah image v1.2.3 sudah ada di registry"

Claude: (memanggil tool image_check)
  → image: "loyaltolpi/pay-be-topup-manager:v1.2.3"
  → Result: {exit_code: 0, data: {exists: true}}

User: "trigger pipeline untuk build develop"

Claude: (memanggil tool pipeline_trigger)
  → repo: "Qoin-Digital-Indonesia/pay-be-topup-manager"
  → ref: "develop"
  → Result: {exit_code: 0, stdout: "...triggered..."}
```

#### Audit Logging

Setiap tool call dicatat di `~/.build-q/.mcp-audit.log` dalam format JSON:

```json
{"ts":"2026-10-07 15:12:50","level":"INFO","msg":"tool=run_doctor exit_code=0 elapsed_ms=6878"}
{"ts":"2026-10-07 15:12:52","level":"INFO","msg":"tool=run_dockerfile_scan exit_code=2 elapsed_ms=0"}
```

#### Structured Response

Semua tool mengembalikan JSON terstruktur:

```json
{
  "exit_code": 0,
  "stdout": "...",
  "stderr": "",
  "elapsed_ms": 123,
  "data": { ... }
}
```

Jika error:
```json
{
  "exit_code": 2,
  "error": "ValueError: path not found",
  "error_type": "ValueError",
  "elapsed_ms": 1
}
```

#### Verifikasi

```bash
# Cek MCP server berjalan
bq --version           # build-q (bq) 0.1.46

# Cek help MCP flags
bq --help | grep mcp

# Test manual (jika punya MCP client SDK)
# Server akan menampilkan 18 tools, 4 resources, 3 prompts
```

#### Arsitektur

```
build_q/mcp/
├── __init__.py
├── _capture.py          # stdout/stderr capture + ANSI strip + audit log
├── server.py            # MCPServer entry point + typed tool wrappers
├── resources.py         # 4 MCP resources (version, config, doctor, catalog)
├── prompts.py           # 3 MCP prompts (deploy, scan-fix, rollout)
└── tools/
    ├── scan.py          # dockerfile_scan, dockerfile_scan_remote, k8s_anomaly_scan, dockerfile_fix
    ├── infra.py         # build_doctor, config_show
    ├── cicd.py          # pipeline_check, gitops_match_check, image_check, webhook_status, pipeline_trigger
    ├── build.py         # docker_build, docker_build_preview
    ├── gitops.py        # gitops_set_image, gitops_bootstrap
    ├── k8s.py           # k8s_set_image
    └── sops.py          # sops_encrypt, sops_decrypt
```

---

### 16. Analisa CI/CD End-to-End (`--e2e`, sejak v0.1.47)

"Commit saya kok belum live?" — `bq --e2e` menelusuri satu commit dari trigger sampai
ArgoCD dan menunjuk **titik putus** (tahap pertama yang gagal). Read-only.

```bash
bq --e2e pay-be-topup-manager develop     # repo pendek → GITHUB_ORG
bq --e2e Qoin-Digital-Indonesia/pay-be-topup-manager v1.4.2
```

| Tahap | Yang dicek |
|---|---|
| Source | ref → commit, ENV & tag image yang diharapkan (aturan sama dgn Tekton), `cicd.json` |
| Trigger | GitHub Action `trigger-ci.yml` untuk commit tsb, webhook `cicd-hw.qoin.id/hook` |
| Pipeline | PipelineRun `webhook-<repo>-*` di `JX_KUBE_CONTEXT` — step gagal + 15 baris log terakhir, results build/GitOps |
| GitOps | tag di YAML branch ENV (`develop`/`staging`/`sandbox`), atau status PR ke `main` untuk tag `v*` |
| ArgoCD | Application di `ARGOCD_KUBE_CONTEXT` (default `hw-pro-p`): sync, health, image live, override kustomize |

Exit code: `0` sehat · `1` ada tahap gagal · `2` menunggu (PR/sync/pipeline) atau ada tahap
yang tidak bisa diperiksa (cluster tak terjangkau → verdict *incomplete*, tidak pernah "sehat").

**Simulasi tanpa cluster** — untuk demo, pelatihan, dan self-test:

```bash
bq --e2e --simulate list                   # 17 skenario (trigger gagal, build gagal, PR belum merge, OutOfSync, …)
bq --e2e --simulate                        # self-test semua skenario
bq --e2e --simulate pipeline-build-failed  # laporan lengkap 1 skenario
```

Config baru di `~/.build-q/.env` (opsional): `ARGOCD_KUBE_CONTEXT=hw-pro-p`, `ARGOCD_NAMESPACE=argocd`.

---

### 17. Contoh full (customize secret, platform, build-arg)

```bash
bq plus-be-service staging \
    --secret id=custom,src=/path/to/secret \
    --platform linux/amd64 \
    --no-push \
    --build-arg BRANCH=custom-branch \
    --build-arg FEATURE_FLAG=on
```

---

## 🔗 Perintah Lengkap

```
bq [<repo> [<ref>]] [OPTIONS]

Subcommands (mutually exclusive):
  --init                    Buat ~/.build-q/.env + init Buildx builder
  --init --force            Recreate config
  --config                  Tampilkan konfigurasi aktif
  --init-jx                 Scaffold Makefile/compose/Dockerfile/workflow (modern secret mount)
  --init-jx --force         Overwrite file existing
  --init-legacy             Scaffold Makefile + compose.yaml pola legacy (ARG GITHUB_USER/TOKEN)
  --init-legacy --force     Overwrite file existing
  --gh-action-init          Bootstrap standar trigger-ci.yml (hapus workflow lain + set secrets)
  --init-secrets [<repo>]   Set GitHub Actions webhook secrets
  --fix-dockerfile [PATH]   Migrasi Dockerfile legacy ke secret mount (+ MAINTAINER/ENV/standalone netrc)
  --pr-fix [<repo> <ref>]   One-shot 11-step fix untuk repo legacy template (branch + commit + push + set secrets + PR).
  --cicd-webhook [<repo>]   Cek eksistensi webhook cicd-hw.qoin.id.
  --cicd-trigger <repo> <ref> Manual trigger pipeline via webhook (simulasi GitHub event).
  --check [<repo> <ref>]    Cek readiness repo (7 aspek).
  --repo-check <repo> <ref> Cek config CICD dari PocketBase tanpa clone.
  --doctor                  Diagnostic local toolchain (git, docker, tokens).
  --tui                     Buka Jev Agent Planner (TUI, katalog dari PocketBase).
  --tui-pull                Refresh TUI catalog cache dari PocketBase.
  --tui-sync <seed.json>    Upload katalog JSON ke PocketBase (upsert).
  --tui-ls                  Ringkas katalog TUI aktif.
  --e2e <repo> <ref>        Analisa CI/CD end-to-end: trigger → Tekton → GitOps → ArgoCD (read-only).
  --e2e --simulate [list|all|<skenario>]
                            Simulasi --e2e tanpa cluster/jaringan (demo & self-test).
  --set-image <ns> <dp> <img> Hot-patch deployment live di K8s.
  --gitops-set-image ...    Update image di repo GitOps + push.
  --is-match-image ...      Cek drift image K8s live vs GitOps.
  --pb-login/status/pull/logout PocketBase IDP operations.
  --mcp                     Run MCP server (stdio transport). 18 tools untuk AI assistant.
  --mcp --mcp-transport sse --mcp-port PORT
                            Run MCP server via SSE (HTTP) di PORT.
  --mcp-host HOST           Bind address untuk MCP SSE (default: 127.0.0.1).
  --bootstrap-k8s <repo> <ref> \
     --gitops-repo <r> --gitops-branch <b> --path-yaml <p>
                            Bootstrap Secret + Deployment + Service ke repo GitOps.
                            Opsional: --namespace, --nodepool, --env, --stack,
                            --replicas, --image-pull-secret, --apply-secret,
                            --kube-context, --force-recreate-deploy.

Build modes:
  (default)                 Build dari direktori lokal via docker buildx
  --clone <owner/repo>      Clone via `gh` CLI, lalu build
  --clone ... --clean       Hapus folder clone setelah build
  --remote                  Build via Buildx Git context (tanpa clone) — auto-fallback HTTPS bila SSH_AUTH_SOCK kosong
  --compose                 Jalankan `make build ENV=... && make release ENV=...` (bukan buildx)

Build options:
  --cicd PATH               Path cicd.json (default: cicd/cicd.json)
  --context DIR             Build context (default: .)
  -f, --dockerfile PATH     Path Dockerfile (default: Dockerfile)
  -t, --tag IMAGE:TAG       Override image tag
  --push / --no-push        Push image (default: push)
  --image-check / --rebuild Cek image di registry, skip bila ada (default: on)
  --no-image-check          Sama dengan --rebuild
  --platform PLATFORM       Target platform (default: linux/amd64)
  --build-arg KEY=VALUE     Build arg (dapat diulang)
  --secret id=ID,src=PATH   Secret build (dapat diulang; netrc otomatis)
  --gh-auth                 Inject GITHUB_USER/TOKEN dari `gh` CLI
  --token VALUE             Webhook token (dengan --init-secrets / --gh-action-init)
  --dry-run                 Preview command tanpa eksekusi
  --version                 Tampilkan versi
```

---

## 📋 Requirements

- **Python 3.10+**
- **Docker** + **Buildx plugin**
- **Git** (opsional; wajib untuk auto-detect & mode local)
- **GitHub CLI (`gh`)** — wajib untuk `--clone`, `--remote`, `--gh-auth`, `--init-secrets`, `--gh-action-init`
- **kubectl** — opsional; hanya untuk `--init-secrets` / `--gh-action-init` (fetch token otomatis dari cluster)
- **MCP SDK** — opsional; hanya untuk `--mcp` (`pipx inject build-q mcp`)

---

## 📄 Template Terpusat (Gist)

Sejak `v0.1.14`, template scaffolding hidup di **gist publik**, bukan hard-coded di package:

- **URL gist:** https://gist.github.com/mamatnurahmat/35cc4c36e7c7c2d236a1b5149cdbcfd9
- **Raw base:** `https://gist.githubusercontent.com/mamatnurahmat/<gist-id>/raw/<filename>`
- **6 file:** `Makefile.modern`, `compose.yaml.modern`, `Dockerfile.modern`, `Makefile.legacy`, `compose.yaml.legacy`, `trigger-ci.yml`

**Alur load:**
```
load_template(name)
   ├── 1. cache ~/.build-q/templates/<file>  (TTL 3600s, fresh?)  ─► pakai
   ├── 2. fetch dari gist raw URL                                ─► refresh cache + pakai
   ├── 3. cache lama (expired)                                   ─► pakai (dengan warning)
   └── 4. bundled snapshot di build_q/templates.py               ─► pakai (warning)
```

### Cara update template

**Update HARUS via gist**, jangan edit `build_q/templates.py`. `_BUNDLED_*` di file itu hanya snapshot offline-fallback.

```bash
# Clone gist untuk edit local
gh gist clone 35cc4c36e7c7c2d236a1b5149cdbcfd9 build-q-templates
cd build-q-templates

# Edit file yang ingin diubah (mis. Makefile.modern)
vim Makefile.modern

# Commit + push (gist adalah git repo — otomatis publish)
git commit -am "fix: adjust Makefile.modern IMAGE_TAG logic"
git push

# Verifikasi raw URL sudah kepakai
curl -s https://gist.githubusercontent.com/mamatnurahmat/35cc4c36e7c7c2d236a1b5149cdbcfd9/raw/Makefile.modern | head
```

### Force refresh cache di sisi user

Kalau user ingin ambil template terbaru sebelum TTL expired:
```bash
rm -rf ~/.build-q/templates      # hapus cache — next scaffold auto-fetch
# atau override TTL per-invocation:
BUILD_Q_GIST_TTL=0 bq --init-jx
```

### Override gist source (advanced)

Untuk fork/private mirror, override via env vars:
```bash
export BUILD_Q_GIST_USER=your-org
export BUILD_Q_GIST_ID=abcd1234...
export BUILD_Q_GIST_TTL=7200      # 2 jam
export BUILD_Q_GIST_TIMEOUT=15    # 15 detik
bq --init-jx
```

### Pipeline Tekton pakai gist yang SAMA (Opsi B strict)

Sejak `~/jenkins-x/pipeline` versi terbaru, Tekton pipeline **overlay** Makefile/compose/Dockerfile dari gist ini SEBELUM `make build`. Hasilnya BYTE-IDENTICAL dengan `bq --init-jx` — jadi tidak ada drift antara build lokal `bq --compose` dan build pipeline JX.

Alur di pipeline (step `sync-scaffold` di `.lighthouse/triggers.yaml`):
1. Early-skip: `docker manifest inspect` — kalau image sudah ada, skip semuanya.
2. Validate: `cicd/cicd.json` wajib ada (fail-fast kalau tidak).
3. Auto-detect mode: `ARG GITHUB_USER/TOKEN` di Dockerfile → legacy (Dockerfile tidak di-overlay). Else → modern (semua 3 file di-overlay).
4. Fetch dari gist raw URL, render placeholder pakai `sed s|{{KEY}}|VALUE|g` dengan 7 keys (IMAGE, PROJECT, PORT, CLUSTER, DEPLOYMENT, NODETYPE, ORG_REGISTRY) — sama persis dengan `render()` di `build_q/templates.py`.
5. `make build && make release` (fail-fast, tanpa `|| echo WARN`).

**BREAKING:** repo yang customize `Makefile` / `compose.yaml` / `Dockerfile` (modern mode) akan **di-overlay tiap build**. Custom target Makefile HILANG. Kalau perlu kustomisasi, edit di gist saja (semua repo dapat perubahan otomatis).

### Sinkronisasi bundled fallback (maintainer only)

Saat rilis `bq` baru, refresh `_BUNDLED_*` di `build_q/templates.py` agar offline user tetap dapat template terkini:
```bash
GID=35cc4c36e7c7c2d236a1b5149cdbcfd9
for f in Makefile.modern compose.yaml.modern Dockerfile.modern Makefile.legacy compose.yaml.legacy trigger-ci.yml; do
  curl -s "https://gist.githubusercontent.com/mamatnurahmat/$GID/raw/$f" > "/tmp/$f"
done
# lalu paste isi ke masing-masing _BUNDLED_* di build_q/templates.py
```

---

## 🧭 Troubleshooting

| Gejala | Kemungkinan penyebab | Solusi |
| -------- | --------------------- | -------- |
| `builder "mybuilder" not found` | Buildx belum di-bootstrap | `bq --init` |
| `authentication required` saat push | Belum login registry | `docker login` (atau `docker login <registry>`) |
| `invalid empty ssh agent socket` (mode `--remote`) | `SSH_AUTH_SOCK` kosong | Sudah auto-fallback ke HTTPS + `gh auth token`. Kalau tetap gagal → `gh auth login` |
| `gh: command not found` | GitHub CLI belum ter-install | `brew install gh && gh auth login` |
| `--init-secrets` gagal fetch token | Konteks kubectl salah / secret tidak ada | Set `JX_KUBE_CONTEXT` di `~/.build-q/.env`, atau `--token xxxx` |
| Build sukses tapi image tidak muncul di registry | Salah `REGISTRY_URL` di config | `bq --config` → verifikasi, lalu `bq --init --force` |
| `bq --version` tidak berubah setelah upgrade | pipx cache | `pipx install --force build-q` |

---

## 🚀 Development & Release

Ada **dua cara** rilis: (A) via GitHub Actions otomatis dari tag — **direkomendasikan**, dan (B) via `Makefile` lokal — cocok untuk hotfix/debug.

### A. Auto-release via GitHub Actions (rekomendasi)

Workflow `.github/workflows/pypi-release.yml` dipicu oleh push tag `v*` (atau manual via `workflow_dispatch`) dan otomatis build + upload ke PyPI.

**Flow rilis versi berikutnya:**

```bash
# 1. Bump versi (patch) — auto-update pyproject.toml + build_q/__init__.py
python3 scripts/bump_version.py                # bump patch (0.1.12 → 0.1.13)
# atau versi eksplisit:
python3 scripts/bump_version.py 0.2.0

# 2. Baca versi baru
VER=$(grep -m1 '^version = ' pyproject.toml | sed 's/.*"\(.*\)".*/\1/')
echo "Releasing v$VER"

# 3. Commit bump
git add pyproject.toml build_q/__init__.py
git commit -m "release: v$VER"

# 4. Tag + push (tag inilah yang memicu workflow)
git tag "v$VER"
git push origin main "v$VER"

# 5. Pantau workflow
gh run watch --repo mamatnurahmat/build-q --exit-status
# atau lihat di browser:
gh run list --workflow=pypi-release.yml --repo mamatnurahmat/build-q --limit 3
```

Setelah workflow selesai (± 15 detik), package tersedia di:

- <https://pypi.org/project/build-q/><versi>/
- Upgrade user: `pipx upgrade build-q`

**Apa yang dilakukan workflow (lihat `.github/workflows/pypi-release.yml`):**

1. **Checkout** repo pada commit tag.
2. **Setup Python 3.12**.
3. **Verifikasi tag** — pastikan `v<X>` cocok dengan version di `pyproject.toml` **dan** `build_q/__init__.py`. Bila mismatch, gagal (mencegah rilis "kosong").
4. **Install** `build` + `twine`.
5. **Build** sdist + wheel via `python -m build`.
6. **Validate** dengan `twine check dist/*`.
7. **Upload** via `twine upload --skip-existing` — kalau versi sudah ada di PyPI, aman skip (idempoten).
8. **Summary** dengan link PyPI.

**Autentikasi:** workflow pakai GitHub Actions secret `PYPI_API_TOKEN` (username `__token__`). Token PyPI **project-scoped** ini di-set sekali via `gh secret set`. Cara set/rotate token:

```bash
# Metode andal — pakai env-file (JANGAN pakai --body - karena pipe stdin
# rawan corrupt untuk token panjang).
TMPFILE=$(mktemp) && chmod 600 "$TMPFILE"
python3 -c "
import configparser, os
c = configparser.ConfigParser()
c.read(os.path.expanduser('~/.pypirc'))
print('PYPI_API_TOKEN=' + c['pypi']['password'])
" > "$TMPFILE"
gh secret set -f "$TMPFILE" --repo mamatnurahmat/build-q
rm -f "$TMPFILE"

# Verifikasi:
gh secret list --repo mamatnurahmat/build-q
```

**Manual trigger tanpa tag** (misal untuk re-upload versi yang sudah ada di `pyproject.toml` — `--skip-existing` akan handle):

```bash
gh workflow run pypi-release.yml --repo mamatnurahmat/build-q
```

**Cek/re-run workflow yang gagal:**

```bash
gh run list --workflow=pypi-release.yml --repo mamatnurahmat/build-q --limit 5
gh run view <RUN_ID> --repo mamatnurahmat/build-q --log-failed
gh run rerun <RUN_ID> --failed --repo mamatnurahmat/build-q
```

### B. Release manual via Makefile (lokal)

Cocok untuk hotfix cepat / troubleshoot. Baca kredensial dari `~/.pypirc` lokal.

```bash
make build             # build sdist + wheel ke dist/
make install           # install lokal (pipx editable)
make release           # bump patch, build, install, upload ke PyPI
make release V=1.0.0   # release versi spesifik
make clean             # bersihkan artefak
```

Langkah `make release`:

1. `scripts/bump_version.py` bump patch di `pyproject.toml` + `build_q/__init__.py`.
2. Build sdist + wheel via `python3 -m build` (fallback: `pipx run --spec build pyproject-build`).
3. Install lokal editable via `pipx install -e . --force`.
4. Upload ke PyPI via `pipx run twine upload --skip-existing dist/*` — kredensial dari `~/.pypirc`.

> ⚠️ **Setelah `make release`, jangan lupa commit + tag + push** supaya history Git & GitHub Actions selaras dengan PyPI:
>
> ```bash
> VER=$(grep -m1 '^version = ' pyproject.toml | sed 's/.*"\(.*\)".*/\1/')
> git add pyproject.toml build_q/__init__.py
> git commit -m "release: v$VER"
> git tag "v$VER" && git push origin main "v$VER"
> ```
>
> Tag push tetap akan memicu workflow, tapi karena `--skip-existing`, upload akan skip (idempoten).

### Release troubleshooting

| Gejala | Penyebab | Solusi |
| -------- | ---------- | -------- |
| Workflow gagal di step **Verify tag matches** | Tag `v0.1.13` tapi `pyproject.toml` masih `0.1.12` | Jalankan `python3 scripts/bump_version.py 0.1.13` dulu, commit, re-tag |
| Workflow gagal `403 Forbidden` di upload | Secret `PYPI_API_TOKEN` corrupt / kadaluwarsa | Re-set secret via metode env-file di atas |
| PyPI `400 File already exists` | Versi sudah pernah di-upload dari lokal | Sudah di-handle `--skip-existing` — workflow tetap hijau |
| Version di PyPI tidak berubah setelah tag | Tag lama di-hapus & re-push versi sama | PyPI TIDAK menerima re-upload versi yang sama — bump patch dulu |

---

## 📄 License

MIT
