# cli-comrade — Teknik Referans > English version → [TECHNICAL.md](TECHNICAL.md) Bu belge `comrade`'ın gerçekte nasıl çalıştığını, bu depodaki kaynak koda göre doğrulanmış şekilde, paket paket anlatır. Config anahtarlarının tam listesi için [CONFIGURATION.md](CONFIGURATION.md)'a, güvenlik/tehdit modeli için [SECURITY.md](SECURITY.md)'a, faz faz geliştirme kaydı için [docs/history/phases/](history/phases/) ve [CHANGELOG.md](../CHANGELOG.md)'a bakın. ## 1. Genel bakış ve tasarım felsefesi `comrade`, kullanıcı ile terminal arasına giren, cross-platform (Linux/macOS/Windows) tek-binary bir Go CLI aracıdır. Kullanıcı ne istediğini doğal dille tarif eder; comrade bunu risk etiketli, adım adım bir plana çevirir ve aktif **davranış moduna** göre ya çalıştırır, ya onay ister ya da sadece açıklar. ### Davranış modları | Mod | Davranış | |---|---| | `auto` | comrade her adımı kendisi çalıştırır, her adımda ne yaptığını tek satırda özetler. | | `ask` | **Varsayılan.** Her adımdan önce comrade gerekçesini ve komutu gösterir, ardından dile özel bir kabul-tuşu lejantıyla sorar (aşağıya bakın). | | `info` | Hiçbir şey çalıştırılmaz. comrade nedeni ve çözümü, kopyalanabilir komutlarla açıklar. | Kaynak: `internal/engine/mode.go` (`Mode` tipi ve `ResolveMode`'un önceliği: `--auto`/`--ask`/`--info` flag'i > `COMRADE_MODE` ortam değişkeni > `general.mode` config değeri, varsayılan `ask`). **Ask-modu onay prompt'unun tuş kümesi artık tamamen i18n'lidir** — diğer her komutun çıktısının kullandığı aynı `general.language`/`COMRADE_LANG`/`LANG`/`LC_ALL`/Windows-sistem-yereli çözümleme zinciri (aşağıya bakın) üzerinden render edilir (`internal/tui/confirm.go`'nun `tr.Lang()`'i, enjekte edilen `i18n.Translator`'dan beslenir; katalog mesajları `MsgConfirmLegend` ve `MsgConfirmEditHeader`, `internal/i18n/catalog.go`): | Seçim | TR tuşu | EN tuşu | Anlamı | |---|---|---|---| | Evet | `e` (evet) | `y` (yes) | Adımı gösterildiği gibi çalıştır | | Hayır | `h` (hayır) | `n` (no) | Bu adımı atla | | Düzenle | `d` (düzenle) | `e` (edit) | Komutu yeniden onaylamadan önce satır içinde düzenle | | Açıkla | `a` (açıkla) | `x` (explain) | Bu adım için ayrıntılı bir açıklama göster, sonra tekrar sor | | Tümü | `t` (tümü) | `a` (all) | Bu adımı ve kalan her read/write/network adımını tekrar sormadan onayla — destructive/elevated adımlar yine de tek tek sorulur | Render edilen lejantlar, birebir: TR `[e]vet [h]ayır [d]üzenle [a]çıkla [t]ümü: `, EN `[y]es [n]o [e]dit [x]plain [a]ll: `. `mapKey`, kabul edilen tuş basımını **kesinlikle aktif dile göre çözer — asla iki tuş kümesinin birleşimi olarak değil**: `e` ve `a`, TR/EN genelinde tehlikeli tersinmelerle çakışır (TR `e`=Evet vs. EN `e`=Düzenle; TR `a`=Açıkla vs. EN `a`=Tümü), bu yüzden iki dilin tuşlarını aynı anda kabul etmek, bir dilde "evet" anlamına gelen bir tuş basımının diğer dilde sessizce "düzenle" veya "tümü" anlamına gelmesine izin verirdi — bu dile-özel ayrımın önlemeye çalıştığı tam da bu tehlikedir (`internal/tui/confirm.go`'nun kendi doc yorumu). ### Pazarlık edilemeyen tek kural `auto` modda bile, **effective risk sınıfı `destructive`** olan bir komut her zaman onay için durur. Bu davranış ancak config'de **hem** `safety.confirm_destructive=false` **hem de** `--yolo` flag'i birlikte verildiğinde kapanır — ve o çalıştırmada gerçekten bir şeyi atlatıp atlatmadığından bağımsız olarak, **her** `--yolo` kullanımı kırmızı bir uyarı basar (`internal/i18n`'deki `flag_yolo`/`yolo_warning` mesajları; `internal/engine`'in mod-dağıtım döngüsünde, `internal/safety.Engine`'in verdiğinin üstüne uygulanır). ### Dil tutumu comrade'ın **arayüzü** — binary'nin kendi ürettiği her kullanıcıya görünen metin — tam olarak iki dilde gelir, Türkçe ve İngilizce, `internal/i18n` kataloğu üzerinden (`internal/i18n/catalog.go`'daki `catalogEN`/`catalogTR`, her string için bir `MessageID`, `TestCatalogsCoverIdenticalKeys` testiyle iki katalog birbirinden asla kopmayacak şekilde tutulur). Arayüz dili şu sırayla çözülür: açık `general.language` config değeri (`tr`/`en`) kesin olarak kazanır; `auto` (varsayılan) sırayla `COMRADE_LANG` ortam değişkenine > `LANG`/`LC_ALL`'a (glibc tarzı locale önek eşleşmesi) > **Windows sistem yereline** (yalnızca Windows'ta, `GetUserDefaultLocaleName` ile, ör. `tr-TR`; diğer platformlarda her zaman boş, çünkü orada `LANG`/`LC_ALL` zaten işletim sistemi yerel mekanizmasının kendisidir) > İngilizce varsayılana düşer (`internal/i18n/lang.go`, `ResolveLanguage`; uygulama detayı için §10'a bakın). **Kullanıcının doğal dilde yazdığı istek** ise Türkçe veya İngilizce ile sınırlı değildir — kullanıcının yazdığı herhangi bir serbest metindir ve yapılandırılmış LLM bunu, o modelin anladığı hangi dil(ler) olursa olsun yorumlar. comrade'ın kontrol ettiği şey, LLM'in yapılandırılmış yanıtındaki kendi yazılı alanlarının dilidir (bir planın `summary`'si ve her adımın `rationale`'ı, bir açıklamanın özeti/parçaları, bir tanının açıklaması): çözülen arayüz dili Türkçe olduğunda comrade system prompt'a bir dil talimatı ekler — bkz. `internal/engine/prompts/plan_lang_tr.txt`, `explain_lang_tr.txt`, `diagnose_lang_tr.txt` — modele bu düz-metin alanları Türkçe yazmasını, ama JSON alan adlarını, `risk` enum değerlerini ve komutun kendisini (komutlar dilden bağımsızdır) değiştirmeden bırakmasını söyler. İngilizce için böyle bir ek eklenmez; temel system prompt'lar (`plan_system.txt`, `explain_system.txt`, `diagnose_system.txt`) varsayılan olarak zaten İngilizcedir. Dolayısıyla: **comrade'ı "sadece Türkçe/İngilizce" diye tanımlamayın** — comrade'ı, TR/EN arayüze sahip, bu aynı TR/EN çözümlemesini LLM'in düz-metin çıktı diline de uygulayan, ama kullanıcının seçtiği modelin desteklediği herhangi bir dilde doğal-dil isteği yazabildiği bir araç olarak tanımlayın. ## 2. Uçtan uca çalışma prensibi ```mermaid flowchart TD A[Kullanıcı girdisi: comrade fix / do / explain / chat] --> B[internal/context: Collector.Collect] B --> C[internal/redact: her giden payload'a Redactor.Apply] C --> D[internal/engine/prompts: system prompt + gerekiyorsa _lang_tr eki] D --> E[internal/llm.Client: connector fallback zinciri] E --> F[internal/llm/parse.go: markdown fence temizliği, JSON çıkarma+doğrulama] F --> G[internal/safety.Engine: denylist + escalation, LLM'in beyan ettiği riskten bağımsız] G --> H{Mod: internal/engine dağıtımı} H -->|auto| I[internal/executor: çalıştır, sadece destructive'te onay iste] H -->|ask| J[internal/tui: her adım için onay prompt'u] H -->|info| K[Sadece planı yazdır, hiçbir şey çalıştırma] I --> L[internal/audit: JSONL kaydı ekle] J --> L ``` 1. **Bağlam toplama** (`internal/context`) — OS/mimari (`runtime.GOOS`), shell adı/versiyonu, çalışma dizini, son başarısız komutu (shell hook'unun yazdığı `last_command.json`'dan — bkz. §9), exit code'u ve yakalanan stderr/stdout kuyruğunu, tespit edilen paket yöneticisi/yöneticilerini (`LookPath` ile apt/dnf/pacman/brew/winget/ scoop/choco), ve — yalnızca `context.send_history`/ `context.send_env_names` config'i açıkça etkinleştirilmişse — son shell geçmişini ve ortam değişkeni **isimlerini** (asla değerlerini) toplar. 2. **Redaction** (`internal/redact`) — bir LLM çağrısı için hazırlanan her istek payload'ı, süreçten çıkmadan önce `Redactor.Apply`'dan geçer; tam pattern listesi için §8'e bakın. Bu, `internal/llm.Client.Complete`/`Stream` içinde sabitlenmiş bir adım olarak bağlanmıştır (`redactPayload`) — her çağrı noktasının hatırlaması gereken opsiyonel bir adım değildir. 3. **Prompt oluşturma** (`internal/engine/prompts`, Go `embed`) — `plan_system.txt` (`do`/`fix` için), `explain_system.txt` (`explain` için) veya `diagnose_system.txt` (`fix`'in tanı adımı için), uygun olduğunda §1'de anlatılan Türkçe ek, ve tanı için birkaç örnekli bir few-shot dosyası (`diagnose_fewshot.txt`). 4. **Fallback'li provider çağrısı** (`internal/llm.Client`) — `llm.provider + "/" + llm.model` ilk deneme olarak, ardından `llm.fallback`'teki her girdi sırayla (`client.go`'daki `New`). `Complete`/`Stream`, her denemeyi bir per-attempt timeout ile (`llm.timeout_seconds`, varsayılan 60sn) sırayla dener; bir auth reddi (401/403, `ErrAuthRejected`) dışındaki her hata bir sonraki denemeye geçer, bir auth reddi zinciri hemen durdurur, ve — her deneme transport seviyesinde (`ErrOffline`) başarısız olduysa ve hiçbir deneme zaten `ollama` değilse — son hata, offline kullanım için `llm.fallback`'e `ollama` eklemeyi önerir. 5. **Yapılandırılmış JSON ayrıştırma + doğrulama** (`internal/llm/parse.go`) — modelin eklemiş olabileceği Markdown code-fence sarmalamasını temizler, tek bir üst-seviye JSON nesnesini çıkarır, ve çağıranın `CompletionRequest.RequiredFields`'ta beyan ettiği her alanın mevcut ve boş olmadığını doğrular — hepsi tek bir paylaşılan kod yolundan, böylece her komutun JSON işleme davranışı tutarlıdır. 6. **Yerel güvenlik ikinci kontrolü** (`internal/safety.Engine.Evaluate`) — LLM'in beyan ettiği risk etiketini asla bir taban değerin ötesinde güvenmez. Komutu bir normalizer/tokenizer'dan geçirir (`tokenize.go`), ardından: (a) yerleşik denylist — herhangi bir eşleşme, mod veya beyan edilen risk ne olursa olsun koşulsuz `Block`'tur; (b) kullanıcının `safety.denylist_extra` regex'leri, aynı etkiyle; (c) yalnızca effective risk sınıfını yükseltebilen, asla düşüremeyen sabit bir escalation kural seti. Somut kural seti için §8'e bakın. 7. **Mod tabanlı yürütme döngüsü** (`internal/engine`, `internal/executor`) — §1'deki mod tablosuna göre dağıtır. Yürütmenin kendisi Windows dışında `sh -c `, Windows'ta `powershell -NoProfile -Command ` çalıştırır (`internal/executor/executor.go`, `buildCommand`), bu seçim bir build tag yerine kurulum anında `runtime.GOOS`'a göre yapılır (`New`) — böylece her üç platformun mantığı tek bir binary'den test edilebilir (CLAUDE.md'nin platform-dallanma kuralı gereği). Tek istisna, timeout/iptal durumunda process-group öldürme: `Setpgid`/ `syscall.Kill` (Unix) ile `Process.Kill` (Windows) kendileri platforma özel syscall'lardır ve diğer `GOOS`'ta derlenemezler, bu yüzden bu dar parça bunun yerine build-tag'li bir `executor_unix.go`/`executor_windows.go` çiftinde yaşar (bu kod tabanındaki diğer böyle çift için §10'a bakın). 8. **Audit log** (`internal/audit`) — çalıştırılan her adım tek bir JSONL satırı olarak eklenir: `timestamp`, `request` (orijinal serbest metin istek), `command`, `risk`, `mode`, `exit_code`, `duration_ms`. `comrade history` ile geri okunur. ## 3. Mimari / paket haritası ``` cmd/comrade/ main() — internal/cli.NewRootCmd'i kurar ve Execute'u çağırır internal/ cli/ cobra alt komutları, flag bağlama, config/runtime bağlama, i18n-help bağlama, renk kararı (color.go), bekleme spinner'ı (spinner.go), çevrilmiş argüman-sayısı/bilinmeyen-alt-komut doğrulayıcıları (argvalidation.go), shell-tamamlama ValidArgsFunction kablolaması (completion.go) config/ viper yükleme, şema, OS'e özel yol çözümleme, doğrulama context/ ortam/son-komut/geçmiş/paket-yöneticisi toplama redact/ sır maskeleme regex boru hattı, her giden LLM payload'ına uygulanır engine/ mod dağıtımı, plan/explain/diagnose üretimi, gömülü prompt'lar, güvenlik-kontrollü adım koşucusu executor/ sh -c / powershell -Command process çalıştırma, OS'e özel process-group yönetimi safety/ risk sınıflandırma, denylist, escalation kuralları, Decision tipi audit/ JSONL yürütme logu + comrade history'nin okuyucusu llm/ Provider arayüzü, 4 connector, Client fallback zinciri, JSON ayrıştırma/doğrulama, SSE streaming i18n/ TR/EN mesaj kataloğu, MessageID disiplini, dil çözümleme secrets/ keychain öncelikli (go-keyring) / 0600-dosya-fallback API key depolama shellinit/ comrade init'in shell-başına snippet üretimi ve rc-dosyası blok yönetimi tui/ bubbletea/lipgloss onay prompt'u ve durum gösterimi update/ comrade upgrade: GitHub release sorgulama, checksum-doğrulamalı indirme, atomik kendi-kendini-değiştirme scripts/ install.sh / install.ps1 (checksum-doğrulamalı curl/iwr kurulum betikleri) docs/ CONFIGURATION.md, SECURITY.md, history/phases/ (FAZ-00..11 geliştirme kaydı), bu dosya third_party/ vendored atotto-clipboard fork'u (bkz. §4) ``` `internal/` altındaki her önemsiz olmayan paket, paket-seviyesi tasarım yorumunu taşıyan bir `doc.go` ile başlar — bir pakette yön bulmaya bu dosyayı okuyarak başlayın. ## 4. Teknoloji yığını | Konu | Seçim | Neden (depodaki dokümantasyona göre) | |---|---|---| | Dil / toolchain | Go 1.25 (modül), toolchain `go1.26.5` (`go.mod`) | Tek statik, cross-compile edilebilir binary | | CLI framework | `spf13/cobra` v1.10.2 | Alt komut ağacı, flag ayrıştırma, help üretimi | | Config | `spf13/viper` v1.21.0 | TOML dosya yükleme/birleştirme; OS'e özel yol çözümlemesi viper'ın değil comrade'ın kendisinindir (`internal/config/paths.go`) | | TUI | `charm.land/bubbletea/v2` v2.0.8 + `charm.land/bubbles/v2` + `charm.land/lipgloss/v2` | Onay prompt'ları, chat girdisi, renkli durum çıktısı | | Keychain | `github.com/zalando/go-keyring` v0.2.8 | macOS Keychain / Windows Credential Manager / Linux Secret Service, hiçbir keychain backend'i mevcut olmadığında 0600 obfuscated-dosya fallback'i ile (`internal/secrets`) | | HTTP | sadece stdlib `net/http` | Provider SDK'sı yok — `internal/llm`'in dört connector'ı elle yazılmış ham REST istemcileridir, bağımlılık yüzeyini minimumda tutmak için (CLAUDE.md) | | Release | `goreleaser/v2` v2.16.0 (`Makefile`'da pinlenmiş) | Cross-platform arşivler, `.deb`/`.rpm` (nfpm), Homebrew Cask, Scoop bucket, winget manifest — bkz. §11 | | Test | stdlib `testing` + `stretchr/testify` v1.11.1 | LLM connector'ları gerçek ağa asla dokunmadan `httptest` sunucularına karşı test edilir | ### Vendored fork: `third_party/atotto-clipboard` `go.mod`'da bir `replace github.com/atotto/clipboard => ./third_party/atotto-clipboard` satırı var. Upstream `atotto/clipboard` v0.1.4'ün Unix build'i, **paket seviyesinde bir `init()` içinde koşulsuz olarak** beşe kadar sıralı `exec.LookPath` PATH taraması yapıyor — bunu `bubbles/v2/textinput`'u import eden her `comrade` çağrısı ödüyor (yani onay prompt'u ve `comrade chat` bunu dolaylı olarak import ettiği için, `--version`/`--help` dahil, hemen hemen her çağrı). Çok sayıda girişi olan bir PATH'te (WSL2 shell'inde 100+ gözlemlendi) bu, çağrı başına yüzlerce milisaniyeye mal oluyordu. Vendored fork'un tek değişikliği, aynı probu `init()`'ten, ilk **gerçek** clipboard kullanımında tetiklenen bir `sync.Once`'a ertelemek — bkz. `third_party/atotto-clipboard/clipboard_unix.go`'nun doc yorumu, `docs/history/phases/FAZ-11.md`, ve `KNOWN_LIMITATIONS.md`. Bunun yerine eşdeğer bir düzeltme alacak daha yeni bir upstream release yok (v0.1.4 en sonuncusu). **Sonuç:** modül yerel bir dosya sistemi yoluna `replace` edildiği için, `go install github.com/firatkutay/cli-comrade/cmd/comrade@` son kullanıcı için **çalışmaz** — Go'nun modül çözümlemesi bir `replace ... => ./göreli/yol` direktifini bir `go install` modül sınırının ötesinde takip edemez. Kurulum, yayınlanmış bir binary üzerinden (`scripts/install.sh` / `install.ps1`), bir paket yöneticisi üzerinden (Homebrew Cask / Scoop / winget / `.deb`/`.rpm`), veya tam bir yerel clone + `make build` üzerinden yapılmalıdır. ## 5. Komut referansı Global flag'ler, root komutta, `do`'da ve `fix`'te mevcuttur (`internal/cli/flags.go`'nun `addExecutionFlags`'i tarafından kaydedilir; `explain`, `chat`, `config`, `history`, `init`, `auth`, `upgrade`'de yoktur): | Flag | Etki | |---|---| | `--auto` | Bu çağrı için `auto` modu zorla (COMRADE_MODE/config'i geçersiz kılar) | | `--ask` | Bu çağrı için `ask` modu zorla | | `--info` | Bu çağrı için `info` modu zorla | | `--dry-run` | Üretilen planı çalıştırmadan yazdır | | `--yolo` | **Tehlikeli.** `auto` modda destructive/elevated onayını atlar, ama yalnızca config'de `safety.confirm_destructive`/`confirm_elevated` da kapalıysa. Verildiğinde her zaman kırmızı bir uyarı basar. | | `-h`, `--help` | Standart cobra help | | `-v`, `--version` | `comrade version ` yazdırır ve çıkar | `--auto`/`--ask`/`--info` birbirini dışlar; birden fazlasının verilmesi bir kullanım hatasıdır (`modeFlagValue`, `flags.go`). **Help çıktısı gruplanmıştır, bir root Examples bölümü vardır, ve renklidir** (`internal/cli/help.go`). Herhangi bir seviyede `--help`, komutları üç i18n'li grup başlığı altında listeler — Core (`do`/`fix`/`explain`/`chat`), Setup (`auth`/`init`/`config`), Info (`history`/`upgrade`) — artı `hook`/`help` için cobra'nın varsayılan "Additional Commands:" kovası (otomatik üretilen `completion` komutu artık help'ten tamamen **gizlidir** — `cobra.CompletionOptions{HiddenDefaultCmd: true}` ile; `comrade completion bash` vb. hâlâ çalışır, sadece reklamı yapılmaz). Cobra'nın kendi yapısal bölüm etiketleri (`Usage:`/`Aliases:`/`Examples:`/`Available Commands:`/ `Additional Commands:`/`Flags:`/`Global Flags:`/`Additional help topics:`, artı sondaki "Use `\"...\"` for more information..." satırı) de artık çevrilidir — `usageTemplateFor(tr)` ile: cobra v1.10.2'nin kendi export edilmemiş `defaultUsageTemplate`'inin, yalnızca o sekiz etiket `tr.T(...)` çağrılarıyla değiştirilmiş, birebir yapısal bir kopyası; `root.SetUsageTemplate` ile tüm ağaca kurulur. Bu bilinçli, belgelenmiş bir sürüm-eşleşmesi riskidir (cobra bu şablonu programatik türetmenin hiçbir yolunu sunmuyor): `TestUsageTemplateForMatchesCobraDefaultShapeInEnglish` (`help_test.go`), `usageTemplateFor(EN)`'in temsili bir komut ağacı için cobra'nın dokunulmamış varsayılanıyla birebir aynı çıktı ürettiğini kanıtlar, bu yüzden gelecekteki bir cobra şablon değişikliği sessizce değil yüksek sesle bozulur — ve `go.mod` cobra'yı tam bir sürüme sabitlediğinden, bu yalnızca bilinçli bir yükseltmede bayatlayabilir. Root'un kendi `--help`'i ayrıca çevrilmiş bir Examples bloğu yazdırır (`root.Example`, `MsgHelpExamplesRoot`). Renk etkinken (aşağıya bakın), bölüm/grup başlıkları kalın pastel lavantada, komut adları pastel cyan/tealde, ve flag adları (tek harfli kısaltmalar dahil) pastel şeftalide render edilir — lipgloss'un canlı-terminal-sorgusu tabanlı adaptive renk yerine, her `--help`/`--version`'da bloklayıcı bir terminal sorgusu ödememek için sabit ANSI256 kodları (§13'ün vendored clipboard fork'u için ele aldığı aynı cold-start kaygısı). **Renk tam olarak tek bir yerde kararlaştırılır**: `internal/cli.resolveColorEnabled` (`internal/cli/color.go`). `general.color=false` her zaman son sözdür — açık bir opt-out. Aksi halde, hedef writer üzerinde `colorprofile.Detect` çağrı-başına karar verir: TTY olmayan/ pipe'lanmış bir çalıştırma için varsayılan düz çıktı, [NO_COLOR](https://no-color.org)'ı (koşulsuz kapatır) ve [CLICOLOR_FORCE=1](https://bixense.com/clicolors/)'i (TTY olmasa bile rengi zorlar — etkileşimli olmayan `--help` çıktı kontrollerinin kullandığı) onurlandırarak. Windows'ta, renk açık çözüldüğünde, `lipgloss.EnableLegacyWindowsANSI`, konsolu `ENABLE_VIRTUAL_TERMINAL_PROCESSING`'e sokar, böylece eski `conhost.exe` (hâlâ Windows PowerShell 5.1'in tipik olarak çalıştığı yer) kendisine verilen ANSI'yi gerçekten yorumlar — başka yerlerde (Windows Terminal/PowerShell 7 içindeyken dahil) bir no-op'tur. Renk yeteneği olan her çağrı noktası (help, aşağıdaki spinner, chat, `do`/`fix`/`explain`, ask-modu prompt'u) aynı bu fonksiyondan geçer, bu yüzden ANSI'nin yazılıp yazılmayacağına karar veren tam olarak tek bir yer vardır. `chatModel` (`internal/cli/chatmodel.go`), `colorEnabled`'ı aynı `resolveColorEnabled` kararından taşır — `chat.go`'da bir kez hesaplanır ve `newChatModel`'e aktarılır. Bunu bağlamak, bu paragrafın anlattığı tam "tek karar noktası" mimarisindeki önceden var olan bir sızıntıyı kapattı: `bubbles/v2/textinput`'in kendi `New()`'i, girdi prompt'unun stilini koşulsuz olarak `DefaultDarkStyles()`'a ayarlıyordu — bu da `NO_COLOR`, TTY olma durumu, veya `general.color=false`'dan bağımsız olarak `\x1b[37m` yayıyordu. `setChatInputPromptStyle` artık prompt'un stilini açıkça ayarlıyor — etkinken pastel-sarı stile, aksi halde gerçekten boş bir `lipgloss.Style{}`'a — bu yüzden rengi kapalı bir chat oturumu bit-bit aynı düz çıktıdır, yalnızca "sarı değil ama hâlâ renkli" değil. Sonraki bir inceleme, aynı sızıntıyı **ikinci** bir yüzeyde buldu — `internal/tui/confirm.go`'nun ask-modu düzenleme-modu (`[e]dit`/`[d]üzenle`) textinput prompt'u — aynı şekilde kapatıldı: `internal/tui/styles.go`'nun yeni `editPromptStyle(colorEnabled)`'i, hem `Focused` hem `Blurred` `Prompt` stiline uygulanır. Pastel-sarı değeri, bir renk paketi üzerinden paylaşılmak yerine bilinçli olarak `tui.PromptYellow` olarak **tekrarlanmıştır** (`internal/tui`, `internal/cli`'yi import edemez — bağımlılık oku yalnızca ters yönde çalışır), `internal/cli/color_test.go`'nun `TestPromptYellowMatchesTUIPackage`'ı ile drift'e karşı korunur — bu test `paletteYellow == tui.PromptYellow`'u doğrular ve ikisinden biri bağımsız değişirse başarısız olur. **Açık kalan madde**: sanal imlecin kendi ters-video render'ı (`\x1b[7;37m`) bu iki textinput'ta da hâlâ koşulsuzdur — bu turun kapsamı bilinçli olarak dışında bırakıldı, `docs/history/PROGRESS.md`'de takip ediliyor, henüz düzeltilmedi. **Bir bekleme spinner'ı** (`internal/cli/spinner.go`), canlı bir bubbletea programının DIŞINDAKİ her bloklayan LLM çağrısı sırasında (`do`/`fix`'in planlama ve tanı adımları, `explain`) stderr üzerinde animasyon yapar — `bubbles/v2/spinner`'ın frame verisinden ödünç alınan bir braille frame seti (tam `tea.Model`'i değil, çünkü bu çağrı noktalarının hiçbiri o anda aktif bir bubbletea programı içinde çalışmaz), i18n'li "düşünüyor…" metniyle etiketlenir (`i18n.MsgSpinnerThinking`). Aynı `resolveColorEnabled` kararıyla (stderr'e karşı değerlendirilir) yönlendirilir, bu yüzden renk kapalıyken (TTY değil, `general.color=false`, `NO_COLOR`, veya `CLICOLOR_FORCE` gerekli ama yoksa) tamamen bir no-op'tur — hiçbir goroutine başlatılmaz, hiçbir şey yazılmaz. Durdurma fonksiyonu, dönmeden önce spinner satırının tamamen temizlendiğinden (sabit genişlikte bir üstüne-yazma değil, bir ANSI erase-in-line) her zaman emin olur, bu da çağıranın bir sonraki yazdırdığı şeyin asla aynı satıra denk gelmemesini garanti eder. Chat tek istisnadır: zaten canlı bir bubbletea programı sahipliğinde olduğundan, bu stderr spinner'ı yerine kendi model-içi spinner'ını render eder — aşağıdaki "`comrade chat` içinde" bölümüne bakın. ### `comrade` (çıplak, alt komutsuz) Versiyon banner'ını, ardından cobra help'i yazdırır. `comrade ` — hiçbir alt komut adıyla eşleşmeyen metin — `comrade do ` ile aynı mantığa dağıtılır (root'un `Args: cobra.ArbitraryArgs` + `RunE`'si), böylece örn. `comrade docker kur` `do` yazmaya gerek kalmadan "çalışır". Gerçek bir alt komut yazım hatası bu yüzden "şunu mu demek istediniz" önerisiyle reddedilmez — bunun yerine serbest-metin olarak dağıtılır, FAZ 6'nın bilinçli bir UX tercihidir. ``` comrade docker'ı kur comrade --auto şu portu kim kullanıyor bul ve kapat ``` ### `comrade do ` Yukarıdaki serbest-metin dağıtımının açık formu. İstek için bir plan üretir ve aktif moda göre çalıştırır. ``` comrade do "8080 portunu kullanan process'i bul ve durdur" comrade do --dry-run "install docker" ``` ### `comrade fix [-- komut...]` Son başarısız komutu (`last_command.json`'dan okunur, bkz. §9) veya açıkça verilen bir komutu tanılar, ardından moda göre bir düzeltme önerir ve — gerekirse — çalıştırır. | Flag | Etki | |---|---| | `--rerun` | Tanılamadan önce son kaydedilen komutu yeniden çalıştırarak taze stderr/stdout yakalar | ``` comrade fix comrade fix --rerun comrade fix -- npm install ``` ### `comrade explain ` Bir komutu **hiçbir zaman çalıştırmadan** flag flag açıklar — bu komutun bağımlılık grafiğinde bir `executor` bile oluşturulmaz. İki katman: (1) diğer her komutun kullandığı aynı yerel `safety.Engine`, komut destructive veya denylist'te ise önce göze çarpan bir uyarı basar; (2) `engine.Explainer` LLM'den sade dilde bir özet, flag flag dökümü ve kendi risk notunu ister. `explain`, `DisableFlagParsing: true` ayarını kullanır — açıklanan komut metni sıklıkla bir tire ile başlar (`-rf`, `-la`) ve comrade'ın kendi flag'i olarak ayrıştırılmamalıdır. Bu, cobra'nın kendi flag ayrıştırıcısının (normalde `-h`/`--help`'i yakalayan ve Args doğrulamasını çalıştıran) bu komut için hiç çalışmadığı anlamına da geldiğinden, `explain`'in `RunE`'si bunu kendisi, açıkça ele alır: hiç argümansız veya tam olarak `-h`/`--help` ile çağrıldığında, bir LLM çağrısı yapmak yerine bu komutun help'ini gösterir (`cmd.Help()`); başka türlü argümansız çağrıldığında, cobra'nın genel İngilizce `MinimumNArgs` mesajı yerine i18n'li bir kullanım hatası (`MsgExplainUsageError`) döner. Tam olarak `-h` veya `--help` olan bir komut dizesini birebir açıklamak için `comrade explain -- -h` kaçış kapısını kullanın — baştaki `--`, `explain`'in kendisi tarafından kaldırılır (cobra bunu burada asla kaldırmaz, çünkü `DisableFlagParsing` argüman token'larını dokunulmadan bırakır) ve ondan sonraki her şey, ne olursa olsun birebir açıklanır. ``` comrade explain "git rebase -i HEAD~5" ``` ### `comrade chat` Etkileşimli, bağlamı koruyan chat oturumu (bubbletea TUI). `-h` dışında kendi flag'i yok. **Etkileşimli bir TTY gerektirir.** bubbletea'nın kendisi gerçek bir terminal gerektirir ve aksi halde TTY olmayan stdin'de asılı kalır; `comrade chat` artık bunu önceden kontrol eder (`requireInteractiveTTY`, `internal/cli/runtime.go`) ve stdin pipe'landığında veya yönlendiril- diğinde asılı kalmak yerine dostane, i18n'li bir hata döner (`MsgChatRequiresTTY`). ``` comrade chat ``` Oturum içinde (`internal/cli/chatdispatch.go`'nun slash-tarzı komutlarına göre — otoriter liste için kataloğun `chat_help` girdisine bakın): mod değiştirme, bağlamı temizleme, transkripti kaydetme, ve oturumdan çıkmadan `do`-tarzı bir istek gönderme. **Renk etkinken transkript stillendirilir** (`internal/cli/color.go`'nun isimli palet sabitleri, `internal/cli/chatmodel.go`). Kullanıcının kendi yankılanan transkript satırları pastel gri render edilir (ANSI256 `245`); `/help`'in render edilen slash-komut listesi, baştaki `/xxx` token'larını pastel mavi renklendirir (`111`); girdi prompt'unun `>`'ı pastel sarı render edilir (`222`) — hem canlı `bubbles/v2/textinput` prompt'u hem de transkriptin kendi yankılanan `> ` öneki, ikisi görsel olarak eşleşsin diye. Asistan yanıtları bilinçli olarak stilsiz bırakılır. Renk kapalıyken, chat çıktısı bit-bit aynı düz metindir — bu stillerin her biri bir no-op'tur, farklı bir renk değil. **Gönderim asenkron, hiçbir zaman `Update` içinde senkron değil.** Enter'a basmak (`internal/cli/chatmodel.go`) satırı hemen ekrana yansıtır, ardından `chatController.dispatchChatLine`'a — hem düz-metin turunun hem `/do`'nun ardındaki AYNI saf mantık — `runChatTurnCmd` üzerinden verir: bubbletea'nın kendi komut goroutine'inde çalışan ve sonucu bir `chatTurnDoneMsg` ile geri bildiren bir `tea.Cmd`. Bu `/do` için de geçerli: ikinci, paralel bir mekanizma DEĞİL, yalnızca daha ağır bir gövdeli (tüm güvenlik-kapılı plan+execute pipeline'ı, terminali kendi iç içe onay programının etrafında öncekiyle birebir aynı şekilde bırakıp geri alarak) aynı gönderim çağrısı. Bir tur beklerken, model-içi bir spinner (`charm.land/bubbles/v2/spinner`, yukarıdaki stderr spinner'ıyla birebir aynı stilde — aynı frame seti, aynı renk) transkriptin altında ve girdi satırının üstünde render edilir, aynı `i18n.MsgSpinnerThinking` metniyle etiketlenir; mevcut tur sonuçlanana kadar ikinci bir Enter yok sayılır, ama Ctrl-C her zaman koşulsuz anında çıkar. `chatTurn`'ün `CompletionRequest`'i `cfg.LLM.MaxTokens`'ı taşır — bu kod tabanındaki HER `Complete` çağrı noktası bunu ayarlar (özellikle Anthropic Messages API'de zorunlu bir alan; `0`/ayarsız bir değer gerçek API tarafından 400 ile reddedilir). ### `comrade config ` | Alt komut | Amaç | |---|---| | `get ` | Bir config anahtarının etkin değerini yazdır | | `set ` | Bir anahtarı doğrula ve kalıcı hale getir (`config models`'ın kullandığı aynı `SetAndSave` yolu) | | `list` | Her anahtarı, etkin değerini ve kaynağını (`env`/`file`/`default`) listele | | `edit` | Config dosyasını `$EDITOR`'da aç | | `path` | Config dosyasının çözümlenmiş yolunu yazdır | | `models` | **O an yapılandırılmış** provider için mevcut modelleri listele ve etkileşimli olarak birini seç, seçimi `llm.model`'e kalıcı hale getir | `config` (üst komut) Runnable'dır — `RunE: cmd.Help()` — YALNIZCA kendi `translatedUnknownSubcommand` `Args` doğrulayıcısının çalışabilmesi için (cobra'nın `execute()`'u, `Args` hiç kontrol edilmeden önce, Runnable OLMAYAN bir komutun HERHANGİ bir çağrısı için `flag.ErrHelp` döner); dürüst yan etki: `comrade config --help` artık öncekinden farklı olarak bir `comrade config [flags]` "Usage:" satırı da gösteriyor (cobra'nın şablonu Runnable HERHANGİ bir komut için bunu render eder — kozmetik, davranış değişikliği değil). Fonksiyonel kazanç: `comrade config bogus` eskiden sessizce help yazdırıp `0` ile çıkıyordu; artık çevrilmiş, eyleme geçirilebilir bir `MsgUnknownSubcommandError` (gerçek her alt komutu, `cmd.Commands()`'tan CANLI olarak adlandırarak) sıfır olmayan bir çıkış koduyla dönüyor. Tam mekanizma için §10'un "Çevrilmiş argüman-sayısı ve bilinmeyen-alt-komut kullanım hataları" bölümüne bakın. `config set`, `explain` gibi `DisableFlagParsing: true` ayarını kullanır (bir değer kendisi bir flag gibi görünebilir, ör. `comrade config set safety.denylist_extra --foo`) ve bu yüzden benzer şekilde `-h`/`--help`'i ve yanlış argüman sayısını kendisi ele alır — cobra'nın ham İngilizce `ExactArgs(2)` mesajı yerine i18n'li bir kullanım hatası (`MsgConfigSetUsageError`). **Bilinen kısıtlama**: `config set`, `config.toml`'ı `viper.WriteConfigAs` üzerinden yeniden yazar (`SetAndSave`, `internal/config/loader.go`), ki bu elle yazılmış `#` yorumlarını korumaz — bir `config set` çalıştırmasından önce dosyada var olan yorumlar, çalıştırmadan sonra kaybolur; anahtar/değerlerin kendisi etkilenmez. Bu, önceden var olan, belgelenmiş, bilinçli olarak ertelenmiş bir kısıtlamadır (bkz. `docs/history/PROGRESS.md`), yeni bir regresyon değil. ``` comrade config path comrade config get llm.provider comrade config set general.mode auto comrade config models ``` Tam anahtar referansı: [CONFIGURATION.md](CONFIGURATION.md). ### `comrade auth ` Saklanan provider API key'lerini yönetir (keychain öncelikli, dosya fallback'i — §7). Yukarıdaki `config` gibi, `auth` (üst komut) da YALNIZCA kendi `translatedUnknownSubcommand` doğrulayıcısının çalışabilmesi için Runnable'dır — aynı kozmetik `comrade auth [flags]` "Usage:" satırı eklemesi, aynı `comrade auth bogus` sessizce-help-0-ile-çık → çevrilmiş-hata düzeltmesi; bkz. §10. | Alt komut | Amaç | |---|---| | `login ` | `provider` için bir key sakla, ardından doğrulamak için küçük bir test completion'ı gönder | | `logout ` | Saklanan bir key'i kaldır | | `status` | Hangi provider'ların saklanan (keychain/dosya) veya ortam değişkeni key'i olduğunu göster | ``` comrade auth login anthropic comrade auth status ``` **`login`, etkileşimli bir TTY gerektirir** (`chat`'in kullandığı aynı `requireInteractiveTTY` kontrolü, `MsgAuthLoginRequiresTTY`) — pipe'lanmış/yönlendirilmiş/betiklenmiş bir çağrı, `x/term.ReadPassword`'ın kendi ham platform errno'su ("inappropriate ioctl for device", Unix'te) — hiçbir eyleme geçirilebilir neden adlandırmayan — yerine önceden dostane, i18n'li bir hata alır. Girilen key, **hiçbir şey yazılmadan önce ping'lenir**: `pingProvider`, key'i doğrudan, bellekte doğrular, asla store üzerinden değil — bu yüzden reddedilirse geri alınacak hiçbir şey yoktur. Provider key'i kendisi reddederse (`llm.ErrAuthRejected`, bir 401/403), `login` gerçek bir komut hatası döner ve **store'a hiç dokunulmaz** — ne yazma, ne silme, bozuk olduğu bilinen bir key'in saklı kaldığı bir pencere yok. Diğer her sonuç — başarı, veya bir ret olmayan ping başarısızlığı (ağ, timeout, geçici bir 5xx) — ping'den sonra tam olarak bir kez `store.Set`'i çağırır, bu da orijinal "key yalnızca doğrulanamıyorsa bile sakla" davranışını korur: çevrimdışı bir kullanıcı, veya geçici bir provider-taraflı hata yaşayan biri, doğru olduğuna inandığı bir key'i kaydetmekten engellenmez. ### `comrade history` `internal/audit`'in JSONL logunu okur ve son kayıtları yazdırır. | Flag | Etki | |---|---| | `--limit ` | Gösterilecek en fazla son kayıt sayısı (varsayılan 20) | | `--json` | Her kaydı tablo yerine satır başına bir JSON nesnesi olarak yazdır | ``` comrade history --limit 50 --json ``` ### `comrade init [bash|zsh|fish|powershell]` Hedef shell'in rc/profile dosyasına shell entegrasyon bloğunu kurar (veya kaldırır). Hook'un çalışma zamanında ne yaptığı için §9'a bakın. **Windows'ta `powershell`, kurulu her PowerShell varyantını bağımsız olarak hedefler** — Windows PowerShell 5.1 (`powershell.exe`) ve PowerShell 7 (`pwsh.exe`), hangisi gerçekten mevcutsa (`internal/shellinit/psprofiles.go`'nun `ResolvePowerShellProfiles`'ı). Bulunan her varyantın kendi `$PROFILE`'ı sorgulanır ve hook, `comrade init`'in diğer her shell için kullandığı aynı idempotent blok-marker mekanizmasıyla oraya kurulur/güncellenir/kaldırılır — her profil için bir rapor satırı (varyant etiketi + durum + yol). Yalnızca bir varyant kuruluysa sorun yok; hiçbiri bulunamazsa hata verir. Birden fazla profilin yazma gerektirdiği durumda, hepsini kapsayan **tek bir birleşik onay** sorulur — profil başına bir prompt değil — ve `--yes` bunu her zamanki gibi atlar. `--remove`, bunu profil başına yansıtır; `--print` değişmemiştir (kaç profil olursa olsun her zaman ham snippet metnini yazdırır). Windows dışında, `powershell` yine yalnızca `pwsh`'a çözülür, önceden olduğu gibi. | Flag | Etki | |---|---| | `--print` | Sadece snippet'i yazdır; hiçbir dosya değişikliği yapma | | `--remove` | cli-comrade bloğunu rc/profile dosyasından/dosyalarından kaldır | | `-y`, `--yes` | Onay prompt'unu atla | y/N onayının kendisi (`confirmYesNo`, `internal/cli/init.go`), dile uygun yanıtları kabul eder: çözümlenen arayüz dili Türkçeyse `e`/`evet`, aksi halde `y`/`yes` — `internal/tui/confirm.go`'nun dile-özel, asla birleştirilmeyen tuş disiplinini yansıtır (§1). ``` comrade init bash comrade init --print zsh comrade init --remove powershell ``` **`comrade init ` shell tamamlamasını da kurar**, hook bloğuna ek olarak — tam mekanizma (shell-başına kayıt satırları, fish'in kendi ayrı tamamlama dosyası, ve bu özellikten önceki bir kurulumda neden `comrade init `'in bir kez yeniden çalıştırılması gerektiği) için §9'un "Shell tamamlama" alt bölümüne bakın. ### `comrade upgrade` GitHub Releases'i çalışan binary'nin build-time versiyonundan daha yeni bir versiyon için kontrol eder, ve `--check` verilmediyse eşleşen platform arşivini indirir, release'in `checksums.txt`'ine karşı checksum'ını doğrular, binary'yi çıkarır, ve o an çalışan executable'ı atomik olarak değiştirir (Windows'un çalışan-bir-exe'nin-üstüne-yazamama durumu için rename manevrası dahil). | Flag | Etki | |---|---| | `--check` | Sadece yeni bir versiyonun mevcut olup olmadığını bildir; indirme/kurulum yapma | Depoda henüz hiç yayınlanmış release yokken — GitHub'ın API'si bu belirli durum için 404 döner (`update.ErrReleaseNotFound`, `internal/update/github.go`) — `comrade upgrade`/`--check`, GitHub'ın kendi ham İngilizce 404 JSON gövdesi yerine temiz, i18n'li bir mesaj yazdırır (`MsgUpgradeNoReleaseFound`). Bu, gerçek bir fetch başarısızlığından (ağ erişilemez, 200/404 dışı bir durum — `MsgUpgradeFetchFailed`) ayırt edilir; onun ham HTTP hata detayı yalnızca `COMRADE_DEBUG` ayarlıyken stderr'e yazılır, `hook.go`'nun kendi yerleşik debug-detay kuralını yansıtarak. ``` comrade upgrade --check comrade upgrade ``` ### Dahili (gizli) komutlar `comrade hook record --shell --exit --command ` — `last_command.json`'un tek yazıcısı; yalnızca `comrade init`'in kurduğu shell snippet'leri tarafından çağrılır, doğrudan etkileşimli kullanım için değildir (`Hidden: true`, bu yüzden `--help`'te asla görünmez). Bkz. §9. ## 6. LLM provider'ları `internal/llm.Provider`, her connector'ın uyguladığı arayüzdür: ```go type Provider interface { Complete(ctx context.Context, req CompletionRequest) (CompletionResponse, error) Stream(ctx context.Context, req CompletionRequest) (<-chan Chunk, error) Name() string } ``` Connector constructor'ları export edilmemiştir (unexported) — `internal/llm` dışından bir `Provider` elde etmenin tek yolu `llm.New(cfg, opts...)`'tur, bu da tüm fallback zincirini saran (§2 adım 4) bir `*Client` döner. | Connector | Backend | Notlar | |---|---|---| | `anthropic` | Anthropic Messages API | `internal/llm/anthropic.go` | | `openai_compat` | Herhangi bir OpenAI-Chat-Completions-uyumlu endpoint | Tek connector, `llm.openai_compat.base_url` ile parametrelenir — OpenAI, Mistral, Groq, GLM/Zhipu, Qwen, Kimi/Moonshot, OpenRouter ve yerel bir LM Studio sunucusunu aynı wire formatı üzerinden kapsar | | `google` | Gemini API | `internal/llm/google.go` | | `ollama` | Yerel Ollama (varsayılan `http://localhost:11434`) | Model boş bırakılabilir ve ilk kullanımda `/api/tags`'a karşı tembel olarak çözülür; API key gerekmez | **Fallback zinciri**: `llm.provider + "/" + llm.model`'den 1. deneme olarak kurulur, ardından her `llm.fallback` girdisi (`"provider/model"` veya çıplak `"provider"`) sırayla. Bir deneme için eksik bir API key, client kurulumunu başarısız kılmaz — o deneme gerçekten ulaşıldığında "hangi ortam değişkenini ayarlamalı" hatasını döndüren bir placeholder haline gelir, böylece önceki başarılı denemeler etkilenmez ve sonraki denemeler hâlâ bir şans elde eder. Bir HTTP 401/403 (`ErrAuthRejected`) tüm zinciri hemen durdurur (reddedilen bir kimlik bilgisini başka bir provider'a karşı yeniden denemek anlamsızdır); diğer her hata (timeout, transport hatası, bozuk yanıt) bir sonraki denemeye geçer. Aynı sentinel, `comrade auth login`'in yazmadan-önce-doğrulama davranışını da yönetir (§5): girilen key hiç saklanmadan önce ping'lenir, ve bu ping'te bir `ErrAuthRejected`, `login`'in key'i hiç yazmaması anlamına gelir — yazıp sonra geri almak yerine. **Timeout**: `llm.timeout_seconds` (ayarlanmamışsa/pozitif değilse varsayılan 60sn), `context.WithTimeout` ile her tek denemeyi bağımsız olarak sarar — yavaş bir deneme, bir sonraki denemenin kendi bütçesini yemez. **Streaming**: `Stream`, dört connector'ın hepsi için aynı sözleşmeyi taşıyan `<-chan Chunk` döner: sıfır veya daha fazla `{Text, Done:false}` chunk'ı, ardından tam olarak bir `{Done:true, Err}` chunk'ı, sonra kanal kapanır. Bu son chunk'ta nil bir `Err`, stream'in normal tamamlandığı anlamına gelir. **Idle timeout**: `llm.idle_timeout_seconds` (varsayılan `0`, kapalı), yukarıdaki `llm.timeout_seconds`'tan ayrı, ikinci bir timeout'tur — `timeout_seconds` isteğin/stream'in tamamını sınırlarken, `idle_timeout_seconds` *iki ardışık stream chunk'ı arasındaki boşluğu* (ilk chunk'tan öncekini de dahil ederek) sınırlar. Tam olarak tek bir yerde uygulanır: `Client.Stream`'in `releaseOnClose`'u, her bir chunk'ı ilettiğinde tek bir timer'ı sıfırlar — bu, dört connector'ın kendi okuma döngülerine ayrı ayrı kopyalanmak yerine yapılır. Timer, başka bir chunk gelmeden ateşlenirse, stream son bir `Chunk{Done:true, Err: ErrIdleTimeout}` ile biter (`internal/llm/errors.go`). `0` değeri, bu paketin idle-timeout-öncesi davranışını tam olarak yeniden üretir — hiç timer başlatılmaz. **JSON stratejisi**: comrade hiçbir zaman bir provider'ın native structured-output parametrelerine güvenmez — her completion isteği bunun yerine system prompt'a "tek bir JSON nesnesiyle yanıt ver" talimatını gömer, ve `internal/llm/parse.go` bu JSON'ı, dört connector genelinde tekdüze olarak, yanıt metninden çıkarır/doğrular (bkz. `docs/history/phases/FAZ-02.md`). **Redaction**: `internal/llm.Client`, hiçbir connector bir `CompletionRequest`'i görmeden önce, her istekte `redactPayload`'ı çalıştırır — bu her çağrı noktasında opsiyonel değildir (§8). ## 7. Yapılandırma Anahtar anahtar tam referans: [CONFIGURATION.md](CONFIGURATION.md). | Platform | Config dosya yolu | |---|---| | Linux/macOS | `$XDG_CONFIG_HOME/cli-comrade/config.toml`, yoksa `~/.config/cli-comrade/config.toml`'a düşer | | Windows | `%APPDATA%\cli-comrade\config.toml` | İlk çalıştırmada şema varsayılanlarıyla otomatik oluşturulur (`internal/config/loader.go`). Anahtar başına etkin değer önceliği: ortam değişkeni (`COMRADE_...`) > dosyadaki değer > yerleşik varsayılan; `comrade config list` her anahtarın çözümlenmiş kaynağını gösterir. **`base_url` doğrulaması** (`internal/config/validate.go`'nun `checkBaseURL`'i, SAST bulgu #3'ü kapatır: doğrulanmamış bir `base_url`, provider API key'ini, bir `Authorization: Bearer` header'ı olarak, adlandırdığı herhangi bir host'a gönderir), `llm.openai_compat.base_url`/ `llm.ollama.base_url`'e bu tek fonksiyonu paylaşan üç bağımsız noktada uygulanır, bu yüzden birbirinden kopamazlar: `comrade config set` (sert-red), yalnızca aktif provider'ın kendi `base_url`'inin config-yükleme-zamanı yeniden doğrulaması (yalnızca-uyarı — burada sert bir başarısızlık, repair komutları dahil her komutu tuğlaya çevirirdi), ve `internal/llm/client.go`'nun `buildProvider`'ı (fallback zincirindeki bir deneme için bir client gerçekten kurulduğu noktada sert-red, çünkü bu yol repair için erişilebilir kalmak zorunda değildir). Bir `http(s)` URL'i olarak host'lu ayrıştırılamayan, veya host'u bir cloud-metadata/link-local adresi olan (`169.254.0.0/16`, IPv6 `fe80::/10`) bir değer, bir `*config.InvalidValueError` ile kesin olarak reddedilir; loopback-olmayan bir host'a düz-metin `http://` URL'i izin verilir ama key'in şifrelenmemiş gideceğine dair bir uyarı basar — private aralıklar (`10/8`, `192.168/16`, `172.16/12`) ve kendi-barındırılan LAN Ollama/LM-Studio kurulumları bilinçli olarak engellenmez. ## 8. Güvenlik modeli Tam tehdit-modeli yazısı: [SECURITY.md](SECURITY.md). ### Risk sınıfları (`internal/safety/risk.go`) Artan şiddet sırası: `read` → `write` → `network` → `elevated` → `destructive`. Üretilen her komut LLM tarafından etiketlenir, ardından **bağımsız olarak** `safety.Engine.Evaluate` tarafından yeniden değerlendirilir — bu, LLM'in etiketine bir taban değerin ötesinde asla güvenmez. ### Denylist — koşulsuz `Block`, hangi mod olursa olsun Yerleşik kural kategorileri (`internal/safety/denylist.go`), bir tırnaklama numarasının (ve bir `$(...)` komut-ikamesi sarmalayıcısının) eşleşmeyi kaçırmasını önlemek için komutun normalize edilmiş/ token'lanmış haline karşı eşleştirilir: - `rm -rf /` (ve `~`/`$HOME`-root eşdeğerleri) - `mkfs` ve daha geniş disk-formatlama ailesi (`mke2fs`, `mkswap`, `mkdosfs`, `mkntfs`, `newfs`) - `dd of=/dev/` (ham disk üzerine yazma) - `diskpart clean` (bir diskin partition tablosunu siler) - PowerShell `Remove-Item`/`ri`/`rd`/`rmdir`/`del`/`erase`/`rm`, `-Recurse` ile bir sürücü kökünü hedefleyerek - `format :` (Windows format) - fork bomb (`:(){:|:&};:`) - `> /dev/` (gerçek bir disk aygıtına shell redirect'i) - bilinen bir destructive disk aracının (`wipefs`/`blkdiscard`/`sgdisk`/ `tee`/`shred`) gerçek bir `/dev/` aygıtına yöneltilmesi (`isDestructiveDiskTool`) Normalizasyon (`internal/safety/tokenize.go`'nun `normalizeCommand`'i), herhangi bir kural çalışmadan önce tırnak karakterlerini kaldırır ve `$(...)` komut ikamesini açar, ve regex-tabanlı her kural case-insensitive'tir — bu yüzden `rm -Rf /` ve `$(rm -rf /)`, çıplak formla birebir aynı şekilde tanınır. Kullanıcı `safety.denylist_extra` (regex) ile daha fazlasını ekleyebilir, aynı koşulsuz-`Block` etkisiyle; bozuk bir kullanıcı regex'i, motoru çökertmek yerine bir stderr uyarısıyla atlanır. ### Escalation kuralları — effective risk'i yalnızca yükseltir, asla düşürmez `internal/safety/escalation.go`'nun sabit kural seti: recursive/force silme flag'leri (`rm -r`/`-f`, PowerShell `-Recurse`/`-Force`), root-benzeri bir hedefte `chmod`/`chown -R`, disk aygıtı yazmaları, registry silme (`HKLM:`/`HKCU:`), `killall`/`taskkill /F`, güvenlik duvarı sıfırlamaları (`iptables -F`, `netsh advfirewall reset`), `git push --force`/`-f`, `sudo`/`runas`/yükseltme, paket yöneticisi kurulumları, ve bir network-erişim fiili içeren herhangi bir komut — artı v0.3.0'ın sertleştirme eklemeleri, ki bunlar modelin kendi beyan ettiği risk etiketinin önceden hiç kapsamadığı signature-allowlist boşluklarını kapatır: `find ... -delete` (kitlesel, `rm`-olmayan silme), `shred -u`/`--remove` ve `truncate -s 0` (`rm`-olmayan güvenli-silme/sıfırlama), `mv ... /dev/null` (move ile atma), Windows storage cmdlet'leri (`Format-Volume`/`Clear-Disk`/`Initialize-Disk`/ `Remove-Partition`), koşulsuz `reg delete ... /f`, `diskpart /s