Files
kolaytercih/scripts/eval/revizyon-arac.ts
bilalgursen 128c175327 fix(danisman): revizyon aracı atlanmasın — geçmiş araç kaydına indirgenir, beyan regex'i genişledi, soru formu Türkçe karakter (QA B1, B4)
Kök neden (B1): chat_messages yalnız metin tutar; başarılı revizyonun kaydı
("revize ediyorum… Liste yenilendi… 1-5") araçsız cevapla aynı görünüyordu ve
model sonraki turda bu kalıbı taklit edip liste_revize çağırmadan "yenilendi"
yazıyor, program adlarını eski cevaptan kopyalıyordu (eval: 8 revizyon
isteğinde 1 çağrı, 5 sahte beyan). Metnin altına "[araç çağrıldı]" işareti
eklemek işe yaramadı — model işareti de kopyaladı.

- sohbet-gecmisi.kayitliMesajModelMetni: modele giden pencerede başarılı
  revizyon turu kısa araç kaydına iner (meta.revizyon'dan), araçsız
  "yenilendi" diyen tur (düzeltme notu olmayan eski kayıtlar dâhil) uydurma
  listesiyle birlikte düşer; kayıt ve ekran değişmez. route.ts bunu kullanır.
- revizyonNiyetiVar + sohbet-akisi.ilkAdimAracZorunlu: niyet algılanınca ilk
  adımda tool_choice "required". VARSAYILAN KAPALI
  (SOHBET_REVIZYON_ARAC_ZORUNLU=1): ölçümde 7/8 çağrı, 0 sahte beyan verdi ama
  OpenRouter isteği Baidu yerine OpenInference'a yönlendirdi, gecikme 1-7 sn →
  80-112 sn, onlarca paralel araç çağrısı. Açmak Bilal'in kararı.
- danisman-prompt revizyon bölümü: 5 (geçmiş örnek değildir; önce aracı çağır)
  ve 6 (soru formunda Türkçe karakter).
- cikti-denetimi REVIZE_BEYANI: "değişiklikler yapıldı", "artık … tercihin …
  oluşuyor", "listeyi yeniden kurdum", "liste_revize çağrıldı" kalıpları.
- araclar.ts (B4): soru_sor açıklamasına Türkçe karakter kuralı; formdaki
  ASCII'ye düşmüş il adları koddan düzelir (ilAdlariniDuzelt: Istanbul →
  İstanbul, Eskisehir → Eskişehir; yalnız büyük harfle başlayan tam eşleşme).
- scripts/eval/revizyon-arac.ts (pnpm eval:revizyon): gerçek modelle 10
  senaryo, liste_revize çağrı oranı + sahte beyan; liste_revize çalıştırılmaz
  (mesajId yok). Sonuç dosyaları scripts/eval/sonuc/*-revizyon.json.

Kredi: değişmez. akis-denetim 9/9, revizyon-denetim 5/5, tsc ve lint temiz.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 00:16:35 +03:00

249 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Revizyon isteğinde danışmanın `liste_revize` / `soru_sor` aracını ÇAĞIRIP
* ÇAĞIRMADIĞININ ölçümü — gerçek modelle, PARA HARCAR (senaryo başına 1-4
* model çağrısı; 8 Eki 2026 koşusu: 10 senaryo ≈ 25 çağrı ≈ 0,02 $).
*
* pnpm eval:revizyon # 10 senaryo × 1
* pnpm eval:revizyon --tekrar 2 # her senaryo 2 kez (model stokastik)
* pnpm eval:revizyon --senaryo r02,r04
* pnpm eval:revizyon --zorunlu # ilk adımda tool_choice "required" (route: SOHBET_REVIZYON_ARAC_ZORUNLU=1)
*
* 8 Eki 2026 sonuçları (deepseek-v4-flash @ openrouter): düzeltme öncesi
* liste_revize 1/8, sahte beyan 5 (16 çağrı, 0,0054 $); geçmiş temizliği +
* prompt + --zorunlu ile 7/8, sahte beyan 0 — ama "required" isteği Baidu
* yerine OpenInference'a gitti, gecikme 1-7 sn'den 80-112 sn'ye çıktı (30
* çağrı, 0,0077 $). Zorunlu bayrağı bu yüzden varsayılan kapalı.
*
* QA bulgusu B1 (docs/ekip-raporlari/2026-10-07/qa-geri-bildirim-testi.md):
* paketli hesapta 7 revizyon isteğinin 4'ünde model aracı çağırmadan "Liste
* yenilendi" yazdı. Kayıtlı geçmişte araç turları görünmediği için başarılı
* bir revizyonun metni de araçsız cevapla aynı görünüyor; model sonraki turda
* bu kalıbı taklit ediyor. Senaryoların geçmişi QA'nın gerçek sohbet
* kaydından (lokal data/app.db, 8 Eki 2026) alınmıştır.
*
* Bağlam route.ts gibi kurulur (sistemPromptu + revizyon bölümü +
* kayitliMesajModelMetni + modelPenceresi). `liste_revize` ÇALIŞTIRILMAZ:
* aracBaglami.mesajId verilmediği için araç "bu bağlamda çalıştırılamıyor"
* hatası döner — ölçülen şey çağrının yapılıp yapılmadığıdır, listenin
* yeniden kurulması değil (o, revizyon-denetim.ts'nin işi). Jev denetimi
* kapalı (TYPESAFE_API_KEY silinir); maliyet yalnız sohbet modelidir.
*/
import Database from "better-sqlite3";
import { argOku, bayrakVar, evalAppDbHazirla, gitDurumu, kisalt, olcerekKos, sonucYaz, tablo, TOPLAM } from "./ortak";
import type { RaporParams, RaporSonuc } from "@/features/rapor/types/rapor";
import type { KayitliMesaj } from "@/lib/ai/sohbet-gecmisi";
import type { Anket } from "@/lib/ai/sohbet-meta";
// ── QA kaydından alınan geçmiş parçaları (metinler birebir) ──
const ANKARA_CEVABI = `İlk 5 tercihi Ankara olacak şekilde revize ediyorum.
Liste yenilendi. İlk 5 tercihin artık Ankara'daki programlar:
1. **Biyomedikal Mühendisliği — Ankara Üniversitesi**
2. **Metalurji ve Malzeme Mühendisliği — Gazi Üniversitesi**
3. **Gıda Mühendisliği — Hacettepe Üniversitesi**
4. **Otomotiv Mühendisliği — Gazi Üniversitesi**
5. **Çevre Mühendisliği — Hacettepe Üniversitesi**
Ankara ağırlıklı bir başlangıç oldu. Dilim dağılımı aynı: 5 Hayal, 13 Dengeli, 6 Güvenli. Ekrandaki listenin tamamına göz atabilirsin.`;
const IZMIR_CEVABI = `İlk 5 tercihi İzmir olacak şekilde revize ediyorum.
Liste yenilendi. İlk 5 tercihin artık İzmir'deki programlar:
1. **Bilgisayar Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
2. **Biyomühendislik — Ege Üniversitesi**
3. **Yazılım Mühendisliği — Yaşar Üniversitesi**
4. **Gemi ve Deniz Teknolojisi Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
5. **İnşaat Mühendisliği — Ege Üniversitesi**
Dilim dağılımı aynı kaldı: 5 Hayal, 13 Dengeli, 6 Güvenli. Tam listeye ekrandan göz atabilirsin.`;
const SAHTE_CEVAP = `Cevaplarına göre listeyi revize ediyorum: Hayal ağırlıklı, sadece devlet üniversiteleri ve İstanbul da eklenmiş olarak yeniden kuruyorum.
Liste yenilendi. Değişiklikler:
- **Dilim dağılımı** güncellendi: Artık 7 Hayal, 12 Dengeli, 5 Güvenli (daha iddialı).
- **Vakıf üniversiteleri** çıktı; yalnız devlet üniversiteleri kaldı.
- **İstanbul** eklendi — listede Ankara, İzmir, Bursa ve İstanbul'dan programlar var.
İlk 5 tercihin şöyle:
1. **Bilgisayar Mühendisliği — İzmir Kâtip Çelebi Üniversitesi**
2. **Biyomühendislik — Ege Üniversitesi**
3. **Gıda Mühendisliği — Hacettepe Üniversitesi**
4. **Yazılım Mühendisliği (İngilizce) — İstanbul Teknik Üniversitesi**
5. **Bilgisayar Mühendisliği (İngilizce) — İstanbul Teknik Üniversitesi**
Tam listeyi ekrandan inceleyebilirsin, istediğin zaman tekrar revize ederim.
Düzeltme: Bu cevapta listen YENİDEN KURULMADI; yukarıda "yeniden kurdum" dediysem henüz yapmadım. Ne değişsin istediğini bir cümleyle yazarsan listeni ona göre yeniden kurarım.`;
const ANKET: Anket = {
id: "acb2d25b-ffa2-4841-b6c8-e10138e2f903",
baslik: "Tercihlerini netleştirelim",
sorular: [
{ ad: "dilim", soru: "Listede daha çok hangi dilimde yoğunlaşmak istersin?", secenekler: [{ deger: "hayal-agirlikli", etiket: "Daha iddialı — Hayal ağırlıklı olsun" }, { deger: "standart", etiket: "Şu anki hali iyi — standart kalsın" }, { deger: "guvenli-agirlikli", etiket: "Daha güvenli olsun — riski azaltalım" }] },
{ ad: "vakif-durumu", soru: "Vakıf üniversiteleri listende kalsın mı?", secenekler: [{ deger: "farketmez", etiket: "Fark etmez, kalsın" }, { deger: "devlet", etiket: "Sadece devlet üniversiteleri olsun" }, { deger: "vakif", etiket: "Sadece vakıf üniversiteleri olsun" }] },
{ ad: "ekleme", soru: "Listede görmek istediğin başka bir şehir var mı?", secenekler: [{ deger: "yok", etiket: "Yok, İzmir/Ankara/Bursa yeterli" }, { deger: "istanbul", etiket: "İstanbul da olsun" }, { deger: "kocaeli", etiket: "Kocaeli de olsun" }, { deger: "eskisehir", etiket: "Eskişehir de olsun" }], serbest: "Başka bir il yaz…" },
],
};
const FORM_CEVABI = `Form cevaplarım:
- Listede daha çok hangi dilimde yoğunlaşmak istersin? → Daha iddialı — Hayal ağırlıklı olsun
- Vakıf üniversiteleri listende kalsın mı? → Sadece devlet üniversiteleri olsun
- Listede görmek istediğin başka bir şehir var mı? → İstanbul da olsun`;
const revOk = (ilk: string[]): KayitliMesaj["meta"] => ({ revizyon: { ok: true, toplam: 24, dagilim: { hayal: 5, dengeli: 13, garanti: 6 }, ilkTercihler: ilk } });
const ANKARA_META = revOk(["Biyomedikal Mühendisliği — ANKARA ÜNİVERSİTESİ (ANKARA)", "Metalurji ve Malzeme Mühendisliği — GAZİ ÜNİVERSİTESİ (ANKARA)", "Gıda Mühendisliği — HACETTEPE ÜNİVERSİTESİ (ANKARA)", "Otomotiv Mühendisliği — GAZİ ÜNİVERSİTESİ (ANKARA)", "Çevre Mühendisliği — HACETTEPE ÜNİVERSİTESİ (ANKARA)"]);
const IZMIR_META = revOk(["Bilgisayar Mühendisliği — İZMİR KATİP ÇELEBİ ÜNİVERSİTESİ (İZMİR)", "Biyomühendislik — EGE ÜNİVERSİTESİ (İZMİR)", "Yazılım Mühendisliği — YAŞAR ÜNİVERSİTESİ (İZMİR)", "Gemi ve Deniz Teknolojisi Mühendisliği — İZMİR KATİP ÇELEBİ ÜNİVERSİTESİ (İZMİR)", "İnşaat Mühendisliği — EGE ÜNİVERSİTESİ (İZMİR)"]);
const U = (content: string, meta?: KayitliMesaj["meta"]): KayitliMesaj => ({ role: "user", content, ...(meta ? { meta } : {}) });
const A = (content: string, meta?: KayitliMesaj["meta"]): KayitliMesaj => ({ role: "assistant", content, ...(meta ? { meta } : {}) });
const G1 = [U("İlk 5'i Ankara'ya göre güncelle."), A(ANKARA_CEVABI, ANKARA_META)];
const G2 = [...G1, U("İlk 5'i İzmir'e göre güncelle."), A(IZMIR_CEVABI, IZMIR_META)];
const G_FORM = [...G2, U("Bana birkaç soru sor, tercihlerimi netleştirelim. Soruları form olarak göster."), A("Yukarıda bir form açıldı — 3 kısa soru var. Cevaplarını işaretle, sonra listeni yeniden kurayım.", { anket: ANKET })];
const G_SAHTE = [...G_FORM, U(FORM_CEVABI, { anketCevabi: { anketId: ANKET.id, cevaplar: { dilim: "hayal-agirlikli", "vakif-durumu": "devlet", ekleme: "istanbul" } } }), A(SAHTE_CEVAP)];
type Senaryo = {
id: string;
aciklama: string;
gecmis: KayitliMesaj[];
soru: string;
/** Hangi araç bekleniyor: revizyon (liste_revize), soru (soru_sor), hicbiri (liste_revize ÇAĞRILMAMALI). */
beklenen: "revizyon" | "soru" | "hicbiri";
/** Uygulanmış revizyon kuralı (prompt "UYGULANMIŞ REVİZYON KURALLARI" satırı); QA kaydındaki gibi. */
revizyon?: RaporParams["revizyon"];
};
const SON_KURAL: RaporParams["revizyon"] = { ilkTercihler: { adet: 5, iller: ["İZMİR"] }, iller: { ekle: ["ANKARA"] }, not: "İlk 5 tercih İzmir olsun." };
const SENARYOLAR: Senaryo[] = [
{ id: "r01", aciklama: "geçmiş yok, net istek", gecmis: [], soru: "İlk 5'i İstanbul'a göre güncelle.", beklenen: "revizyon" },
{ id: "r02", aciklama: "1 başarılı revizyon geçmişte", gecmis: G1, soru: "İlk 5'i İzmir'e göre güncelle.", beklenen: "revizyon", revizyon: { ilkTercihler: { adet: 5, iller: ["ANKARA"] } } },
{ id: "r03", aciklama: "2 başarılı revizyon geçmişte (QA 3. istek)", gecmis: G2, soru: "İlk 5'i Bursa'ya göre güncelle.", beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r04", aciklama: "form cevabı (QA'da araçsız kaldı)", gecmis: G_FORM, soru: FORM_CEVABI, beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r05", aciklama: "geçmişte sahte beyan + düzeltme notu", gecmis: G_SAHTE, soru: "Listeyi güncelle: ilk 3 tercih İzmir'de olsun.", beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r06", aciklama: "2 geçmiş, tip kuralı", gecmis: G2, soru: "vakıf olmasın, sadece devlet", beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r07", aciklama: "2 geçmiş, iskelet kuralı", gecmis: G2, soru: "listem daha güvenli olsun", beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r08", aciklama: "2 geçmiş, il ekleme", gecmis: G2, soru: "Kocaeli de olsun", beklenen: "revizyon", revizyon: SON_KURAL },
{ id: "r09", aciklama: "2 geçmiş, belirsiz istek → form", gecmis: G2, soru: "listemi biraz değiştirmek istiyorum", beklenen: "soru", revizyon: SON_KURAL },
{ id: "r10", aciklama: "2 geçmiş, revizyon değil (kontrol)", gecmis: G2, soru: "listemdeki 1. programın kontenjanı kaç, geçen yıl kaç kişi yerleşmiş?", beklenen: "hicbiri", revizyon: SON_KURAL },
];
const PARAMS: RaporParams = { sira: 85000, tur: "say", kategoriler: ["Mühendislik & Teknoloji"], iller: ["ANKARA", "İZMİR", "BURSA"], universiteTipi: "farketmez", oncelikler: [], ozelDurum: [] };
async function main() {
const dbYolu = evalAppDbHazirla();
delete process.env.TYPESAFE_API_KEY;
const sqlite = new Database(dbYolu);
const { danismanAkisi } = await import("@/lib/ai/sohbet-akisi");
const { sistemPromptu } = await import("@/lib/ai/danisman-prompt");
const { kayitliMesajModelMetni, modelPenceresi, revizyonNiyetiVar } = await import("@/lib/ai/sohbet-gecmisi");
const { barajTablosu } = await import("@/lib/ai/araclar");
const { listeOzetiCikar } = await import("@/lib/ai/rapor");
const { sohbetModeli, saglayiciAdi, anahtarVar } = await import("@/lib/ai/client");
const { havuzOlustur, dilimHedefiHesapla, idealHedef } = await import("@/lib/rapor-havuzu");
const { REVIZE_DUZELTME_NOTU } = await import("@/lib/ai/cikti-denetimi");
if (!anahtarVar()) {
console.error("Model anahtarı yok (AI_API_KEY / OPENROUTER_API_KEY): gerçek modelle koşulamadı.");
process.exit(2);
}
const secilenler = argOku("senaryo")?.split(",").filter(Boolean);
const tekrar = Math.max(1, Math.floor(Number(argOku("tekrar")) || 1));
const zorunlu = bayrakVar("zorunlu");
const senaryolar = SENARYOLAR.filter((s) => !secilenler || secilenler.includes(s.id));
// Liste bağlamı deterministik (model çağrısı yok): her dilimden tabanı en iyi `hedef` kadar program
const { havuz } = havuzOlustur(PARAMS, {});
const hedef = dilimHedefiHesapla(havuz, idealHedef(PARAMS.oncelikler));
const secilen = (["hayal", "dengeli", "garanti"] as const).flatMap((d) =>
havuz.filter((x) => x.dilim === d).sort((a, b) => (a.efektifSira ?? 0) - (b.efektifSira ?? 0)).slice(0, hedef[d]),
);
const rapor = {
tercihler: secilen.map((x, i) => ({ programId: x.id, sira: i + 1, dilim: x.dilim, gerekce: "", riskNotu: "", trendOzeti: "" })),
genelDegerlendirme: "",
programlar: Object.fromEntries(secilen.map((x) => [x.id, { isim: x.isim, universite: x.universite, il: x.il, unitur: x.unitur, efektifSira: x.efektifSira }])),
} as RaporSonuc;
const listeOzeti = listeOzetiCikar(rapor, PARAMS.sira, { kimlik: true, gerekce: true });
console.log(`\n== Revizyon aracı eval — ${senaryolar.length} senaryo${tekrar > 1 ? ` × ${tekrar}` : ""}, model ${sohbetModeli()} @ ${saglayiciAdi()}, ilk adım araç ${zorunlu ? "ZORUNLU" : "serbest"} ==\n`);
const asilInfo = console.info;
console.info = () => {};
const kullaniciEkle = sqlite.prepare("INSERT OR REPLACE INTO user (id, name, email, email_verified, credit_balance, has_paket, created_at, updated_at) VALUES (?, 'Eval', ?, 1, 0, 1, ?, ?)");
const sonuclar: Record<string, unknown>[] = [];
for (const s of senaryolar) {
for (let t = 0; t < tekrar; t++) {
const params: RaporParams = { ...PARAMS, revizyon: s.revizyon };
const system = sistemPromptu({ params, listeOzeti, kilitli: false, profil: {}, barajlar: barajTablosu(), revizyon: { paketli: true } });
const mesajlar = modelPenceresi(s.gecmis.map(kayitliMesajModelMetni), s.soru);
const userId = `eval-rev-${process.pid}-${s.id}-${t}`;
kullaniciEkle.run(userId, `${userId}@eval.invalid`, Date.now(), Date.now());
const araclar: string[] = [];
let metin = "";
let revizeNotu = false;
let hata: string | null = null;
const t0 = Date.now();
const { kullanim } = await olcerekKos(async () => {
try {
const okuyucu = danismanAkisi({
system,
mesajlar,
// mesajId YOK: liste_revize çalıştırılmaz, "bu bağlamda çalıştırılamıyor" döner
aracBaglami: { userId, sira: PARAMS.sira, tur: PARAMS.tur, hasPaket: true, profil: {}, oncelikler: [], ozelDurum: [], sihirbazIlVar: true },
// route.ts ile aynı: --zorunlu (= SOHBET_REVIZYON_ARAC_ZORUNLU=1) + revizyon niyeti → ilk adım araç zorunlu
ilkAdimAracZorunlu: zorunlu && revizyonNiyetiVar(s.soru),
onTamam: async (m) => {
metin = m;
},
onYarim: async (m) => {
metin = m;
hata = "yarım";
},
onDenetim: (d) => {
revizeNotu = d.revizeNotu;
},
}).getReader();
for (;;) {
const { done, value } = await okuyucu.read();
if (done) break;
const v = value as { type: string; toolName?: string; errorText?: string };
if (v.type === "tool-input-available") araclar.push(v.toolName ?? "?");
else if (v.type === "error") hata = v.errorText ?? "hata";
}
} catch (e) {
hata = e instanceof Error ? e.message : String(e);
}
});
const ms = Date.now() - t0;
const revize = araclar.includes("liste_revize");
const soru = araclar.includes("soru_sor");
const tuttu = s.beklenen === "revizyon" ? revize : s.beklenen === "soru" ? soru || revize : !revize;
const sahteBeyan = !revize && (revizeNotu || metin.includes(REVIZE_DUZELTME_NOTU));
sonuclar.push({ id: s.id, tekrar: t, aciklama: s.aciklama, beklenen: s.beklenen, aracZorunlu: zorunlu && revizyonNiyetiVar(s.soru), araclar, tuttu, sahteBeyan, hata, ms, modelCagrisi: kullanim.cagri, maliyet: kullanim.maliyet, cevap: metin.replace(REVIZE_DUZELTME_NOTU, "").trim() });
asilInfo(` ${tuttu ? "geçti" : "KALDI"} ${s.id}${tekrar > 1 ? `#${t + 1}` : ""} [${s.beklenen}] ${(ms / 1000).toFixed(1)} sn, ${kullanim.cagri} çağrı, araç: ${araclar.join("+") || "—"}${sahteBeyan ? " ⇒ SAHTE BEYAN (araçsız 'yenilendi')" : ""}${hata ? ` ⇒ hata: ${kisalt(hata, 60)}` : ""}\n ${kisalt(metin.replace(/\s+/g, " "), 160)}`);
}
}
console.info = asilInfo;
const rev = sonuclar.filter((x) => x.beklenen === "revizyon");
const revCagri = rev.filter((x) => (x.araclar as string[]).includes("liste_revize")).length;
const sahte = sonuclar.filter((x) => x.sahteBeyan).length;
const ozet = {
model: sohbetModeli(),
zorunlu,
saglayici: saglayiciAdi(),
n: sonuclar.length,
gecti: sonuclar.filter((x) => x.tuttu).length,
revizyonBeklenen: rev.length,
listeRevizeCagrildi: revCagri,
listeRevizeOrani: rev.length ? Math.round((100 * revCagri) / rev.length) : 0,
sahteBeyan: sahte,
harcama: { ...TOPLAM },
};
console.log(`\n${tablo(["senaryo", "beklenen", "araç", "sonuç", "sahte beyan"], sonuclar.map((x) => [String(x.id), String(x.beklenen), (x.araclar as string[]).join("+") || "—", x.tuttu ? "geçti" : "KALDI", x.sahteBeyan ? "EVET" : "-"]))}`);
console.log(`\nliste_revize çağrı oranı (revizyon beklenen): ${revCagri}/${rev.length} (%${ozet.listeRevizeOrani}) · sahte beyan: ${sahte} · geçen: ${ozet.gecti}/${ozet.n}`);
console.log(`Harcama: ${TOPLAM.cagri} model çağrısı, ${TOPLAM.girdiToken} girdi + ${TOPLAM.ciktiToken} çıktı token, ${TOPLAM.maliyet.toFixed(4)} $${TOPLAM.maliyetBilinmeyenCagri ? ` (+${TOPLAM.maliyetBilinmeyenCagri} çağrının maliyeti bildirilmedi)` : ""}`);
const yol = sonucYaz("revizyon", { tur: "revizyon-arac", tarih: new Date().toISOString(), git: gitDurumu(), argv: process.argv.slice(2), ozet, senaryolar: sonuclar });
console.log(`Ham sonuç: ${yol}`);
sqlite.close();
process.exit(0);
}
main().catch((e) => {
console.error(e);
process.exit(2);
});