AGENTS.md · git:20260418.9f757ad · 2026-04-18 · sha256 311be1fb70e84afb

AGENTS.md git:20260418.9f757adA

Immutable. This exact content never changes and is served at /api/v1/blob/311be1fb70e84afb.

# CLAUDE.md — Rust Jira CLI

Dokumen ini adalah panduan untuk Claude (dan contributor) saat bekerja di repo ini.
Baca seluruh dokumen sebelum membuat perubahan apapun.

---

## Aturan Claude — hal yang TIDAK BOLEH dilakukan

> Bagian ini khusus untuk Claude. Wajib dibaca dan dipatuhi tanpa exception.

**Claude DILARANG menjalankan perintah git apapun yang bersifat menulis ke history:**

```
# DILARANG — jangan pernah jalankan ini
git commit
git push
git tag
git merge
git rebase
git cherry-pick
git reset --hard
git stash
```

Claude hanya boleh:

- Membuat dan mengedit file di filesystem
- Menjalankan `cargo` commands untuk build/test/check
- Menjalankan `git status`, `git diff`, `git log` untuk **membaca** state saja

Semua operasi commit, push, dan tag adalah tanggung jawab pemilik repo sepenuhnya.
Kalau Claude perlu "commit sesuatu", cukup siapkan file-nya dan instruksikan
pemilik repo untuk melakukan commit sendiri.

### TASK.md — checklist kerja Claude

File `TASK.md` di root repo adalah checklist kerja Claude yang **gitignored** (tidak masuk repo).

**Claude WAJIB:**

1. Baca `TASK.md` di awal setiap sesi baru
2. Update checkbox `[ ]` → `[x]` segera setelah task selesai dan smoke test hijau
3. Kalau `TASK.md` tidak ada (misal fresh clone), buat ulang berdasarkan context percakapan atau tanya pemilik repo

Tujuan: supaya setelah compaction, Claude bisa lanjut dari titik yang benar tanpa harus
mengulang pekerjaan yang sudah selesai.

---

## Project overview

Rust CLI untuk Atlassian Jira yang menggantikan / memperbaiki keterbatasan
[jira-cli (Go)](https://github.com/ankitpokhrel/jira-cli). Fokus utama:

- Support **full custom field** — dynamic introspection via API, tidak ada YAML
  config manual
- **Attachment upload** native dari terminal (multipart/form-data)
- Kompatibel dengan **Jira REST API v3** terbaru — `/search/jql`, bulk ops,
  field schemes, priority schemes
- TUI interaktif via **ratatui** + **crossterm**
- Binary tunggal, tidak ada runtime dependency

---

## Workspace structure

```
jira-commands/
├── CLAUDE.md
├── SECURITY.md             # responsible disclosure policy
├── CHANGELOG.md            # generated/updated by release-please
├── Cargo.toml              # workspace root
├── crates/
│   ├── jira-core/          # PUBLIC LIBRARY: API client, auth, model, ADF parser
│   │   ├── Cargo.toml      # dipublish ke crates.io sebagai "jira-core"
│   │   └── src/
│   └── jira/               # BINARY: clap commands, TUI, wiring semua crate
│       ├── Cargo.toml      # dipublish ke crates.io sebagai "jira-commands"
│       └── src/
├── plugin/
│   └── .claude-plugin/     # Claude Code plugin (9 skills)
├── .github/
│   └── workflows/
│       ├── ci.yml              # fmt + clippy + test, semua push/PR ke main
│       ├── security.yml        # cargo audit, SHA-pinned
│       ├── release-please.yml  # otomatis bump versi + CHANGELOG + tag
│       └── release.yml         # build binaries + publish crates.io (trigger: tag)
├── release-please-config.json
├── .release-please-manifest.json
└── tests/                  # integration tests (cross-crate)
```

### Dua cara install untuk end user

```bash
# Cara 1 — via cargo (dari crates.io)
cargo install jira-commands

# Cara 2 — download binary langsung dari GitHub Releases
# (tidak perlu Rust toolchain)
```

### Pemisahan tanggung jawab crate

**`jira-core`** — public API, harus stabil dan terdokumentasi:

- `JiraClient` — semua HTTP call ke Jira API
- Model types (`Issue`, `Field`, `Sprint`, dll.)
- ADF parser dan renderer
- Auth (token, PAT, keyring)
- Error types (`thiserror`)

Orang lain bisa pakai `jira-core` sebagai library dependency tanpa harus pakai CLI-nya:

```toml
[dependencies]
jira-core = "0.4"
```

**`jira/` (binary)** — tidak perlu stabil sebagai API publik:

- Semua `clap` command definitions
- TUI dengan `ratatui` + `crossterm`
- Interactive prompt dengan `inquire`
- Hanya re-export hal yang perlu dari `jira-core`

---

## Tech stack

| Kebutuhan          | Crate                                              | Keterangan                                     |
| ------------------ | -------------------------------------------------- | ---------------------------------------------- |
| CLI arg parsing    | `clap` (derive)                                    | Subcommands, help generation                   |
| TUI framework      | `ratatui` + `crossterm`                            | Fork aktif dari tui-rs                         |
| Interactive prompt | `inquire`                                          | Select, input, confirm, Text                   |
| Async HTTP         | `reqwest` (async, multipart, rustls-tls)           | Native multipart untuk attachment              |
| Async runtime      | `tokio`                                            | Full features                                  |
| Serialisasi        | `serde` + `serde_json`                             | JSON API response                              |
| Config             | `figment` + `toml`                                 | Multi-source config (file + env)               |
| Token storage      | Config file `~/.config/jira/config.toml` chmod 600 | Tidak pakai keyring (cross-platform)           |
| Path resolution    | `dirs`                                             | XDG-compliant config path                      |
| Markdown → ADF     | `comrak`                                           | CommonMark + GFM, konversi ke ADF              |
| MIME detection     | `mime_guess`                                       | Untuk attachment upload                        |
| Browser open       | `open`                                             | `jira issue view --open`, TUI `o` key          |
| Progress           | `indicatif`                                        | Spinner dan progress bar (suppress di non-TTY) |
| Error handling     | `anyhow` (app) + `thiserror` (lib)                 |                                                |
| Logging            | `tracing` + `tracing-subscriber`                   | Debug mode via `--verbose`                     |

---

## Jira API — aturan implementasi

### Endpoint yang WAJIB dipakai (API v3 terbaru)

```
# Search — JANGAN pakai /rest/api/3/search (sudah mati Okt 2025)
GET/POST /rest/api/3/search/jql        ← ini yang benar
POST     /rest/api/3/search/approximate-count   ← untuk estimasi total

# Field system — JANGAN pakai /fieldconfiguration* (dihapus Juli 2026)
GET  /rest/api/3/projects/fields       ← field tersedia per project
PUT  /rest/api/3/field/association     ← associate field ke project
DEL  /rest/api/3/field/association     ← remove association

# Priority — JANGAN assume priority global
GET  /rest/api/3/priorityscheme        ← priority per project scheme
```

### Endpoint yang JANGAN diimplementasi

```
GET/POST /rest/api/3/search            ← sudah dimatikan
GET/POST /rest/api/3/fieldconfiguration*  ← dihapus Juli 2026
```

### Pagination — cursor-based (bukan offset)

```rust
// BENAR
let mut next_page_token: Option<String> = None;
let mut iterations = 0;
let max_iterations = 500; // safeguard infinite loop bug Atlassian

loop {
    iterations += 1;
    if iterations > max_iterations { break; }

    let resp = client.search_jql(&jql, next_page_token.as_deref()).await?;
    // proses resp.issues ...

    match resp.next_page_token {
        Some(token) => next_page_token = Some(token),
        None => break,
    }
}

// SALAH — startAt tidak ada lagi
client.search(jql, start_at).await?; // ❌
```

### Rate limiting — wajib handle

Setiap HTTP client call harus handle 429 dengan `Retry-After`:

```rust
// Di jira-core/src/client.rs
if response.status() == 429 {
    let retry_after = response
        .headers()
        .get("Retry-After")
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.parse::<u64>().ok())
        .unwrap_or(60);
    tokio::time::sleep(Duration::from_secs(retry_after)).await;
    // retry request
}
```

### Field resolution — selalu runtime, tidak hardcode

```rust
// BENAR — fetch field schema saat runtime, cache dengan TTL
let fields = client.get_project_fields(&project_key).await?;

// SALAH — jangan hardcode assumption tentang field apapun
let story_points = issue.fields["customfield_10016"]; // ❌
```

### Async task pattern — untuk operasi berat

Beberapa operasi (archive, bulk ops besar) bersifat async di sisi Jira:

```rust
// Submit → poll → selesai
let task_url = client.archive_issues(&jql).await?;
loop {
    let status = client.poll_task(&task_url).await?;
    match status {
        TaskStatus::Complete => break,
        TaskStatus::InProgress(pct) => spinner.set_message(format!("{}%", pct)),
        TaskStatus::Failed(msg) => return Err(anyhow!(msg)),
    }
    tokio::time::sleep(Duration::from_secs(2)).await;
}
```

### Deteksi Jira tier sebelum pakai fitur premium

```rust
// Plans API hanya Jira Premium — detect dulu
let info = client.get_server_info().await?;
if !info.is_premium() {
    return Err(UserError::FeatureRequiresPremium("Plans"));
}
```

### Dua base URL — jangan campur

```rust
// Platform API
const PLATFORM_BASE: &str = "/rest/api/3";

// Jira Software / Agile API (sprint, board)
const AGILE_BASE: &str = "/rest/agile/1.0";

// JANGAN hardcode langsung di setiap call
// Pakai method di client yang sudah tahu endpoint mana yang benar
```

---

## Commit message convention (untuk pemilik repo)

> Claude tidak melakukan commit. Bagian ini adalah panduan untuk pemilik repo.

Pakai **Conventional Commits**:

```
feat: add attachment upload command
fix: handle nextPageToken infinite loop
docs: update CLAUDE.md with rate limit rules
chore: bump reqwest to 0.12
refactor: extract ADF renderer to separate module
test: add integration test for bulk transition
ci: add Windows target to release workflow
perf: cache field schema per project with TTL
```

Format: `<type>(<scope optional>): <description singkat>`

Types: `feat`, `fix`, `docs`, `chore`, `refactor`, `test`, `ci`, `perf`

Commit message dalam bahasa Inggris.

---

## Checklist sebelum commit/push (tanggung jawab pemilik repo)

Claude akan menjalankan smoke test dan melaporkan hasilnya.
Pemilik repo yang memutuskan apakah akan commit dan push.

### 1. Smoke test — Claude jalankan ini, laporkan hasilnya

```bash
cargo fmt --all -- --check && \
cargo clippy --all-targets --all-features -- -D warnings && \
cargo test --all && \
cargo build --all
```

Kalau ada yang merah, Claude fix dulu sampai semua hijau sebelum lapor ke pemilik repo.

### 2. Conflict check — pemilik repo jalankan ini sebelum push

```bash
git fetch origin
git status

# Kalau ada divergence
git rebase origin/main
# resolve conflict kalau ada
git rebase --continue
```

Gunakan `rebase`, bukan `merge`, supaya history tetap linear.

### 3. Version bump

**Jangan pernah manual bump versi.** Release-please menentukan versi berikutnya
secara otomatis berdasarkan tipe commit:

| Commit type                                | Bump yang dihasilkan       |
| ------------------------------------------ | -------------------------- |
| `feat:`                                    | MINOR (`0.4.1 → 0.5.0`)    |
| `fix:`, `perf:`, `refactor:`               | PATCH (`0.4.1 → 0.4.2`)    |
| `feat!:` atau `BREAKING CHANGE:` di footer | MAJOR (`0.4.1 → 1.0.0`)    |
| `chore:`, `docs:`, `ci:`, `test:`          | Tidak trigger release baru |

Release-please akan bump: `crates/jira-core/Cargo.toml`, `crates/jira/Cargo.toml`,
dan `plugin/.claude-plugin/plugin.json` sekaligus dalam satu Release PR.

---

## GitHub Actions

Semua workflow menggunakan **SHA-pinned actions** untuk supply-chain security.
Lihat file aktual di `.github/workflows/` — jangan salin YAML dari dokumen ini.

### `ci.yml` — quality gate di setiap push/PR

Trigger: semua push ke `main` dan semua PR ke `main`.
Matrix: ubuntu, macos, windows.
Steps: `cargo fmt --check` → `cargo clippy -D warnings` → `cargo test --all` → `cargo build --all`

### `security.yml` — dependency audit

Trigger: setiap push ke `main`.
Menjalankan `cargo audit` terhadap RustSec Advisory Database.

### `release-please.yml` — otomatis versi + CHANGELOG + tag

Trigger: setiap push ke `main`.
Menganalisis Conventional Commits lalu membuat/mengupdate Release PR.
Saat Release PR di-merge: push tag → trigger `release.yml`.

Butuh secret `RELEASE_PLEASE_TOKEN` (fine-grained PAT: Contents + Pull requests write).
Fallback ke `GITHUB_TOKEN` tapi tag yang dibuat tidak akan trigger `release.yml`.

### `release.yml` — build + publish (trigger: tag `v*`)

1. Validasi tag cocok dengan versi di `Cargo.toml`
2. Build binary 5 platform (linux x86/arm64, macos x86/arm64, windows x86)
3. Publish `jira-core` ke crates.io → tunggu sparse index → publish `jira-commands`
4. Create GitHub Release dengan binaries + checksums

### Secrets yang perlu di-setup

| Secret                 | Isi                                     | Di mana set                  |
| ---------------------- | --------------------------------------- | ---------------------------- |
| `CARGO_REGISTRY_TOKEN` | API token dari crates.io                | Settings → Secrets → Actions |
| `RELEASE_PLEASE_TOKEN` | Fine-grained PAT (Contents + PRs write) | Settings → Secrets → Actions |

---

## Branch protection rules (setup manual di GitHub)

Setelah repo public, aktifkan di `Settings → Branches → Add rule` untuk branch `main`:

- [x] Require status checks to pass before merging
  - Pilih: `Check (ubuntu-latest)`, `Check (macos-latest)`, `Check (windows-latest)`
- [x] Require branches to be up to date before merging
- [x] Do not allow bypassing the above settings

---

## Development workflow

```bash
# Clone dan setup
git clone https://github.com/mulhamna/jira-commands
cd jira-commands
cargo build --all

# Develop — Claude bisa bantu edit file, jalankan test, fix issues

# Claude akan jalankan smoke test dan kasih tahu hasilnya:
cargo fmt --all -- --check && \
cargo clippy --all-targets --all-features -- -D warnings && \
cargo test --all && \
cargo build --all

# Kalau semua hijau — pemilik repo yang commit dan push:
# git add .
# git commit -m "feat: ..."
# git push origin main
```

---

## Roadmap singkat

| Phase                         | Fokus                                                                                                                              | Status   |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------- |
| 1 — Foundation                | Auth, config, HTTP client, search (cursor pagination), issue CRUD, TUI dasar                                                       | **Done** |
| 2 — Custom field & Attachment | Dynamic field introspection, semua field type, upload file/image                                                                   | **Done** |
| 3 — Bulk ops & Advanced TUI   | Bulk edit/transition, worklog CRUD, JQL builder interaktif                                                                         | **Done** |
| 4 — Power features            | Plans API, archive, raw API passthrough, Claude Code plugin                                                                        | **Done** |
| 5 — UX & Automation           | Improved `--help`, non-interactive create/update, `--json` mode, `bulk-create`, `clone`, `batch`, TUI edit actions (c/e/a/w/l/m/u) | **Done** |
| 6 — Distribution              | Homebrew tap (macOS/Linux), automated formula updates via CI/CD                                                                    | **Done** |

---

## Alur release — release-please (otomatis)

> **Jangan pernah manual bump version, manual update CHANGELOG, atau manual push tag.**
> Semua diurus oleh **release-please** via CI/CD.

### Cara kerjanya

1. Push commit ke `main` dengan **Conventional Commits** (`feat:`, `fix:`, `chore:`, dll.)
2. release-please otomatis buat/update sebuah "Release PR" yang:
   - Bump versi di `crates/jira-core/Cargo.toml`, `crates/jira/Cargo.toml`, `plugin/.claude-plugin/plugin.json`
   - Generate entry baru di `CHANGELOG.md`
3. Pemilik repo **merge** Release PR
4. release-please push tag → `release.yml` trigger:
   - Build binary 5 platform
   - Publish `jira-core` ke crates.io
   - Publish `jira-commands` ke crates.io
   - Create GitHub Release dengan binaries + checksums

### Yang perlu dilakukan sebelum push ke main

```bash
# 1. Smoke test
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all

# 2. Commit dengan Conventional Commits — ini yang release-please baca untuk bump versi
git add .
git commit -m "feat: add TUI edit actions and bulk-create command"
git push origin main

# 3. Tunggu CI hijau → release-please buat/update Release PR otomatis
# 4. Review dan merge Release PR → tag + release otomatis
```

### Aturan crates.io

- **Jangan publish manual** — publish selalu via GitHub Actions setelah Release PR di-merge
- Urutan publish: `jira-core` dulu → tunggu 90 detik → baru `jira-commands`
- Kalau publish gagal di tengah jalan: cek apakah salah satu sudah terpublish di crates.io — versi yang sama tidak bisa di-publish ulang
- CI hanya menjalankan `cargo publish --dry-run -p jira-core`. `jira-commands` tidak di-dry-run karena depend ke `jira-core` versi baru yang belum ada di crates.io

### Aturan Claude Code plugin marketplace

- Versi di `plugin/.claude-plugin/plugin.json` di-bump otomatis oleh release-please (via `extra-files` di `release-please-config.json`)
- Plugin menggunakan binary `jirac` yang sudah terinstall — pastikan README dokumentasikan `cargo install jira-commands` sebagai prerequisite
- Setiap ada skill baru atau perubahan perilaku skill, update description di `plugin/skills/<skill>/SKILL.md` dan update tabel di README.md

---

## Setiap kali update CLAUDE.md ini

Kalau ada perubahan arsitektur, aturan baru, atau temuan soal Jira API:

1. Update bagian yang relevan di dokumen ini
2. Tambahkan entry di bawah dengan format:

```
## Changelog CLAUDE.md

| Tanggal | Perubahan |
|---|---|
| 2025-04-14 | Initial version |
| YYYY-MM-DD | <deskripsi perubahan> |
```

---

## Changelog CLAUDE.md

| Tanggal    | Perubahan                                                                                                                                                                                                                                        |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 2026-04-14 | Initial version — hasil analisis jira-commands gaps + Jira API v3 terbaru                                                                                                                                                                        |
| 2026-04-14 | Hapus jira-tui sebagai crate terpisah, TUI masuk ke binary crate                                                                                                                                                                                 |
| 2026-04-14 | Tambah aturan Claude dilarang git commit/push/tag — hanya pemilik repo                                                                                                                                                                           |
| 2026-04-15 | Phase 1 selesai — auth, config, HTTP client, issue CRUD, TUI dasar                                                                                                                                                                               |
| 2026-04-15 | Hapus keyring, token disimpan di config file chmod 600 (cross-platform)                                                                                                                                                                          |
| 2026-04-15 | Tambah `auth update` untuk ganti URL/email/token tanpa login ulang                                                                                                                                                                               |
| 2026-04-15 | Fix JQL default: fallback ke `assignee = currentUser()` jika tanpa project                                                                                                                                                                       |
| 2026-04-15 | Fix Cargo.toml untuk publish crates.io: version di path dep + metadata fields                                                                                                                                                                    |
| 2026-04-15 | Publish ke crates.io trigger via tag `v*`, BUKAN push ke main                                                                                                                                                                                    |
| 2026-04-15 | Rename crate binary dari "jira" ke "jira-commands" (nama "jira" sudah dipakai di crates.io)                                                                                                                                                      |
| 2026-04-15 | Phase 2 selesai — FieldKind/FieldValue, FieldCache, attachment upload, `issue attach`, `issue fields`, create dengan dynamic field prompts                                                                                                       |
| 2026-04-15 | Phase 3 & 4 selesai — worklog CRUD, bulk transition/update, archive, JQL builder, `jira api` raw passthrough, `jira plan list`; versi bump ke 0.2.0                                                                                              |
| 2026-04-15 | Tambah Claude Code plugin di `plugin/` — 9 skills (list, view, create, transition, worklog, bulk-transition, attach, jql, api); versi bump ke 0.3.0                                                                                              |
| 2026-04-16 | Fix 204 No Content handling; fix assignee ke accountId (resolve email→accountId via /user/search, support "me" via /myself); raw_request return Option<Value>; quiet spinner/progress bar saat non-TTY; tambah CHANGELOG.md; versi bump ke 0.4.0 |
| 2026-04-16 | Fix TUI JQL search: tambah f.set_cursor_position() di render_search_bar() supaya cursor terminal muncul saat user ketik JQL; hapus fake █ cursor di footer; update plugin list-issues skill                                                      |
| 2026-04-17 | Phase B–E selesai — improved --help, non-interactive create/update, bulk-create, clone, batch, --json mode, TUI edit actions (c/e/a/w/l/m/u), CI security job, SECURITY.md                                                                       |
| 2026-04-17 | Ganti alur release: hapus manual version bump/tag, gunakan release-please via CI/CD — update CLAUDE.md, TASK.md                                                                                                                                  |
| 2026-04-17 | Phase 6 — Homebrew tap: Formula/jira-commands.rb di mulhamna/homebrew-tap, job update-homebrew di release.yml (auto-update SHA256 + version setiap release); update semua README + CLAUDE.md                                                     |
| 2026-04-18 | Phase 7 — Rename binary `jira` → `jirac`: dual binary shim (argv[0] deprecation warning), curl install script (install.sh), ecosystem disclaimer di semua README, Homebrew caveats + symlink, update release.yml artifacts                       |


<claude-mem-context>
# Memory Context

# [jira-commands] recent context, 2026-04-19 4:53am GMT+7

Legend: 🎯session 🔴bugfix 🟣feature 🔄refactor ✅change 🔵discovery ⚖️decision
Format: ID TIME TYPE TITLE
Fetch details: get_observations([IDs]) | Search: mem-search skill

Stats: 3 obs (523t read) | 8,811t work | 94% savings

### Apr 19, 2026
132 4:24a ⚖️ AGENTS.md: Symlink to CLAUDE.md Instead of Separate File
133 " 🔵 jira-commands: Both AGENTS.md and CLAUDE.md Already Exist
134 4:29a 🔵 jira-commands: AGENTS.md is a Regular File (147 bytes), Not a Symlink

Access 9k tokens of past work via get_observations([IDs]) or mem-search skill.
</claude-mem-context>