@akredit/acik-mcp 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,17 @@
4
4
  // geçmeyeni düzeltip yeniden denetler, sonunda tek bir özet gösterir. Hoca isterse numarayla düzeltir.
5
5
  // Halüsinasyona yer yok: kitap, kişi, tarih, sayı uydurulmaz; kaynaklar kaynak_dogrula'dan geçmeden önerilmez.
6
6
  //
7
+ // Dış inceleme 2026-11-05: akıllı doldurma kalır, ama bilinmesi mümkün olmayan ve resmî taahhüt olan bilgiler
8
+ // (değerlendirme yöntemi ve yüzdeleri, uygulama/lab, kullanılan kaynaklar, sayfada yoksa amaç ve içerik) sayfadan
9
+ // okunur; yoksa asistan bunları TEK mesajda, numaralı ve varsayılan seçenekli sorar, uydurmaz. Ders adından
10
+ // üretilen her şey "öneri" etiketiyle gösterilir. OBS'ye yazmadan önce alan alan önceki/yeni değer özeti gösterilir,
11
+ // hoca bir kez "kaydet" der; kayıttan sonra sayfa geri okunur ve obs_karsilastir ile doğrulanır.
12
+ //
7
13
  // İstem metni veri değil asistana yönergedir; içinde hocanın girdisi yalnız isteğe bağlı kısa bağımsız
8
14
  // değişkenler olarak yer alır (ders kodu, çerçeve) ve sınırlanır. Giriş bilgisi (parola, doğrulama kodu,
9
- // çerez) asistana asla verilmez (ilke 8).
15
+ // çerez) istenmez; görülürse reddedilir ve kaydedilmez (ilke 8).
10
16
  import { z } from "zod";
17
+ import { arac, KOD_KAYNAGI } from "./temel.js";
11
18
  import { GIRIS_KURALI_HOCAYA } from "./yardim.js";
12
19
  /** Giriş bilgisi kuralı: bütün istemlerde aynen yer alır. */
13
20
  export const GIRIS_BILGISI_KURALI = [
@@ -16,19 +23,37 @@ export const GIRIS_BILGISI_KURALI = [
16
23
  "- Hoca bunları kendisi yalnız OBS'nin kendi giriş ekranına yazar. Bunu hocaya ilk adımda açıkça söyle.",
17
24
  "- Hoca yine de sohbete parola yazarsa kullanma, hiçbir araca gönderme; silmesini ve OBS'de parolasını değiştirmesini öner.",
18
25
  "- CAPTCHA'yı sen çözmezsin, hoca çözer.",
19
- '- OBS\'de Kaydet düğmesine yalnız hocanın açık izniyle basarsın (ya o sekme için "evet", ya bir kez verilen "hepsini kaydet" izni). İzin yoksa basmazsın. Onaya ya da dış kuruma Gönder düğmesine hiç basmazsın; onu hoca basar.',
26
+ '- OBS\'de Kaydet düğmesine yalnız hocanın açık izniyle basarsın: önce alan alan önceki ve yeni değer özetini gösterirsin, hoca bir kez "kaydet" der. İzin yoksa basmazsın. Onaya ya da dış kuruma Gönder düğmesine hiç basmazsın; onu hoca basar.',
20
27
  ].join("\n");
21
28
  /** Akıllı doldurma kısayolu: bütün menülerde aynı numara ve aynı anlam. */
22
29
  export const AKILLI_DOLDURMA_SECENEGI = "4) Hepsini akıllıca doldur (önerilen)";
23
30
  /** "4) Hepsini akıllıca doldur" seçildiğinde (ya da hoca "sen seç", "hepsi" derse) izlenecek yol. */
31
+ /** Bilinmesi mümkün olmayan ve resmî taahhüt olan bilgiler: sayfadan okunur, yoksa tek mesajda sorulur. */
32
+ export const TEYIT_SORULARI = [
33
+ "Teyit gerektiren bilgiler (asla tahmin etme):",
34
+ "- Değerlendirme yöntemleri ve yüzdeleri (vize, final, ödev, proje...).",
35
+ "- Dersin uygulama ya da laboratuvar içerip içermediği.",
36
+ "- Derste gerçekten kullanılan kaynaklar ve ders notları.",
37
+ "- Sayfada yoksa dersin amacı ve içeriği.",
38
+ "Bunları önce sayfadan oku. Sayfada olmayanları hocaya TEK mesajda, birlikte, numaralı ve her birine varsayılan seçenekle sor. Örnek:",
39
+ ' "Birkaç bilgiyi yalnız siz bilebilirsiniz. Numarayla yanıtlayın; boş bırakırsanız parantezdeki varsayılanı kullanırım.',
40
+ " 1) Değerlendirme: a) Vize %40, Final %60 (varsayılan) b) Vize %30, Ödev %20, Final %50 c) başka: yazın",
41
+ " 2) Uygulama ya da laboratuvar var mı? a) yok (varsayılan, T+U+L'ye göre) b) var",
42
+ ' 3) Kullandığınız kitap ya da notlar: a) önereyim, siz seçin (varsayılan) b) şunları kullanıyorum: yazın"',
43
+ "Hocanın seçtiği varsayılan da hoca teyidi sayılır (degerlendirmeDayanagi: 'hoca'). Yanıt gelmeden değerlendirme yüzdesi yazma paketine girmez.",
44
+ ].join("\n");
24
45
  export const AKILLI_DOLDURMA = [
25
46
  "Hepsini akıllıca doldur (seçenek 4):",
26
- "- Kalan her alan için seçeneklerini kendin yaz ve en iyisini kendin seç. Hocaya ara soru sorma.",
47
+ "- Önce teyit gerektiren bilgileri sayfadan oku; sayfada olmayanları hocaya TEK mesajda birlikte sor (aşağıdaki kural). Bundan sonra ara soru sorma.",
48
+ "- Kalan her alan için seçeneklerini kendin yaz ve en iyisini kendin seç. Ders adından ve program çıktılarından ürettiğin her şeyi 'öneri' etiketiyle göster.",
27
49
  "- Her seçimi ilgili araçla denetle (bologna_taslak_denetle, bologna_durum, rapor_dil_denetle). Geçmeyeni düzelt ve yeniden denetle; geçene kadar hocaya gösterme. Üç denemede geçmezse o alanı EKSİK bırak.",
28
50
  "- Sayıları (toplam, yüzde, iş yükü, sağlanma düzeyi) yalnız araç yanıtından al.",
29
51
  "- Uydurma yok: hocanın vermediği ve doğrulayamadığın bilgi (kişi, kitap, tarih, sayı, toplantı) EKSİK kalır. Kaynaklar yalnız kaynak_dogrula'dan 'dogrulandi' dönenlerdir.",
30
- "- Bitince tek bir özet göster: numara, alan, ne yazdığın (ilk cümle ya da kısa özet), denetim sonucu. Altına şunu yaz:",
31
- ' "Şu alanları şöyle doldurdum. Değiştirmek istediğiniz alanın numarasını yazın; yoksa devam diyebilirsiniz. Bunları daha sonra da kolayca güncelleyebilirsiniz."',
52
+ "- Araç sonucunu 'geçer' diye sunma: 'biçim denetiminden geçti; içerik teyidi: hoca' de.",
53
+ "- Bitince tek bir özet göster: numara, alan, ne yazdığın (ilk cümle ya da kısa özet), kaynağı (sayfa, hoca ya da öneri), biçim denetimi sonucu. Altına şunu yaz:",
54
+ ' "Şu alanları şöyle doldurdum; öneri yazanlar benim önerimdir, içeriği siz teyit edersiniz. Değiştirmek istediğiniz alanın numarasını yazın; yoksa devam diyebilirsiniz."',
55
+ "",
56
+ TEYIT_SORULARI,
32
57
  ].join("\n");
33
58
  /** Bütün istemlerde ortak çalışma biçimi. */
34
59
  export const ORTAK_KURALLAR = [
@@ -41,9 +66,9 @@ export const ORTAK_KURALLAR = [
41
66
  "- Sunucu metin üretmez. Seçenek metinlerini sen yazarsın; sunucu kural verir, denetler ve sayıları hesaplar.",
42
67
  "- Sayıları (toplam, yüzde, iş yükü, sağlanma düzeyi) yalnız araç yanıtından al, kendin toplama.",
43
68
  "- Uydurma yok: kişi, kitap, ISBN, DOI, tarih, toplantı, sayı uydurma. Bilinmeyen bilgi EKSİK kalır.",
44
- "- Kaynak önerirken: hocaya kitabın adını sorma. Ders içeriğine uygun kaynakları kendi web arama aracınla bul, her birini kaynak_dogrula ile doğrula ve yalnız 'dogrulandi' dönenleri künyesi ve bağlantısıyla göster. Doğrulanamayan kaynağı asla önerme.",
69
+ "- Kaynak: önce sayfadaki ve hocanın kullandığı kaynakları al. Öneri gerekiyorsa kendi web arama aracınla aday bul, her birini kaynak_dogrula ile sorgula ve yalnız 'katalogda_bulundu' dönenleri künyesi ve bağlantısıyla 'öneri' olarak göster; uygunluğuna hoca karar verir. Katalogda bulunmayan kaynağı kendin önerme; hoca kullandığını söylerse 'hoca_beyani' olarak yaz.",
45
70
  "- Bologna sayfasındaki Dersi Verenler, Yardımcılar ve Koordinatör satırları hocanın yayımlanmış görev bilgisidir: olduğu gibi oku, koru, değiştirme.",
46
- "- Öğrenci adı ve numarası içeren not tablolarında öğrencilere o1, o2 gibi takma ad verilir (not tablosu aracı bunu kendisi yapar).",
71
+ "- Öğrenci adı ya da numarası sohbete yapıştırılmaz: hoca Excel'de Ad, Soyad, Numara sütunlarını silip yalnız not sütunlarını yapıştırır ya da https://akredit.org/deneyin hesaplayıcısını kullanır. Not tablosu aracı kimlik sütunu görürse tabloyu işlemez.",
47
72
  "- Engel çıkarsa (tarayıcı aracı yok, site açılmıyor, giriş ekranı, CAPTCHA, çerçeve, oturum düştü) engel_coz ile adımları al ve hocaya tek tek göster.",
48
73
  "- Her araç yanıtındaki sonrakiAdim önerisine uy.",
49
74
  "- Sonunda kopyalanabilir bir çıktı ver. Onaya ya da dış kuruma gönderimi hoca yapar.",
@@ -82,23 +107,26 @@ const obsGirisi = () => [
82
107
  ' 2 seçilirse şunu yaz: "OBS\'de dersinizin Bologna sayfasını açın. Sayfada Ctrl+A ile hepsini seçin, Ctrl+C ile kopyalayın, buraya Ctrl+V ile yapıştırın."',
83
108
  " Herhangi bir engelde engel_coz'u ilgili engel türüyle çağır.",
84
109
  ].join("\n");
85
- /** Kaynak bulma: hocaya sormadan, web aramasıyla, kaynak_dogrula'dan geçirerek. */
110
+ /** Kaynak: önce sayfadaki ve hocanın kullandığı; öneri yalnız katalogda bulunan, "öneri" etiketiyle. */
86
111
  const KAYNAK_ADIMI = [
87
- " Kaynaklar: hocaya kitabın adını sorma. Ders adı, içerik ve haftalık konulara göre kendi web arama aracınla 4-6 aday eser bul (ders kitabı, temel makale).",
88
- " Her adayı DOI, ISBN ya da başlık ile yazar olarak kaynak_dogrula'ya ver. Yalnız 'dogrulandi' dönenleri kullan; künyeyi aracın verdiği gibi yaz, bağlantısıyla birlikte göster.",
89
- " Doğrulanamayan adayı asla önerme, künyesini yazma. Web arama aracın yoksa bunu hocaya söyle; hoca bir eser adı verirse onu da kaynak_dogrula'dan geçir, geçmezse kaynaklar alanı EKSİK kalır.",
112
+ " Kaynaklar: önce sayfadaki kaynakları al; derste kullanılan kaynakları teyit sorularıyla birlikte (tek mesajda) sor.",
113
+ " Öneri gerekiyorsa kendi web arama aracınla 4-6 aday eser bul; her adayı DOI, ISBN ya da başlık, yazar, tür ve biliniyorsa yılla kaynak_dogrula'ya ver (çağrı başına en çok 5).",
114
+ " Yalnız 'katalogda_bulundu' dönenleri 'öneri' etiketiyle, künyesi ve bağlantısıyla göster; uygunluğuna hoca karar verir. Bologna araçlarına kaynağı { kunye, dogrulama: { durum, baglanti, imza } } olarak aynen ver.",
115
+ " Katalogda bulunmayan eseri kendin önerme. Hoca bir eseri kullandığını söylerse (çoğu Türkçe kitap katalogda yoktur) künyeyi yalnız hocanın verdiği bilgiyle yaz ve dogrulama: 'hoca_beyani' ver; eksik alanı uydurma, EKSİK bırak.",
90
116
  ].join("\n");
91
- /** OBS'ye yazma menüsü: tarayıcı aracı yoksa 3'e düşer. */
117
+ /** OBS'ye yazma: önce önceki/yeni özeti, tek "kaydet", sonra geri okuma ve obs_karsilastir. */
92
118
  const OBS_YAZMA = [
93
- "6. OBS'ye yazma. Tarayıcı aracın yoksa doğrudan 3'ü uygula. Varsa sor:",
119
+ "6. OBS'ye yazma.",
120
+ " Önce bologna_kopya_metni'ni çağır (OBS'deki şimdiki değerleri oncekiDegerler olarak ver). Yazma paketine alınmayanları (doğrulanmamış kaynak, teyitsiz değerlendirme yüzdesi) hocaya söyle; onları yazma.",
121
+ " Hocaya degisiklikOzeti'ni alan alan göster: numara, sekme, alan, önceki değer, yeni değer (uzunsa ilk cümle).",
122
+ " Tarayıcı aracın yoksa doğrudan 3'ü uygula. Varsa sor:",
94
123
  " 1) Formu ben doldurayım, Kaydet'e siz basın",
95
- " 2) Formu ben doldurup kaydedeyim (her sekmeden önce kısa özet gösterip evet isterim)",
124
+ ' 2) Formu ben doldurup kaydedeyim: yukarıdaki özeti onaylıyorsanız bir kez "kaydet" yazın',
96
125
  " 3) Alan alan kopyalayın",
97
- ` ${AKILLI_DOLDURMA_SECENEGI.replace("doldur", "doldur ve kaydet")}: bütün sekmeleri doldurup kaydederim; başlamadan bir kez "hepsini kaydet" izninizi alırım, sonunda tek özet gösteririm.`,
98
126
  " 1: OBS düzenleme ekranında sekme sekme ilgili kutulara yaz. Her sekmeden sonra dur ve hocadan gözle denetleyip Kaydet'e basmasını iste.",
99
- ' 2: Her sekmeyi doldur, kaydetmeden önce o sekmede ne yazdığını kısa özetle ve "Kaydedeyim mi? evet / hayır" diye sor. Yalnız "evet" gelirse Kaydet\'e bas.',
100
- ' 4: Önce şunu sor: "Bütün sekmeleri doldurup kaydetmem için izin veriyor musunuz? hepsini kaydet / hayır". Yalnız "hepsini kaydet" gelirse sekme sekme doldur ve kaydet; sonunda neyi nereye yazdığını tek özetle göster.',
101
- " 3: bologna_kopya_metni ile metni al; her alanı ayrı kod bloğunda ver, hangi sekmedeki hangi kutuya yapıştırılacağını yaz.",
127
+ ' 2: Yalnız hoca "kaydet" yazdıysa, yalnız özetteki alanları yaz ve kaydet. Her sekmeden önce ders kodunu ve dönemi sayfada denetle; beklenenden farklıysa yazmayı durdur ve hocaya söyle.',
128
+ " 3: bologna_kopya_metni metnini her alan ayrı kod bloğunda ver, hangi sekmedeki hangi kutuya yapıştırılacağını yaz.",
129
+ " Kayıttan sonra (1 ve 2): sayfayı geri oku, obs_karsilastir'e obsKarsilastirGirdisi'ni ve okuduğun metni ver. dur true ise yazmayı bırak, nedenini söyle. Farklı alanları hocaya göster.",
102
130
  " Her durumda: Dersin Yetkilileri satırlarına dokunma, OBS'deki hâliyle kalsın. Parolayı asla isteme; o yalnız hocadadır. Onaya gönderme (Gönder) düğmesine sen basma.",
103
131
  ].join("\n");
104
132
  const bolognaAkisi = (dersKodu) => [
@@ -106,22 +134,23 @@ const bolognaAkisi = (dersKodu) => [
106
134
  "",
107
135
  "Akış:",
108
136
  obsGirisi(),
109
- "1. Ayrıştır. bologna_ayristir önce Proliz biçimini, olmazsa genel ayrıştırıcıyı dener. Bulunamayan alanları not al.",
137
+ "1. Ayrıştır. bologna_ayristir önce Proliz biçimini, olmazsa genel ayrıştırıcıyı dener (sekmeli düz metin de olur). Bulunamayan alanları ve ingilizce değerini not al; sonraki araçlara ingilizce değerini aynen ver.",
110
138
  " Dersi Verenler, Yardımcılar ve Koordinatör satırları yetkililer alanında olduğu gibi gelir; onları değiştirme, sonraki çağrılarda aynen geri ver.",
111
139
  " Program çıktıları sayfası da varsa onu da ayrıştır ve P kodlarını sakla; P kodlarını asla ezberden yazma.",
112
140
  "2. Denetle. bologna_denetle'yi çağır. Sonucu tek tabloda göster: numara, alan, durum (boş, zayıf, tamam), kısa neden. Kalan alan sayısını söyle.",
113
141
  ` Sor: 1) alan alan seçenek göstererek dolduralım 2) yalnız boş olanlar 3) numaraları kendim seçeyim ${AKILLI_DOLDURMA_SECENEGI}.`,
114
- "3. Bağlam. Amaç ya da içerik yoksa dersin odağını ders adından ve program çıktılarından çıkar. Akıllı doldurmada sorma; 1-3 seçildiyse tek soru sor: 1) [1. öneri] 2) [2. öneri] 3) kendim yazayım.",
142
+ "3. Teyit. Değerlendirme yöntemi ve yüzdeleri, uygulama/lab, kullanılan kaynaklar ve (sayfada yoksa) amaç ve içerik yalnız hocanın bilebileceği bilgilerdir. Sayfada olmayanları hocaya TEK mesajda, numaralı ve varsayılan seçenekli sor (akıllı doldurmada da). Uydurma.",
143
+ " Amaç ve içerik için hocanın verdiği kısa odak yoksa ders adından ve program çıktılarından 'öneri' yaz ve öneri olduğunu söyle.",
115
144
  "4. Her alan için: bologna_alan_rehberi'ni çağır. Kurala uyan 2-3 kısa seçenek yaz.",
116
- " Seçenekleri bologna_taslak_denetle ile denetle; geçmeyeni düzelt ve yeniden denetle, yalnız geçenleri kullan. Uzun metinlerde ilk cümleyi ve kelime sayısını göster.",
145
+ " Seçenekleri bologna_taslak_denetle ile denetle; biçim denetiminden geçmeyeni düzelt ve yeniden denetle. Uzun metinlerde ilk cümleyi ve kelime sayısını göster. Sonucu 'biçim denetiminden geçti; içerik teyidi: hoca' diye sun.",
117
146
  " Akıllı doldurmada (4) en iyi seçeneği kendin seç ve hocaya alan alan sorma; sonunda tek özet göster.",
118
- " Haftalık konular: 14 hafta, ara sınav ve yarıyıl sonu haftası görünür. Değerlendirme: yüzdeler toplamı 100.",
119
- " AKTS iş yükü: is_yuku_oner'i çağır; üç dağılımın toplamı hedefe tam eşittir. AKTS'yi değiştirme.",
147
+ " Haftalık konular: 14 hafta, ara sınav ve yarıyıl sonu haftası görünür. Değerlendirme: yalnız sayfadan ya da hocadan; degerlendirmeDayanagi'ni ('sayfa' ya da 'hoca') ver.",
148
+ " AKTS iş yükü: is_yuku_oner'i sayfadaki değerlendirme kalemleriyle çağır; derste olmayan etkinliğe satır açılmaz. Sayfadan okunan iş yüküyle öneriyi ayrı göster. AKTS'yi değiştirme.",
120
149
  " Öğrenme çıktısı ile program çıktısı katkı tablosu: yalnız ayrıştırılan P kodlarıyla öner.",
121
150
  KAYNAK_ADIMI,
122
151
  '5. Her alandan sonra bologna_durum\'u çağır. Alan alan ilerliyorsan kalan alan sayısını tek cümleyle söyle: "Kalan 3 alan."',
123
152
  OBS_YAZMA,
124
- '7. Tamamlandı denetimi. Kayıttan sonra sayfayı yeniden oku (tarayıcı varsa) ya da yeniden yapıştırmasını iste; bologna_ayristir ve bologna_denetle ile denetle. Eksik yoksa "Tamamlandı" de.',
153
+ '7. Doğrulama. Kayıttan sonra sayfayı yeniden oku (tarayıcı varsa) ya da yeniden yapıştırmasını iste; obs_karsilastir ve bologna_denetle ile denetle. Fark yoksa "Kaydedildi ve geri okunarak doğrulandı; içerik teyidi sizde" de.',
125
154
  " Sor: 1) başka ders 2) bir alanı değiştir 3) bitir.",
126
155
  ].join("\n");
127
156
  const ciktiAkisi = (dersKodu) => [
@@ -129,7 +158,7 @@ const ciktiAkisi = (dersKodu) => [
129
158
  "",
130
159
  "Akış:",
131
160
  "1. Sor: 1) OBS'deki mevcut çıktılarımı okuyalım (tarayıcı aracın varsa OBS girişi için Bologna akışındaki Adım 0'ı izle) 2) çıktılarımı yapıştırayım 3) sıfırdan yaz: ders adı, AKTS, T+U+L ve kısa içerik vereyim.",
132
- "2. bologna_alan_rehberi'ni alan=ciktilar ile çağır: kurallar, Bloom fiilleri, 8-12 sınırı.",
161
+ "2. bologna_alan_rehberi'ni alan=ciktilar ile çağır: kurallar, Bloom fiilleri, 4-10 sınırı (5-8 önerilir).",
133
162
  `3. Sor: 1) her çıktı için seçenek göstereyim 2) yalnız zayıf olanları iyileştireyim 3) numaraları kendim seçeyim ${AKILLI_DOLDURMA_SECENEGI}.`,
134
163
  " 1-3: her çıktı için 2-3 seçenek yaz (farklı Bloom düzeyi ya da ölçme biçimi), türünü (Bilgi, Beceri, Yetkinlik) yaz.",
135
164
  " 4: her çıktı için en iyi ifadeyi kendin seç; hocaya ara soru sorma.",
@@ -142,8 +171,8 @@ const ckPcAkisi = (dersKodu) => [
142
171
  `Odak: ÇK ve PÇ sağlanma düzeyi hesabı.${dersKodu ? ` Ders: ${dersKodu}.` : ""}`,
143
172
  "",
144
173
  "Akış:",
145
- "1. Hocadan Excel not tablosunu kopyalayıp yapıştırmasını iste. İlk satır başlık, her soru bir sütun; başlıkta sınav, soru, ÇK ve tam puan olursa iş biter (ör. Vize S1 (ÇK1) /20).",
146
- " Ad ve numara sütunlarını araç ayrıştırmadan önce atar ve o1, o2 takma adı verir; bunu hocaya tek cümleyle söyle.",
174
+ '1. Hocaya önce şunu söyle: "Kişisel veriyi yapay zekâ sohbetine yapıştırmayın: Excel\'de Ad, Soyad, Numara sütunlarını silip yalnız not sütunlarını yapıştırın ya da verinin tarayıcınızdan çıkmadığı hesaplayıcıyı kullanın: https://akredit.org/deneyin"',
175
+ " Sonra yalnız not sütunlarını yapıştırmasını iste. İlk satır başlık, her soru bir sütun; başlıkta sınav, soru, ÇK ve tam puan olursa iş biter (ör. Vize S1 (ÇK1) /20). Öğrenciler o1, o2 takma adı alır.",
147
176
  "2. Tabloyu not_tablosu_ayristir'e ver.",
148
177
  `3. Sor: 1) eşlemeyi tek tek gözden geçirelim 2) yalnız eksikleri soralım 3) eşlemeyi kendim yazayım ${AKILLI_DOLDURMA_SECENEGI}.`,
149
178
  " 4: başlıktan okunan eşlemeyi ve varsayılanları kullan: eşik 60, sınav ağırlıkları tablodan. Başlıkta olmayan soru-çıktı eşlemesini ve tam puanı tahmin etme; bunları tek mesajda topluca sor.",
@@ -192,7 +221,7 @@ const akreditMenusu = (odak) => {
192
221
  "",
193
222
  "Hocaya tam olarak şunu göster ve yanıtını bekle:",
194
223
  "",
195
- "Merhaba. Bugün neye odaklanalım? Numarasını yazmanız yeter.",
224
+ "Merhaba. Neye odaklanalım? Numarasını yazmanız yeter.",
196
225
  ...ODAKLAR.map(([no, ad]) => `${no}) ${ad}`),
197
226
  `${ODAKLAR.length + 1}) Başka bir şey: tüm araçları kısaca listele`,
198
227
  "",
@@ -249,5 +278,46 @@ export const ACIK_ISTEMLER = [
249
278
  metin: () => ile(dilAkisi()),
250
279
  },
251
280
  ];
281
+ /** Kısa kurallar: akredit_basla yanıtında (istem desteklemeyen istemciler için) yer alır. */
282
+ export const KISA_KURALLAR = [
283
+ "Hoca seçmeden hiçbir akışa başlama; menüyü aynen göster ve numara bekle.",
284
+ "Kısa, sade Türkçe yaz; araç adı, JSON ve teknik terim gösterme. Her mesajda tek iş iste.",
285
+ "Uydurma yok: kişi, kitap, ISBN, DOI, tarih, sayı uydurma. Bilinmeyen EKSİK kalır.",
286
+ "Değerlendirme yöntem ve yüzdeleri, uygulama/lab, kullanılan kaynaklar ve sayfada yoksa amaç/içerik sayfadan okunur; yoksa hocaya TEK mesajda, numaralı ve varsayılan seçenekli sorulur.",
287
+ "Ders adından ürettiğin her şeyi 'öneri' etiketiyle göster. Araç sonucu 'biçim denetiminden geçti; içerik teyidi: hoca' diye sunulur.",
288
+ "Sayıları yalnız araç yanıtından al.",
289
+ "Kaynak yalnız 'katalogda_bulundu' ise öneri olarak, ya da hoca kullandığını söylerse 'hoca_beyani' olarak yazılır.",
290
+ "Giriş bilgisi (parola, doğrulama kodu, çerez) istenmez; hoca yazarsa kullanma, araçlara gönderme.",
291
+ "OBS'ye yazmadan önce alan alan önceki/yeni değer özeti göster; hoca bir kez 'kaydet' der. Kayıttan sonra geri oku ve obs_karsilastir ile doğrula. Gönder düğmesine hoca basar.",
292
+ "Öğrenci adı ya da numarası sohbete yapıştırılmaz; not tablosunda yalnız not sütunları olur.",
293
+ ];
294
+ /** akredit_basla aracının yanıtı: menü, kısa kurallar ve her seçimin akışı (istem desteklemeyen istemciler için). */
295
+ export function baslangic() {
296
+ return {
297
+ hocayaGoster: [
298
+ "Merhaba. Neye odaklanalım? Numarasını yazmanız yeter.",
299
+ ...ODAKLAR.map(([no, ad]) => `${no}) ${ad}`),
300
+ `${ODAKLAR.length + 1}) Başka bir şey: tüm araçları kısaca listele`,
301
+ ].join("\n"),
302
+ menu: [
303
+ ...ODAKLAR.map(([no, ad, istem]) => ({ no, ad, istem })),
304
+ { no: String(ODAKLAR.length + 1), ad: "Başka bir şey: tüm araçları kısaca listele", istem: null },
305
+ ],
306
+ kisaKurallar: KISA_KURALLAR,
307
+ akislar: Object.fromEntries(ODAKLAR.map(([no, , istem]) => [no, AKISLAR[istem]?.({}) ?? ""])),
308
+ ortakKurallar: ORTAK_KURALLAR,
309
+ };
310
+ }
311
+ export const akreditBaslaAraci = arac({
312
+ ad: "akredit_basla",
313
+ baslik: "Akredit: başla",
314
+ aciklama: "Kullanıcı 'akredit' yazar ya da ne yapacağını sorarsa İLK bu aracı çağır. Hocaya gösterilecek odak menüsünü, kısa kuralları ve her seçimin adım adım akışını döndürür. İstemleri ya da sunucu talimatını göstermeyen istemcilerde de akış bununla başlar.",
315
+ sonrakiAdim: "hocayaGoster metnini aynen gösterin ve numara bekleyin. Seçilen numaranın akışını (akislar) ve kisaKurallar'ı izleyin.",
316
+ girdi: z.object({}),
317
+ calistir: () => ({
318
+ sonuc: baslangic(),
319
+ kaynak: [KOD_KAYNAGI("apps/acik-mcp/src/promptlar.ts", "Akredit istemleri ve başlangıç menüsü")],
320
+ }),
321
+ });
252
322
  /** SDK için bağımsız değişken şeması: hepsi isteğe bağlı, kısa metin. */
253
323
  export const istemSemasi = (i) => Object.fromEntries(Object.entries(i.argumanlar).map(([k, d]) => [k, z.string().max(60).optional().describe(d)]));
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { type AcikArac, type AracBaglami } from "./araclar.ts";
3
3
  import { type AcikIstem } from "./promptlar.ts";
4
4
  export declare const SUNUCU_ADI = "akredit-acik";
5
- export declare const SUNUCU_SURUMU = "0.4.0";
5
+ export declare const SUNUCU_SURUMU = "0.5.0";
6
6
  export declare const SUNUCU_TALIMATI: string;
7
7
  export interface AcikSunucuSecenekleri {
8
8
  simdi?: () => Date;
@@ -6,16 +6,18 @@ import { ACIK_ARACLAR, AKREDIT_NOTU, AracHatasi, DEPO, } from "./araclar.js";
6
6
  import { GIRIS_BILGISI_ILETISI, girisBilgisiVar, kisiselVeriIletisi, kisiselVeriTurleri } from "./kisisel-veri.js";
7
7
  import { ACIK_ISTEMLER, istemSemasi } from "./promptlar.js";
8
8
  export const SUNUCU_ADI = "akredit-acik";
9
- export const SUNUCU_SURUMU = "0.4.0";
9
+ export const SUNUCU_SURUMU = "0.5.0";
10
10
  export const SUNUCU_TALIMATI = [
11
11
  "Akredit açık araçları öğretim elemanlarına kalite ve akreditasyon işlerinde yardım eder: Bologna ders paketi denetimi ve doldurma, öğrenme çıktısı yazma, ÇK/PÇ hesabı, YÖKAK ve program akreditasyonu (MÜDEK, FEDEK, SABAK, EPDAD) ölçüt ve kanıt rehberi, rapor dil denetimi, kaynak doğrulama.",
12
- "Hoca ne yapacağını söylemediyse ya da 'akredit' yazarsa 'akredit' istemindeki araç menüsünü göster ve önce hangi aracı kullanmak istediğini sor.",
12
+ "Hoca ne yapacağını söylemediyse ya da 'akredit' yazarsa İLK akredit_basla aracını çağır, menüyü göster ve önce hangi aracı kullanmak istediğini sor.",
13
13
  "Amaç: hocanın en az müdahalesiyle işi hızlı ama denetimli ve teyitli bitirmek. Her seçim menüsünde son seçenek '4) Hepsini akıllıca doldur (önerilen)' olur: her alan için en iyi seçeneği sen seçersin, araçla denetlersin, geçmeyeni düzeltip yeniden denetlersin, sonunda tek özet gösterip değiştirmek istediği numarayı sorarsın.",
14
- "Giriş bilgisi: hocadan kullanıcı adı, parola, doğrulama kodu ya da çerez asla isteme; hoca yazarsa kullanma, araçlara gönderme, silmesini ve parolasını değiştirmesini öner. Hoca giriş bilgisini yalnız OBS'nin kendi ekranına kendisi yazar. CAPTCHA'yı hoca çözer. OBS'de Kaydet'e yalnız hocanın açık izniyle basarsın; onaya Gönder düğmesine hoca basar.",
14
+ "Yalnız hocanın bilebileceği ve resmî taahhüt olan bilgiler (değerlendirme yöntem ve yüzdeleri, uygulama/lab, kullanılan kaynaklar, sayfada yoksa amaç ve içerik) sayfadan okunur; yoksa hocaya TEK mesajda, numaralı ve varsayılan seçenekli sorulur, uydurulmaz. Ders adından üretilen her şey 'öneri' etiketiyle gösterilir. Araç sonucu 'biçim denetiminden geçti; içerik teyidi: hoca' diye sunulur.",
15
+ "Giriş bilgisi: hocadan kullanıcı adı, parola, doğrulama kodu ya da çerez asla isteme. Sunucu bunları istemez; görülürse çağrıyı reddeder ve kaydetmez. Hoca yazarsa kullanma, araçlara gönderme, silmesini ve parolasını değiştirmesini öner. Hoca giriş bilgisini yalnız OBS'nin kendi ekranına kendisi yazar. CAPTCHA'yı hoca çözer.",
16
+ "OBS'ye yazma: önce alan alan önceki ve yeni değer özetini göster, hoca bir kez 'kaydet' der; ancak ondan sonra Kaydet'e basarsın. Kayıttan sonra sayfayı geri okuyup obs_karsilastir ile doğrula; ders kodu ya da dönem değişmişse yazmayı durdur. Doğrulanmamış kaynak ve teyitsiz değerlendirme yüzdesi yazılmaz. Onaya Gönder düğmesine hoca basar.",
15
17
  "Tarayıcı aracın varsa OBS sayfasını okuyup formu doldurabilirsin; yoksa ya da engel çıkarsa engel_coz ve kurulum_yardimi araçlarının adımlarını hocaya sırayla, tek tek göster. Tarayıcı yoksa OBS'ye yazma alan alan kopyalamaya düşer.",
16
18
  "Sunucu model çağırmaz ve metin üretmez: seçenekleri sen yazarsın, sunucu kural ve kalıp verir, denetler ve sayıları hesaplar. Sayıları yalnız araç sonucundan al.",
17
- "Bologna sayfasındaki Dersi Verenler, Yardımcılar ve Koordinatör satırları yayımlanmış görev bilgisidir: olduğu gibi okunur, korunur, değiştirilmez. Öğrenci adı ve numarası içeren not tabloları takma adla işlenir. Sunucu istek içeriğini günlüğe yazmaz.",
18
- "Kaynak önerirken hocaya kitap adını sorma: kendi web arama aracınla ders içeriğine uygun eserleri bul, kaynak_dogrula ile doğrula, yalnız doğrulananları künyesi ve bağlantısıyla göster. Doğrulanamayan kaynağı asla önerme. Ölçüt metnini yalnız olcut_getir sonucundan alıntılayın; DOĞRULANMADI ya da 'Belgeden okunamadı' işaretli metni tahminle doldurmayın. Programın disiplinini kurum seçer. Kanıtı olmayan cümle EKSİK kalır. Kitap, kişi, tarih uydurmayın.",
19
+ "Bologna sayfasındaki Dersi Verenler, Yardımcılar ve Koordinatör satırları yayımlanmış görev bilgisidir: olduğu gibi okunur, korunur, değiştirilmez. Öğrenci adı ve numarası sohbete yapıştırılmaz: not tablosunda yalnız not sütunları olur; kimlik sütunu görülürse tablo işlenmez. Sunucu istek içeriğini günlüğe yazmaz.",
20
+ "Kaynak: önce sayfadaki ve hocanın kullandığı kaynaklar. Öneri gerekirse kendi web arama aracınla aday bul, kaynak_dogrula ile sorgula, yalnız 'katalogda_bulundu' dönenleri künyesi ve bağlantısıyla 'öneri' olarak göster; uygunluğuna hoca karar verir. Katalogda bulunmayanı kendin önerme; hoca kullandığını söylerse 'hoca_beyani' olarak yaz. Ölçüt metnini yalnız olcut_getir sonucundan alıntılayın; DOĞRULANMADI ya da 'Belgeden okunamadı' işaretli metni tahminle doldurmayın. Programın disiplinini kurum seçer. Kanıtı olmayan cümle EKSİK kalır. Kitap, kişi, tarih uydurmayın.",
19
21
  "Sunucu kullanıcıdan adres çekmez: Bologna sayfasını hoca yapıştırır ya da siz kendi web aracınızla okursunuz. Tek dış istek kaynak_dogrula'nın sabit katalog adresleridir (Crossref, Open Library, Google Books). Onay ve dış kuruma gönderim hocadadır. Akredit'in YÖKAK ile bağı yoktur.",
20
22
  ].join(" ");
21
23
  const metinSonuc = (veri, isError = false) => ({
@@ -29,6 +31,9 @@ function hazirla(a, girdi) {
29
31
  girdi = ayik.girdi;
30
32
  if (girisBilgisiVar(girdi))
31
33
  return { hata: { veri: { hata: GIRIS_BILGISI_ILETISI, kod: "giris_bilgisi", not: AKREDIT_NOTU }, hata: true } };
34
+ const red = a.onDenetim?.(girdi);
35
+ if (red)
36
+ return { hata: { veri: { hata: red, kod: "kisisel_veri", not: AKREDIT_NOTU }, hata: true } };
32
37
  const turler = kisiselVeriTurleri(girdi, a.kunyeAlanlari, a.kisiselVeriKapsami ?? "ogrenci");
33
38
  if (turler.length)
34
39
  return {
@@ -39,6 +39,11 @@ export interface AcikArac<S extends z.ZodObject = z.ZodObject> {
39
39
  atilan: string[];
40
40
  ileti?: string;
41
41
  };
42
+ /**
43
+ * Kişisel veri taramasından önce çalışan red denetimi. İleti dönerse çağrı işlenmeden "kisisel_veri" koduyla
44
+ * reddedilir; değerler hiçbir yere yazılmaz.
45
+ */
46
+ onDenetim?: (girdi: unknown) => string | null;
42
47
  /** Kişisel veri denetiminin kapsamı; varsayılan "ogrenci" (bkz. kisisel-veri.ts). */
43
48
  kisiselVeriKapsami?: KisiselVeriKapsami;
44
49
  /** Araç dış ağa (yalnız izinli hostlara) istek atar mı. */
@@ -34,31 +34,35 @@ const ISTEMCI_ADI = {
34
34
  claude_code: "Claude Code",
35
35
  };
36
36
  const BAGLAYICI = {
37
+ // Anthropic yardım belgesi "Get started with custom connectors using remote MCP" (erişim 2026-11-05).
37
38
  claude_web: [
38
39
  ["claude.ai adresine girin ve oturum açın.", "Sol tarafta sohbet listesi, ortada yazı kutusu."],
39
- ["Sol alttaki adınıza tıklayın, Settings (Ayarlar) seçin.", "Ayarlar sayfası açılır.", true],
40
- ["Connectors (Bağlayıcılar) bölümünü açın.", "Bağlı araçların listesi.", true],
41
- ["Add custom connector (Özel bağlayıcı ekle) düğmesine basın.", "Ad ve adres soran küçük bir pencere.", true],
42
- [`Ad olarak Akredit, adres olarak ${MCP_ADRESI} yazın ve Add (Ekle) deyin.`, "Listede Akredit görünür."],
40
+ ["Customize (Özelleştir), ardından Connectors (Bağlayıcılar) bölümünü açın.", "Bağlı araçların listesi."],
41
+ ["+ Add düğmesine, ardından Add custom connector seçeneğine basın.", "Ad ve adres soran pencere."],
42
+ [`Ad olarak Akredit, adres olarak ${MCP_ADRESI} yazın ve Continue deyin.`, "Kimlik doğrulama seçenekleri."],
43
+ ["Authentication altında No sign in seçin ve Add deyin.", "Listede Akredit görünür."],
44
+ [
45
+ "Ücretsiz planda tek özel bağlayıcı eklenebilir. Team ve Enterprise planlarında bağlayıcıyı kuruluş sahibi ekler; siz Connectors listesinde Akredit'i bulup Connect dersiniz.",
46
+ undefined,
47
+ ],
43
48
  [
44
49
  "Yeni sohbet açın. Yazı kutusunun altındaki + düğmesinden Akredit'in açık olduğunu denetleyin.",
45
50
  "Akredit'in yanında açık anahtar.",
46
51
  true,
47
52
  ],
48
53
  [
49
- "Yazı kutusuna akredit yazın ya da + menüsünden Akredit altındaki akredit başlangıcını seçin.",
50
- "Odak menüsü: 1-5 arası seçenek.",
54
+ "akredit yazın. Menü gelmezse Akredit araçlarıyla Bologna paketimi denetle yazın.",
55
+ "Odak menüsü: numaralı seçenekler.",
51
56
  ],
52
57
  ],
53
58
  claude_masaustu: [
54
59
  ["Claude masaüstü uygulamasını açın ve oturum açın.", "claude.ai ile aynı görünüm."],
60
+ ["Customize (Özelleştir), ardından Connectors (Bağlayıcılar) bölümünü açın.", "Bağlayıcı listesi."],
55
61
  [
56
- "Sol alttaki adınızdan Settings (Ayarlar), ardından Connectors (Bağlayıcılar) bölümüne girin.",
57
- "Bağlayıcı listesi.",
58
- true,
62
+ `+ Add, Add custom connector ile ad Akredit, adres ${MCP_ADRESI} yazın; Authentication altında No sign in seçip Add deyin.`,
63
+ "Listede Akredit görünür.",
59
64
  ],
60
- [`Add custom connector ile ad Akredit, adres ${MCP_ADRESI} olarak ekleyin.`, "Listede Akredit görünür.", true],
61
- ["Yeni sohbette akredit yazın.", "Odak menüsü."],
65
+ ["Yeni sohbette akredit yazın. Menü gelmezse Akredit araçlarıyla Bologna paketimi denetle yazın.", "Odak menüsü."],
62
66
  ],
63
67
  claude_chrome: [
64
68
  ["Önce Claude'a Akredit bağlayıcısını ekleyin (Claude web adımları).", "Sohbette Akredit açık."],
@@ -80,20 +84,26 @@ const BAGLAYICI = {
80
84
  true,
81
85
  ],
82
86
  ],
87
+ // OpenAI geliştirici belgesi "Custom MCP server" (erişim 2026-11-05). Hangi planlarda açık olduğu belgede yazmıyor.
83
88
  chatgpt: [
84
- ["chatgpt.com adresinde oturum açın.", "Sohbet ekranı."],
89
+ ["chatgpt.com adresinde, web tarayıcısında oturum açın.", "Sohbet ekranı."],
90
+ ["chatgpt.com/plugins adresini açın; + düğmesine basıp Add custom MCP server seçin.", "Ad ve bağlantı soran form."],
85
91
  [
86
- "Settings (Ayarlar), Apps (Uygulamalar), Advanced settings altında Developer mode (geliştirici kipi) seçeneğini açın.",
87
- "Uygulama oluşturma düğmesi görünür.",
92
+ `Ad olarak Akredit yazın; Connection altında Server URL olarak ${MCP_ADRESI} girin; kimlik doğrulama olarak no authentication seçin.`,
93
+ undefined,
88
94
  true,
89
95
  ],
90
96
  [
91
- `Create (Oluştur) ile yeni uygulama ekleyin: ad Akredit, adres ${MCP_ADRESI}, kimlik doğrulama yok.`,
92
- "Akredit araçları listelenir.",
97
+ "Uyarıyı okuyup I understand and want to continue, ardından Create as a plugin deyin; sonra eklentiyi kurun.",
98
+ "Akredit eklentilerinizde görünür.",
99
+ true,
100
+ ],
101
+ [
102
+ "Bu özelliğin açık olup olmadığı planınıza göre değişebilir. Kurum hesabında çalışma alanı izinleri yöneticiye bağlıdır.",
103
+ undefined,
93
104
  true,
94
105
  ],
95
- ["İş ve kurum hesaplarında bu ayarı yönetici açar; açamıyorsanız yöneticinize sorun.", undefined, true],
96
- ["Yeni sohbette Akredit'i seçin ve akredit yazın.", "Odak menüsü."],
106
+ ["Yeni sohbette @ yazıp Akredit'i seçin, sonra akredit yazın.", "Odak menüsü."],
97
107
  ],
98
108
  cursor: [
99
109
  ["Cursor'da Settings (Ayarlar), ardından MCP bölümünü açın.", "MCP sunucu listesi.", true],
@@ -4,9 +4,25 @@ export interface TaslakDenetimSonucu {
4
4
  bulgular: Bulgu[];
5
5
  isToplam: number;
6
6
  hedef: number;
7
+ /** "guncel" kural setinde iş yükünün kabul aralığı (saat). Kurum değeri verildiyse alt ve üst eşittir. */
8
+ hedefAraligi?: [number, number];
7
9
  }
10
+ export type TaslakKuralSeti = "cli" | "guncel";
11
+ export interface TaslakDenetimSecenekleri {
12
+ /** "cli" kural setinde AKTS başına saat (varsayılan 30). */
13
+ bolen?: number;
14
+ kuralSeti?: TaslakKuralSeti;
15
+ /** "guncel" sette kurumun AKTS başına saati; verilirse iş yükü tam AKTS × bu değer olmalı. */
16
+ kurumBoleni?: number | undefined;
17
+ /** "guncel" sette kurum değeri yoksa AKTS başına saat aralığı; varsayılan [25, 30]. */
18
+ aktsSaat?: readonly [number, number];
19
+ }
20
+ /** Kuralların dayanak notları. Hiçbiri resmî belgeyle doğrulanmadı; bu yüzden hiçbiri "resmî" değildir. */
21
+ export declare const TASLAK_DAYANAK: {
22
+ readonly akredit: "Akredit iç kuralı (packages/cekirdek/src/bologna/taslak.ts); resmî dayanağı DOĞRULANMADI.";
23
+ readonly akts: "AKTS başına 25-30 saat yaygın uygulamadır; resmî metinle DOĞRULANMADI. Kurumun yönergesi önceliklidir.";
24
+ readonly kurum: "Kurumun verdiği AKTS başına saat değeri.";
25
+ };
8
26
  /** Kısmi ya da dışarıdan gelmiş taslak da denetlenebilir; eksik alanlar bulgu üretir. */
9
27
  export type DenetlenecekTaslak = Partial<BolognaTaslagi>;
10
- export declare function taslakDenetle(t: DenetlenecekTaslak, { bolen }?: {
11
- bolen?: number;
12
- }): TaslakDenetimSonucu;
28
+ export declare function taslakDenetle(t: DenetlenecekTaslak, secenek?: TaslakDenetimSecenekleri): TaslakDenetimSonucu;
@@ -1,6 +1,20 @@
1
1
  // Bologna ders sayfası taslağı denetimi (K1-K12 ve diğer kurallar).
2
2
  // apps/cli/lib/bologna-taslak.mjs `taslakDenetle` işlevinin saf, tipli taşıması. Mesajlar aynen korunur:
3
3
  // CLI'deki "K8: metin" dizesi burada { kural: "K8", aciklama: "metin" } olur.
4
+ //
5
+ // İki kural seti vardır (dış inceleme 2026-11-05):
6
+ // - "cli" (varsayılan): CLI ile birebir eşdeğer, eşdeğerlik testleri bunu sınar.
7
+ // - "guncel": açık MCP'nin kullandığı set. ÖÇ sayısı 4-10 geçerli (5-8 önerilir), "-ebilme/-abilme" kabul,
8
+ // "sahip ol/farkında ol/haberdar ol/vakıf ol" ölçülemez, AKTS iş yükü 25-30 saat/AKTS aralığı (kurum değeri
9
+ // verilirse tam o değer). Her bulgu kuralTuru ve dayanak taşır; resmî olduğu doğrulanamayan kural resmî sayılmaz.
10
+ /** Kuralların dayanak notları. Hiçbiri resmî belgeyle doğrulanmadı; bu yüzden hiçbiri "resmî" değildir. */
11
+ export const TASLAK_DAYANAK = {
12
+ akredit: "Akredit iç kuralı (packages/cekirdek/src/bologna/taslak.ts); resmî dayanağı DOĞRULANMADI.",
13
+ akts: "AKTS başına 25-30 saat yaygın uygulamadır; resmî metinle DOĞRULANMADI. Kurumun yönergesi önceliklidir.",
14
+ kurum: "Kurumun verdiği AKTS başına saat değeri.",
15
+ };
16
+ const ONERILEN_CIKTI = [5, 8];
17
+ const GECERLI_CIKTI = [4, 10];
4
18
  const kelime = (s) => String(s ?? "")
5
19
  .trim()
6
20
  .split(/\s+/)
@@ -13,7 +27,13 @@ const OLCULEMEZ = /(bilgi sahibi olur|hakkında bilgi|fikir sahibi olur|(^|\s)(
13
27
  const ZAYIF = /farkındalık/i;
14
28
  // Türkçe çekimli fiille biten çıktı: açıklar, uygular, tasarlar, geliştirebilir…
15
29
  const FIILLE_BITER = /(ar|er|ır|ir|ur|ür)\.?\s*$/i;
16
- export function taslakDenetle(t, { bolen = 30 } = {}) {
30
+ // "guncel" set: "-ebilme/-abilme" kalıbı da kabul (tasarlayabilme, analiz edebilme).
31
+ const FIILLE_BITER_GUNCEL = /(ar|er|ır|ir|ur|ür|abilme|ebilme)\.?\s*$/iu;
32
+ // "guncel" set: "sahip olur", "farkında olur", "haberdar olur", "vakıf olur" (ve -abilir, -ma biçimleri) ölçülemez.
33
+ const OLCULEMEZ_GUNCEL = /(sahibi? ol|sahip ol|farkında ol|haberdar ol|vakıf ol)\p{L}*\.?\s*$/iu;
34
+ export function taslakDenetle(t, secenek = {}) {
35
+ const { bolen = 30 } = secenek;
36
+ const guncel = secenek.kuralSeti === "guncel";
17
37
  const bulgular = [];
18
38
  const hata = (kural, aciklama) => bulgular.push({ duzey: "hata", kural, aciklama });
19
39
  const uyari = (kural, aciklama) => bulgular.push({ duzey: "uyari", kural, aciklama });
@@ -56,9 +76,35 @@ export function taslakDenetle(t, { bolen = 30 } = {}) {
56
76
  const isyuku = t.isyuku ?? [];
57
77
  const isToplam = isyuku.reduce((s, x) => s + Number(x.sayi || 0) * Number(x.sure || 0), 0);
58
78
  const carpan = t.bolen || bolen;
59
- const hedef = Number(t.akts) * carpan;
60
- if (isToplam !== hedef)
61
- hata("K8", `Toplam iş yükü ${isToplam} saat, AKTS ${t.akts} × ${carpan} = ${hedef} olmalı. AKTS'yi değil saat dağılımını değiştir.`);
79
+ let hedef = Number(t.akts) * carpan;
80
+ let hedefAraligi;
81
+ if (!guncel) {
82
+ if (isToplam !== hedef)
83
+ hata("K8", `Toplam iş yükü ${isToplam} saat, AKTS ${t.akts} × ${carpan} = ${hedef} olmalı. AKTS'yi değil saat dağılımını değiştir.`);
84
+ }
85
+ else {
86
+ const kurum = secenek.kurumBoleni ?? t.bolen;
87
+ const [alt, ust] = secenek.aktsSaat ?? [25, 30];
88
+ const akts = Number(t.akts);
89
+ hedefAraligi = kurum ? [akts * kurum, akts * kurum] : [akts * alt, akts * ust];
90
+ hedef = kurum ? akts * kurum : akts * ust;
91
+ if (kurum && isToplam !== hedef)
92
+ bulgular.push({
93
+ duzey: "hata",
94
+ kural: "K8",
95
+ aciklama: `Toplam iş yükü ${isToplam} saat, AKTS ${t.akts} × ${kurum} (kurum değeri) = ${hedef} olmalı. AKTS'yi değil saat dağılımını değiştir.`,
96
+ kuralTuru: "kurum",
97
+ dayanak: TASLAK_DAYANAK.kurum,
98
+ });
99
+ else if (!kurum && (isToplam < hedefAraligi[0] || isToplam > hedefAraligi[1]))
100
+ bulgular.push({
101
+ duzey: "hata",
102
+ kural: "K8",
103
+ aciklama: `Toplam iş yükü ${isToplam} saat, AKTS ${t.akts} için ${hedefAraligi[0]}-${hedefAraligi[1]} saat (AKTS başına ${alt}-${ust}) olmalı. AKTS'yi değil saat dağılımını değiştir.`,
104
+ kuralTuru: "Akredit önerisi",
105
+ dayanak: TASLAK_DAYANAK.akts,
106
+ });
107
+ }
62
108
  const dersSuresi = isyuku.find((x) => /ders süresi/i.test(x.ad));
63
109
  if (dersSuresi && Number(dersSuresi.sure) !== teorik + uygulama + lab) {
64
110
  uyari("K8", `Ders süresi satırı haftada ${dersSuresi.sure} saat, T+U+L toplamı ${teorik + uygulama + lab}.`);
@@ -69,14 +115,22 @@ export function taslakDenetle(t, { bolen = 30 } = {}) {
69
115
  hata("K6", `Ders yapısı yüzdeleri toplamı %${yapiToplam}, tam %100 olmalı.`);
70
116
  // K4, K5: öğrenme çıktıları.
71
117
  const cik = t.ciktilar ?? [];
72
- if (cik.length < 8 || cik.length > 12)
73
- hata("K5", `${cik.length} öğrenme çıktısı var, 8-12 olmalı.`);
118
+ if (!guncel) {
119
+ if (cik.length < 8 || cik.length > 12)
120
+ hata("K5", `${cik.length} öğrenme çıktısı var, 8-12 olmalı.`);
121
+ }
122
+ else if (cik.length < GECERLI_CIKTI[0] || cik.length > GECERLI_CIKTI[1])
123
+ hata("K5", `${cik.length} öğrenme çıktısı var, ${GECERLI_CIKTI[0]}-${GECERLI_CIKTI[1]} olmalı (${ONERILEN_CIKTI[0]}-${ONERILEN_CIKTI[1]} önerilir).`);
124
+ else if (cik.length < ONERILEN_CIKTI[0] || cik.length > ONERILEN_CIKTI[1])
125
+ uyari("K5", `${cik.length} öğrenme çıktısı var; ${ONERILEN_CIKTI[0]}-${ONERILEN_CIKTI[1]} önerilir.`);
126
+ const olculemez = (m) => OLCULEMEZ.test(m) || (guncel && OLCULEMEZ_GUNCEL.test(m));
127
+ const fiilleBiter = (m) => (guncel ? FIILLE_BITER_GUNCEL : FIILLE_BITER).test(m);
74
128
  cik.forEach((c, i) => {
75
129
  if (c.kod !== `Ö${i + 1}`)
76
130
  hata("K5", `Çıktı numaralandırması kesintili: ${i + 1}. sırada "${c.kod}" var.`);
77
131
  if (bos(c.tr))
78
132
  hata("K3", `${c.kod} boş.`);
79
- else if (OLCULEMEZ.test(c.tr) || !FIILLE_BITER.test(c.tr.trim()))
133
+ else if (olculemez(c.tr) || !fiilleBiter(c.tr.trim()))
80
134
  hata("K4", `${c.kod} ölçülebilir bir eylem fiiliyle bitmiyor: "${c.tr}"`);
81
135
  else if (ZAYIF.test(c.tr))
82
136
  uyari("K4", `${c.kod} "farkındalık" ifadesi ölçmesi zor. Gözlenebilir bir eylemle yazmayı düşün.`);
@@ -164,6 +218,13 @@ export function taslakDenetle(t, { bolen = 30 } = {}) {
164
218
  if (ascii.length)
165
219
  hata("Türkçe", `Türkçe karakteri düşürülmüş kelimeler: ${ascii.slice(0, 10).join(", ")}.`);
166
220
  // CLI hataları önce, uyarıları sonra listeler; sıra korunur.
167
- const sirali = [...bulgular.filter((b) => b.duzey === "hata"), ...bulgular.filter((b) => b.duzey !== "hata")];
168
- return { bulgular: sirali, isToplam, hedef };
221
+ const etiketli = guncel
222
+ ? bulgular.map((b) => ({
223
+ ...b,
224
+ kuralTuru: b.kuralTuru ?? "Akredit önerisi",
225
+ dayanak: b.dayanak ?? TASLAK_DAYANAK.akredit,
226
+ }))
227
+ : bulgular;
228
+ const sirali = [...etiketli.filter((b) => b.duzey === "hata"), ...etiketli.filter((b) => b.duzey !== "hata")];
229
+ return { bulgular: sirali, isToplam, hedef, ...(hedefAraligi ? { hedefAraligi } : {}) };
169
230
  }
@@ -187,4 +187,12 @@ export interface Bulgu {
187
187
  /** Bulgunun konusu: ders kodu, çıktı kodu, paragraf numarası. */
188
188
  konu?: string;
189
189
  aciklama: string;
190
+ /**
191
+ * Kuralın dayanağının türü. "resmî" yalnız resmî belgeyle doğrulanmış kuraldır; doğrulanamayan kural resmî
192
+ * sayılmaz. "kurum": kurumun verdiği değer (ör. AKTS başına saat). "Akredit önerisi": iç kural.
193
+ */
194
+ kuralTuru?: KuralTuru;
195
+ /** Kuralın kaynağı (belge, kod yolu ya da "DOĞRULANMADI" notu). */
196
+ dayanak?: string;
190
197
  }
198
+ export type KuralTuru = "resmî" | "kurum" | "Akredit önerisi";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akredit/acik-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Herkese açık Akredit MCP sunucusu: hesapsız, salt okuma, deterministik araçlar (ölçüt, Bologna denetimi, ÇK/PÇ hesabı, rapor dil denetimi). Kurum verisine erişmez, model çağırmaz.",
@@ -37,8 +37,8 @@
37
37
  "devDependencies": {
38
38
  "@akredit/baglayicilar": "0.1.0",
39
39
  "@akredit/cekirdek": "0.1.0",
40
- "@akredit/cerceveler": "0.1.0",
41
40
  "@akredit/olcme": "0.1.0",
41
+ "@akredit/cerceveler": "0.1.0",
42
42
  "@akredit/yz": "0.1.0"
43
43
  },
44
44
  "publishConfig": {