prpm-dbp 0.17.0 → 0.18.1
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.
- package/SKILL.md +186 -0
- package/bin/cli.js +70 -1
- package/package.json +1 -1
- package/src/gate.js +138 -0
- package/src/install.js +48 -1
- package/src/prpm.js +19 -3
- package/src/tukar.js +76 -3
package/SKILL.md
CHANGED
|
@@ -9,6 +9,59 @@ Sumber kebenaran tunggal untuk ejaan BM dan Jawi ialah PRPM DBP, bukan ingatan m
|
|
|
9
9
|
|
|
10
10
|
Model boleh hasilkan Jawi yang nampak betul tetapi salah, dan pembaca tidak dapat kesan. Skill ini tukar tekaan jadi semakan.
|
|
11
11
|
|
|
12
|
+
## Sebelum menghantar apa-apa
|
|
13
|
+
|
|
14
|
+
Jalankan `npx prpm-dbp gate <fail> --luar-talian` ke atas **keseluruhan teks**. Bukan sebahagian. Bukan perkataan yang anda rasa berisiko.
|
|
15
|
+
|
|
16
|
+
Ejaan yang salah ialah tepat ejaan yang anda **tidak** syak. Kalau anda syak, anda sudah menyemaknya.
|
|
17
|
+
|
|
18
|
+
Ini termasuk ejaan yang anda tulis dalam **kod, ujian dan dokumentasi**, bukan hanya dalam teks untuk pengguna. Semasa membina pakej ini sendiri, satu nilai Jawi ditulis ke dalam ujian daripada ingatan tanpa disemak. Nilainya kebetulan betul. Kebetulan bukan proses.
|
|
19
|
+
|
|
20
|
+
**Apa yang berlaku apabila langkah ini dilangkau:**
|
|
21
|
+
|
|
22
|
+
Satu agen menggunakan skill ini sepanjang sesi penuh untuk menukar Jawi ke Rumi, kemudian menghantar 549 kad RPH Pendidikan Islam ke pangkalan data produksi dengan **273 ejaan yang salah**.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pusingan 1 sunnah x206, hadith x16, fardhu x2 -> masuk produksi
|
|
26
|
+
pusingan 2 tayammum, dhuha, qadha, redha, iddah x90 -> masuk produksi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas dalam PRPM: `sunnah` ialah rujukan silang ke `sunah`, `hadith` sepatutnya `hadis`, `fardhu` sepatutnya `fardu`, `tayammum` sepatutnya `tayamum`, `dhuha` sepatutnya `duha`, `qadha` sepatutnya `qada`.
|
|
30
|
+
|
|
31
|
+
Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
|
|
32
|
+
|
|
33
|
+
`gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
|
|
34
|
+
|
|
35
|
+
## Kuatkuasa automatik
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx prpm-dbp install --hook
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Memasang hook PostToolUse yang menjalankan `gate --luar-talian` ke atas setiap fail `.md`, `.txt` atau `.json` yang ditulis atau disunting. Ia **melaporkan** ralat kepada agen tanpa menyekat tulisan: menyekat atas kamus yang tidak lengkap akan menghalang kerja yang sah, manakala melaporkan sudah memadai kerana agen membaca output hook.
|
|
42
|
+
|
|
43
|
+
Untuk menampal sendiri: `npx prpm-dbp hook-json`.
|
|
44
|
+
|
|
45
|
+
Panaskan cache **sekali** semasa pemasangan, bukan semasa gate berjalan:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx prpm-dbp warm kosa-kata-projek.txt
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Gate atas cache panas mengambil 0.19 saat bagi 406 perkataan. Itu cukup pantas untuk setiap penyuntingan. Gate yang perlahan akan dimatikan orang.
|
|
52
|
+
|
|
53
|
+
## Tiga keadaan gate, bukan dua
|
|
54
|
+
|
|
55
|
+
| Medan | Maksud | Kesan |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `lulusDbp` | ada dalam PRPM, ejaan padan | — |
|
|
58
|
+
| `ralat` | ada dalam PRPM dengan ejaan lain, entri rujukan silang `®`, atau tiada dalam PRPM mahupun dataset | **exit 1** |
|
|
59
|
+
| `tidakDapatDisahkan` | tiada dalam cache, nama khas, akronim, atau tiada entri PRPM tetapi wujud dalam dataset | dilapor; exit 1 jika melebihi `--ambang` |
|
|
60
|
+
|
|
61
|
+
`tidakDapatDisahkan` **tidak pernah lulus senyap**. Gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih bahaya daripada tiada gate. Gunakan `--ambang 10` supaya cache sejuk menggagalkan gate.
|
|
62
|
+
|
|
63
|
+
PRPM tiada entri untuk kata tugas paling asas: `dan`, `di`, `ke`, `yang`, `atau`. Menandakannya sebagai ralat menjadikan gate mustahil dipakai. Pembezanya bersumber, bukan senarai putih: kata tugas itu **semua** wujud dalam dataset, manakala `hadith`, `fardhu`, `tayammum`, `dhuha`, `qadha`, `redha` dan `iddah` **tiada satu pun**.
|
|
64
|
+
|
|
12
65
|
## Prosedur wajib
|
|
13
66
|
|
|
14
67
|
Ikut ini setiap kali, jangan langkau langkah.
|
|
@@ -184,6 +237,59 @@ Ejaan yang disimpan begini ditandakan datang daripada pengguna, bukan daripada D
|
|
|
184
237
|
|
|
185
238
|
Bagi nama berasal Arab, ejaan Jawinya memang ejaan Arab asal, jadi ia nampak seperti dikekalkan. Bagi nama bukan Arab ia ditransliterasi penuh: `Kuala Lumpur` menjadi `کوالا لومڤور`.
|
|
186
239
|
|
|
240
|
+
## Sebelum menghantar apa-apa
|
|
241
|
+
|
|
242
|
+
Jalankan `npx prpm-dbp gate <fail> --luar-talian` ke atas **keseluruhan teks**. Bukan sebahagian. Bukan perkataan yang anda rasa berisiko.
|
|
243
|
+
|
|
244
|
+
Ejaan yang salah ialah tepat ejaan yang anda **tidak** syak. Kalau anda syak, anda sudah menyemaknya.
|
|
245
|
+
|
|
246
|
+
Ini termasuk ejaan yang anda tulis dalam **kod, ujian dan dokumentasi**, bukan hanya dalam teks untuk pengguna. Semasa membina pakej ini sendiri, satu nilai Jawi ditulis ke dalam ujian daripada ingatan tanpa disemak. Nilainya kebetulan betul. Kebetulan bukan proses.
|
|
247
|
+
|
|
248
|
+
**Apa yang berlaku apabila langkah ini dilangkau:**
|
|
249
|
+
|
|
250
|
+
Satu agen menggunakan skill ini sepanjang sesi penuh untuk menukar Jawi ke Rumi, kemudian menghantar 549 kad RPH Pendidikan Islam ke pangkalan data produksi dengan **273 ejaan yang salah**.
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
pusingan 1 sunnah x206, hadith x16, fardhu x2 -> masuk produksi
|
|
254
|
+
pusingan 2 tayammum, dhuha, qadha, redha, iddah x90 -> masuk produksi
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas dalam PRPM: `sunnah` ialah rujukan silang ke `sunah`, `hadith` sepatutnya `hadis`, `fardhu` sepatutnya `fardu`, `tayammum` sepatutnya `tayamum`, `dhuha` sepatutnya `duha`, `qadha` sepatutnya `qada`.
|
|
258
|
+
|
|
259
|
+
Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
|
|
260
|
+
|
|
261
|
+
`gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
|
|
262
|
+
|
|
263
|
+
## Kuatkuasa automatik
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
npx prpm-dbp install --hook
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Memasang hook PostToolUse yang menjalankan `gate --luar-talian` ke atas setiap fail `.md`, `.txt` atau `.json` yang ditulis atau disunting. Ia **melaporkan** ralat kepada agen tanpa menyekat tulisan: menyekat atas kamus yang tidak lengkap akan menghalang kerja yang sah, manakala melaporkan sudah memadai kerana agen membaca output hook.
|
|
270
|
+
|
|
271
|
+
Untuk menampal sendiri: `npx prpm-dbp hook-json`.
|
|
272
|
+
|
|
273
|
+
Panaskan cache **sekali** semasa pemasangan, bukan semasa gate berjalan:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
npx prpm-dbp warm kosa-kata-projek.txt
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Gate atas cache panas mengambil 0.19 saat bagi 406 perkataan. Itu cukup pantas untuk setiap penyuntingan. Gate yang perlahan akan dimatikan orang.
|
|
280
|
+
|
|
281
|
+
## Tiga keadaan gate, bukan dua
|
|
282
|
+
|
|
283
|
+
| Medan | Maksud | Kesan |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| `lulusDbp` | ada dalam PRPM, ejaan padan | — |
|
|
286
|
+
| `ralat` | ada dalam PRPM dengan ejaan lain, entri rujukan silang `®`, atau tiada dalam PRPM mahupun dataset | **exit 1** |
|
|
287
|
+
| `tidakDapatDisahkan` | tiada dalam cache, nama khas, akronim, atau tiada entri PRPM tetapi wujud dalam dataset | dilapor; exit 1 jika melebihi `--ambang` |
|
|
288
|
+
|
|
289
|
+
`tidakDapatDisahkan` **tidak pernah lulus senyap**. Gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih bahaya daripada tiada gate. Gunakan `--ambang 10` supaya cache sejuk menggagalkan gate.
|
|
290
|
+
|
|
291
|
+
PRPM tiada entri untuk kata tugas paling asas: `dan`, `di`, `ke`, `yang`, `atau`. Menandakannya sebagai ralat menjadikan gate mustahil dipakai. Pembezanya bersumber, bukan senarai putih: kata tugas itu **semua** wujud dalam dataset, manakala `hadith`, `fardhu`, `tayammum`, `dhuha`, `qadha`, `redha` dan `iddah` **tiada satu pun**.
|
|
292
|
+
|
|
187
293
|
## Prosedur wajib
|
|
188
294
|
|
|
189
295
|
Ikut ini setiap kali, jangan langkau langkah.
|
|
@@ -319,6 +425,59 @@ Dua puluh dua kes kabur yang tinggal itu sah: `قلقله` boleh dibaca `qalqala
|
|
|
319
425
|
|
|
320
426
|
**Sifar perkataan hilang atau rosak.**
|
|
321
427
|
|
|
428
|
+
## Sebelum menghantar apa-apa
|
|
429
|
+
|
|
430
|
+
Jalankan `npx prpm-dbp gate <fail> --luar-talian` ke atas **keseluruhan teks**. Bukan sebahagian. Bukan perkataan yang anda rasa berisiko.
|
|
431
|
+
|
|
432
|
+
Ejaan yang salah ialah tepat ejaan yang anda **tidak** syak. Kalau anda syak, anda sudah menyemaknya.
|
|
433
|
+
|
|
434
|
+
Ini termasuk ejaan yang anda tulis dalam **kod, ujian dan dokumentasi**, bukan hanya dalam teks untuk pengguna. Semasa membina pakej ini sendiri, satu nilai Jawi ditulis ke dalam ujian daripada ingatan tanpa disemak. Nilainya kebetulan betul. Kebetulan bukan proses.
|
|
435
|
+
|
|
436
|
+
**Apa yang berlaku apabila langkah ini dilangkau:**
|
|
437
|
+
|
|
438
|
+
Satu agen menggunakan skill ini sepanjang sesi penuh untuk menukar Jawi ke Rumi, kemudian menghantar 549 kad RPH Pendidikan Islam ke pangkalan data produksi dengan **273 ejaan yang salah**.
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
pusingan 1 sunnah x206, hadith x16, fardhu x2 -> masuk produksi
|
|
442
|
+
pusingan 2 tayammum, dhuha, qadha, redha, iddah x90 -> masuk produksi
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Semuanya kata lazim dalam bahan Pendidikan Islam. Setiap satu ada jawapan tegas dalam PRPM: `sunnah` ialah rujukan silang ke `sunah`, `hadith` sepatutnya `hadis`, `fardhu` sepatutnya `fardu`, `tayammum` sepatutnya `tayamum`, `dhuha` sepatutnya `duha`, `qadha` sepatutnya `qada`.
|
|
446
|
+
|
|
447
|
+
Alat untuk menangkap kesemuanya ada di tangan agen itu sepanjang masa. Ia tidak pernah dijalankan ke atas teks Rumi keluarannya sendiri. Kedua-dua pusingan ditemui kerana **pengguna bertanya**, bukan kerana proses menangkapnya.
|
|
448
|
+
|
|
449
|
+
`gate` keluar dengan kod 1 apabila ada ralat, jadi ia boleh dipasang sebagai hook atau langkah CI. Peraturan yang bergantung pada agen ingat sendiri akan gagal, dan gagal secara senyap.
|
|
450
|
+
|
|
451
|
+
## Kuatkuasa automatik
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
npx prpm-dbp install --hook
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Memasang hook PostToolUse yang menjalankan `gate --luar-talian` ke atas setiap fail `.md`, `.txt` atau `.json` yang ditulis atau disunting. Ia **melaporkan** ralat kepada agen tanpa menyekat tulisan: menyekat atas kamus yang tidak lengkap akan menghalang kerja yang sah, manakala melaporkan sudah memadai kerana agen membaca output hook.
|
|
458
|
+
|
|
459
|
+
Untuk menampal sendiri: `npx prpm-dbp hook-json`.
|
|
460
|
+
|
|
461
|
+
Panaskan cache **sekali** semasa pemasangan, bukan semasa gate berjalan:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
npx prpm-dbp warm kosa-kata-projek.txt
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Gate atas cache panas mengambil 0.19 saat bagi 406 perkataan. Itu cukup pantas untuk setiap penyuntingan. Gate yang perlahan akan dimatikan orang.
|
|
468
|
+
|
|
469
|
+
## Tiga keadaan gate, bukan dua
|
|
470
|
+
|
|
471
|
+
| Medan | Maksud | Kesan |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| `lulusDbp` | ada dalam PRPM, ejaan padan | — |
|
|
474
|
+
| `ralat` | ada dalam PRPM dengan ejaan lain, entri rujukan silang `®`, atau tiada dalam PRPM mahupun dataset | **exit 1** |
|
|
475
|
+
| `tidakDapatDisahkan` | tiada dalam cache, nama khas, akronim, atau tiada entri PRPM tetapi wujud dalam dataset | dilapor; exit 1 jika melebihi `--ambang` |
|
|
476
|
+
|
|
477
|
+
`tidakDapatDisahkan` **tidak pernah lulus senyap**. Gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih bahaya daripada tiada gate. Gunakan `--ambang 10` supaya cache sejuk menggagalkan gate.
|
|
478
|
+
|
|
479
|
+
PRPM tiada entri untuk kata tugas paling asas: `dan`, `di`, `ke`, `yang`, `atau`. Menandakannya sebagai ralat menjadikan gate mustahil dipakai. Pembezanya bersumber, bukan senarai putih: kata tugas itu **semua** wujud dalam dataset, manakala `hadith`, `fardhu`, `tayammum`, `dhuha`, `qadha`, `redha` dan `iddah` **tiada satu pun**.
|
|
480
|
+
|
|
322
481
|
## Prosedur wajib
|
|
323
482
|
|
|
324
483
|
Ikut ini setiap kali, jangan langkau langkah.
|
|
@@ -457,6 +616,33 @@ Diukur pada 168 baris DSKP sebenar: token yang langsung tidak dikenali turun dar
|
|
|
457
616
|
|
|
458
617
|
Kegunaannya bukan menjadikan alat lebih longgar. Ia membezakan **"ini surah, cuma dieja lain"** daripada **"saya tidak kenal benda ini"** — dua keadaan yang dahulu keluar sama sebagai `«...»`.
|
|
459
618
|
|
|
619
|
+
## Kekaburan dalam teks domain
|
|
620
|
+
|
|
621
|
+
`--pilih` menggunakan kekerapan korpus. Dalam teks am ia betul hampir sentiasa. Dalam **teks domain ia gagal tepat di tempat paling penting**: setiap istilah khusus kalah kepada kata lazim yang berkongsi rangka konsonan.
|
|
622
|
+
|
|
623
|
+
Diukur pada DSKP KKQ/PQS: salah untuk 5 daripada 16 pasangan taksa, dan kelima-limanya ialah inti pelajaran.
|
|
624
|
+
|
|
625
|
+
```
|
|
626
|
+
اسم -> asam patut isim "hamzah wasal pada kalimah asam dan faal"
|
|
627
|
+
فعل -> faal patut fiil
|
|
628
|
+
رسم -> rasam patut Rasm (Uthmani)
|
|
629
|
+
سنة -> sanat patut sunah
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Gunakan glosari domain. Ia **mengatasi segalanya**, termasuk lapisan 1:
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
npx prpm-dbp rumi dskp.txt --pilih --glosari istilah-kkq.json
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
```json
|
|
639
|
+
{ "اسم": "isim", "فعل": "fiil", "رسم": "rasm", "سنة": "sunah" }
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Setiap penggunaan direkod dalam medan `dariGlosari`, jadi ia boleh diaudit. Bina glosari itu sekali untuk domain anda; ia lebih murah daripada membetulkan output berulang kali, dan ia menjadi rekod: siapa membenarkan istilah apa.
|
|
643
|
+
|
|
644
|
+
Kekerapan kekal sebagai pemecah seri sahaja, dan setiap keputusan tetap dilapor dalam `kabur` untuk semakan.
|
|
645
|
+
|
|
460
646
|
## Rantai kepercayaan
|
|
461
647
|
|
|
462
648
|
Setiap ejaan Jawi yang dikeluarkan membawa asal-usulnya. Jangan runtuhkan lapisan ini.
|
package/bin/cli.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
5
|
-
import { pasangSkill } from '../src/install.js'
|
|
5
|
+
import { HOOK, pasangHook, pasangSkill } from '../src/install.js'
|
|
6
6
|
import { banding, cariKamus } from '../src/kamus.js'
|
|
7
7
|
import { keJawi, keRumi, semakJawi } from '../src/tukar.js'
|
|
8
8
|
import { huraiTsv, muatDataset, simpanDataset } from '../src/dataset.js'
|
|
9
9
|
import { NAMA_PATH, muatNama, tambahNama } from '../src/nama.js'
|
|
10
|
+
import { gate, kodKeluar } from '../src/gate.js'
|
|
10
11
|
import {
|
|
11
12
|
CACHE_PATH,
|
|
12
13
|
auditJawi,
|
|
@@ -29,9 +30,13 @@ const BANTUAN = `prpm-dbp — semak Bahasa Melayu dan Jawi terhadap PRPM DBP
|
|
|
29
30
|
npx prpm-dbp import-tsv <fail.tsv> import dataset rumi<TAB>jawi (dilabel bukan DBP)
|
|
30
31
|
npx prpm-dbp nama <rumi> <jawi> sahkan satu nama khas (tokoh, surah, tempat)
|
|
31
32
|
npx prpm-dbp nama senaraikan nama khas yang disahkan
|
|
33
|
+
npx prpm-dbp gate <fail...> semakan boleh GAGAL untuk hook dan CI
|
|
34
|
+
exit 0 bersih, exit 1 ada ralat
|
|
32
35
|
npx prpm-dbp banding <kata...> silang semak PRPM lawan KamusDBP
|
|
33
36
|
npx prpm-dbp cache [--buang <kata>] status cache
|
|
34
37
|
npx prpm-dbp install [--link] pasang sebagai Claude Code skill
|
|
38
|
+
npx prpm-dbp install --hook pasang hook PostToolUse yang jalankan gate
|
|
39
|
+
npx prpm-dbp hook-json papar blok settings.json untuk ditampal sendiri
|
|
35
40
|
|
|
36
41
|
Pilihan: --refresh --had <n> --delay <ms> --json --cepat
|
|
37
42
|
--pedoman jana imbuhan ikut peraturan DBP (88-98% tepat, hasil ditanda)
|
|
@@ -39,6 +44,9 @@ Pilihan: --refresh --had <n> --delay <ms> --json --cepat
|
|
|
39
44
|
--homograf tandakan perkataan yang ejaan Jawinya dikongsi perkataan lain
|
|
40
45
|
--konteks papar ayat dan makna setiap calon bagi kekaburan (rumi)
|
|
41
46
|
--rangka padan ejaan bukan piawai melalui rangka konsonan (berlabel)
|
|
47
|
+
--glosari <fail> rumi: glosari domain {jawi: rumi} yang mengatasi segalanya
|
|
48
|
+
--ambang <n> gate: exit 1 jika tidakDapatDisahkan melebihi n peratus
|
|
49
|
+
--abai <fail> gate: senarai nama khas/istilah yang sudah diisytihar
|
|
42
50
|
--pilih pilih calon paling lazim supaya teks boleh terus dibaca;
|
|
43
51
|
setiap pilihan disenaraikan untuk disemak ikut konteks
|
|
44
52
|
|
|
@@ -53,6 +61,7 @@ function huraiArgs(argv) {
|
|
|
53
61
|
if (a === '--refresh') pilihan.refresh = true
|
|
54
62
|
else if (a === '--json') pilihan.json = true
|
|
55
63
|
else if (a === '--link') pilihan.link = true
|
|
64
|
+
else if (a === '--hook') pilihan.hook = true
|
|
56
65
|
else if (a === '--cepat') pilihan.cepat = true
|
|
57
66
|
else if (a === '--pedoman') pilihan.pedoman = true
|
|
58
67
|
else if (a === '--luar-talian') pilihan.luarTalian = true
|
|
@@ -60,6 +69,9 @@ function huraiArgs(argv) {
|
|
|
60
69
|
else if (a === '--konteks') pilihan.konteks = true
|
|
61
70
|
else if (a === '--pilih') pilihan.pilih = true
|
|
62
71
|
else if (a === '--rangka') pilihan.rangka = true
|
|
72
|
+
else if (a === '--ambang') pilihan.ambang = Number(argv[++i])
|
|
73
|
+
else if (a === '--abai') pilihan.abai = argv[++i]
|
|
74
|
+
else if (a === '--glosari') pilihan.glosari = argv[++i]
|
|
63
75
|
else if (a === '--jenis') pilihan.jenis = argv[++i]
|
|
64
76
|
else if (a === '--had') pilihan.had = Number(argv[++i])
|
|
65
77
|
else if (a === '--delay') pilihan.delay = Number(argv[++i])
|
|
@@ -85,10 +97,20 @@ async function main() {
|
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
if (perintah === 'install') {
|
|
100
|
+
if (pilihan.hook) {
|
|
101
|
+
keluar(await pasangHook())
|
|
102
|
+
return
|
|
103
|
+
}
|
|
88
104
|
keluar(await pasangSkill({ link: pilihan.link }))
|
|
89
105
|
return
|
|
90
106
|
}
|
|
91
107
|
|
|
108
|
+
if (perintah === 'hook-json') {
|
|
109
|
+
// Blok siap salin untuk pengguna yang mahu menampal sendiri.
|
|
110
|
+
keluar({ hooks: { PostToolUse: [HOOK] } })
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
const cache = await loadCache()
|
|
93
115
|
const opts = {
|
|
94
116
|
refresh: pilihan.refresh,
|
|
@@ -229,6 +251,7 @@ async function main() {
|
|
|
229
251
|
dataset: await muatDataset(),
|
|
230
252
|
pilihLazim: pilihan.pilih,
|
|
231
253
|
padanRangka: pilihan.rangka,
|
|
254
|
+
glosari: pilihan.glosari ? JSON.parse(await readFile(pilihan.glosari, 'utf8')) : null,
|
|
232
255
|
})
|
|
233
256
|
if (pilihan.json) { keluar(h); break }
|
|
234
257
|
console.log(h.teks)
|
|
@@ -280,6 +303,52 @@ async function main() {
|
|
|
280
303
|
break
|
|
281
304
|
}
|
|
282
305
|
|
|
306
|
+
case 'gate': {
|
|
307
|
+
if (!baki.length) throw new Error('perlu sekurang-kurangnya satu fail')
|
|
308
|
+
const abai = new Set()
|
|
309
|
+
if (pilihan.abai) {
|
|
310
|
+
const senarai = await readFile(pilihan.abai, 'utf8')
|
|
311
|
+
for (const b of senarai.split('\n')) {
|
|
312
|
+
const k = b.trim().toLowerCase()
|
|
313
|
+
if (k && !k.startsWith('#')) abai.add(k)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const gabungan = { jumlahKataUnik: 0, lulusDbp: 0, ralat: [], tidakDapatDisahkan: [] }
|
|
318
|
+
for (const fail of baki) {
|
|
319
|
+
const laporan = await gate(await readFile(fail, 'utf8'), cache, {
|
|
320
|
+
...opts,
|
|
321
|
+
luarTalian: pilihan.luarTalian !== false,
|
|
322
|
+
abai,
|
|
323
|
+
})
|
|
324
|
+
gabungan.jumlahKataUnik += laporan.jumlahKataUnik
|
|
325
|
+
gabungan.lulusDbp += laporan.lulusDbp
|
|
326
|
+
gabungan.ralat.push(...laporan.ralat.map((r) => ({ ...r, fail })))
|
|
327
|
+
gabungan.tidakDapatDisahkan.push(...laporan.tidakDapatDisahkan.map((r) => ({ ...r, fail })))
|
|
328
|
+
}
|
|
329
|
+
gabungan.peratusTidakDapatDisahkan = gabungan.jumlahKataUnik
|
|
330
|
+
? Math.round((gabungan.tidakDapatDisahkan.length / gabungan.jumlahKataUnik) * 100)
|
|
331
|
+
: 0
|
|
332
|
+
|
|
333
|
+
const kod = kodKeluar(gabungan, pilihan.ambang)
|
|
334
|
+
if (pilihan.json) keluar({ ...gabungan, kodKeluar: kod })
|
|
335
|
+
else {
|
|
336
|
+
console.error(
|
|
337
|
+
`gate: ${gabungan.lulusDbp}/${gabungan.jumlahKataUnik} disahkan DBP, ` +
|
|
338
|
+
`${gabungan.ralat.length} ralat, ` +
|
|
339
|
+
`${gabungan.tidakDapatDisahkan.length} tidak dapat disahkan (${gabungan.peratusTidakDapatDisahkan}%)`,
|
|
340
|
+
)
|
|
341
|
+
for (const r of gabungan.ralat.slice(0, 30)) {
|
|
342
|
+
console.error(
|
|
343
|
+
` RALAT ${r.kata}: ${r.sebab}` + (r.sepatutnya ? ` -> guna "${r.sepatutnya}"` : ''),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
if (gabungan.ralat.length > 30) console.error(` ... ${gabungan.ralat.length - 30} lagi`)
|
|
347
|
+
}
|
|
348
|
+
process.exitCode = kod
|
|
349
|
+
break
|
|
350
|
+
}
|
|
351
|
+
|
|
283
352
|
case 'nama': {
|
|
284
353
|
if (baki.length >= 2) {
|
|
285
354
|
const rec = await tambahNama(baki[0], baki[1], { jenis: pilihan.jenis ?? null })
|
package/package.json
CHANGED
package/src/gate.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate: semakan yang boleh GAGAL, untuk hook dan CI.
|
|
3
|
+
*
|
|
4
|
+
* Dibina selepas satu agen menggunakan skill ini sepanjang sesi penuh dan
|
|
5
|
+
* tetap menghantar 273 ejaan salah ke pangkalan data produksi. Alat untuk
|
|
6
|
+
* menangkapnya ada di tangannya sepanjang masa; ia tidak pernah dijalankan.
|
|
7
|
+
* Kedua-dua pusingan ditemui kerana pengguna bertanya, bukan kerana proses
|
|
8
|
+
* menangkapnya.
|
|
9
|
+
*
|
|
10
|
+
* Kesimpulannya: skill yang bergantung pada agen ingat sendiri akan gagal,
|
|
11
|
+
* dan gagal secara senyap. Gate wujud supaya kepatuhan tidak bergantung
|
|
12
|
+
* pada ingatan.
|
|
13
|
+
*
|
|
14
|
+
* TIGA keadaan, bukan dua. `tidakDapatDisahkan` TIDAK PERNAH lulus senyap:
|
|
15
|
+
* gate yang lulus atas cache sejuk memberi jaminan palsu, dan itu lebih
|
|
16
|
+
* bahaya daripada tiada gate.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { lookup } from './prpm.js'
|
|
20
|
+
import { mungkinAkronim, mungkinNamaKhas } from './nama.js'
|
|
21
|
+
import { cariDataset } from './dataset.js'
|
|
22
|
+
|
|
23
|
+
const TOKEN_RUMI = /([A-Za-zÀ-ÿ][A-Za-zÀ-ÿ'-]*)/g
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Semak teks Rumi. Setiap perkataan jatuh ke dalam SATU daripada tiga baldi.
|
|
27
|
+
*/
|
|
28
|
+
export async function gate(teks, cache, opts = {}) {
|
|
29
|
+
// Dataset boleh disuntik supaya ujian tidak bergantung pada fail yang
|
|
30
|
+
// diimport ke mesin tertentu. Ujian yang bergantung pada keadaan mesin
|
|
31
|
+
// lulus di sini dan gagal di CI, atau lebih teruk, sebaliknya.
|
|
32
|
+
const { luarTalian = true, abai = new Set(), cariDalamDataset = cariDataset } = opts
|
|
33
|
+
|
|
34
|
+
const lulusDbp = []
|
|
35
|
+
const ralat = []
|
|
36
|
+
const tidakDapatDisahkan = []
|
|
37
|
+
const dilihat = new Set()
|
|
38
|
+
|
|
39
|
+
const bahagian = teks.split(TOKEN_RUMI)
|
|
40
|
+
for (const [i, cebis] of bahagian.entries()) {
|
|
41
|
+
if (i % 2 === 0) continue
|
|
42
|
+
const kata = cebis.toLowerCase()
|
|
43
|
+
if (kata.length < 2 || dilihat.has(kata)) continue
|
|
44
|
+
dilihat.add(kata)
|
|
45
|
+
|
|
46
|
+
if (abai.has(kata)) continue
|
|
47
|
+
|
|
48
|
+
// Akronim dan nama khas tidak boleh disahkan oleh kamus. Ia BUKAN lulus;
|
|
49
|
+
// ia keadaan ketiga, supaya pengguna tahu apa yang tidak disemak.
|
|
50
|
+
if (mungkinAkronim(cebis) || mungkinNamaKhas(cebis, bahagian[i - 1])) {
|
|
51
|
+
tidakDapatDisahkan.push({ kata: cebis, sebab: 'nama khas atau akronim' })
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const rec = await lookup(kata, cache, { ...opts, luarTalian })
|
|
56
|
+
|
|
57
|
+
if (rec?.ralat) {
|
|
58
|
+
tidakDapatDisahkan.push({ kata, sebab: `gagal semak: ${rec.ralat}` })
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!rec?.dijumpai) {
|
|
63
|
+
// Dalam mod luar talian, "tiada dalam cache" berbeza daripada "tiada
|
|
64
|
+
// dalam PRPM". Yang pertama tidak dapat disahkan; yang kedua ralat.
|
|
65
|
+
if (luarTalian && /luar talian/.test(rec?.nota ?? '')) {
|
|
66
|
+
tidakDapatDisahkan.push({ kata, sebab: 'tiada dalam cache' })
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// PRPM menyatakan sendiri perkataan ini tiada dalam kamus TERKINI
|
|
71
|
+
// tetapi ada dalam tesaurus. Itu bukan ejaan salah; ia perkataan sah
|
|
72
|
+
// yang bahagian kamusnya tidak diliputi. Menandakannya ralat
|
|
73
|
+
// menghasilkan negatif palsu pada perkataan paling biasa.
|
|
74
|
+
if (rec?.adaTesaurus) {
|
|
75
|
+
tidakDapatDisahkan.push({
|
|
76
|
+
kata,
|
|
77
|
+
sebab: 'tiada dalam kamus terkini PRPM, tetapi ada dalam tesaurus',
|
|
78
|
+
})
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// PRPM tiada entri untuk kata tugas paling asas: dan, di, ke, yang,
|
|
83
|
+
// atau. Menandakannya sebagai ralat menjadikan gate mustahil dipakai.
|
|
84
|
+
// Pembezanya bersumber, bukan senarai putih dari ingatan: kata tugas
|
|
85
|
+
// itu SEMUA wujud dalam dataset, manakala hadith, fardhu, tayammum,
|
|
86
|
+
// dhuha, qadha, redha dan iddah TIADA satu pun.
|
|
87
|
+
const ds = await cariDalamDataset(kata)
|
|
88
|
+
if (ds?.jawi) {
|
|
89
|
+
tidakDapatDisahkan.push({
|
|
90
|
+
kata,
|
|
91
|
+
sebab: 'tiada entri PRPM, tetapi wujud dalam dataset',
|
|
92
|
+
})
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ralat.push({ kata, sebab: 'tiada entri dalam PRPM', url: rec?.url })
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Entri rujukan silang bermakna DBP mengalihkan bentuk ini ke bentuk lain.
|
|
101
|
+
// sunnah -> ®sunah. Menggunakannya adalah ejaan yang salah.
|
|
102
|
+
const def = (rec.entri?.[0]?.definisi ?? '').trim()
|
|
103
|
+
const silang = /^®\s*(.+?)\.?$/.exec(def)
|
|
104
|
+
if (silang) {
|
|
105
|
+
ralat.push({
|
|
106
|
+
kata,
|
|
107
|
+
sebab: 'rujukan silang, DBP mengalihkan ke bentuk lain',
|
|
108
|
+
sepatutnya: silang[1].trim(),
|
|
109
|
+
url: rec.url,
|
|
110
|
+
})
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lulusDbp.push(kata)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const jumlah = lulusDbp.length + ralat.length + tidakDapatDisahkan.length
|
|
118
|
+
const peratusTidakSah = jumlah ? Math.round((tidakDapatDisahkan.length / jumlah) * 100) : 0
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
jumlahKataUnik: jumlah,
|
|
122
|
+
lulusDbp: lulusDbp.length,
|
|
123
|
+
ralat,
|
|
124
|
+
tidakDapatDisahkan,
|
|
125
|
+
peratusTidakDapatDisahkan: peratusTidakSah,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Tentukan kod keluar. Ralat sentiasa menggagalkan. `tidakDapatDisahkan`
|
|
131
|
+
* menggagalkan apabila melebihi ambang, kerana cache sejuk bermakna gate
|
|
132
|
+
* tidak menyemak apa-apa.
|
|
133
|
+
*/
|
|
134
|
+
export function kodKeluar(laporan, ambang = null) {
|
|
135
|
+
if (laporan.ralat.length) return 1
|
|
136
|
+
if (ambang != null && laporan.peratusTidakDapatDisahkan > ambang) return 1
|
|
137
|
+
return 0
|
|
138
|
+
}
|
package/src/install.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Mod salin (default) untuk pengguna npx, mod --link untuk pembangun.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { cp, mkdir, rm, symlink } from 'node:fs/promises'
|
|
6
|
+
import { cp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
|
7
7
|
import { homedir } from 'node:os'
|
|
8
8
|
import { dirname, join, resolve } from 'node:path'
|
|
9
9
|
import { fileURLToPath } from 'node:url'
|
|
@@ -26,3 +26,50 @@ export async function pasangSkill({ link = false } = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
return { dipasang: SASARAN, mod: 'salin', sumber: AKAR }
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pasang hook PostToolUse Claude Code yang menjalankan gate ke atas fail
|
|
32
|
+
* yang baru ditulis atau disunting.
|
|
33
|
+
*
|
|
34
|
+
* Ini satu-satunya lapisan yang benar-benar memaksa. Peraturan dalam
|
|
35
|
+
* SKILL.md bergantung pada agen memilih untuk patuh, dan satu agen sudah
|
|
36
|
+
* membuktikan itu tidak mencukupi: 273 ejaan salah masuk produksi walaupun
|
|
37
|
+
* alat menangkapnya ada di tangan sepanjang masa.
|
|
38
|
+
*
|
|
39
|
+
* Hook gagal LEMBUT: ia melaporkan ralat kepada agen tanpa menyekat tulisan.
|
|
40
|
+
* Menyekat tulisan atas kamus yang tidak lengkap akan menghalang kerja yang
|
|
41
|
+
* sah; melaporkan sudah memadai kerana agen membaca output hook.
|
|
42
|
+
*/
|
|
43
|
+
export const HOOK = {
|
|
44
|
+
matcher: 'Write|Edit',
|
|
45
|
+
hooks: [
|
|
46
|
+
{
|
|
47
|
+
type: 'command',
|
|
48
|
+
command:
|
|
49
|
+
'f=$(echo "$CLAUDE_TOOL_INPUT" | grep -oE \'"file_path"[^,]*\' | cut -d\'"\' -f4); ' +
|
|
50
|
+
'case "$f" in *.md|*.txt|*.json) npx -y prpm-dbp gate "$f" --luar-talian 2>&1 | head -20 ;; esac; exit 0',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function pasangHook({ fail = join(homedir(), '.claude', 'settings.json') } = {}) {
|
|
56
|
+
let tetapan = {}
|
|
57
|
+
try {
|
|
58
|
+
tetapan = JSON.parse(await readFile(fail, 'utf8'))
|
|
59
|
+
} catch {
|
|
60
|
+
tetapan = {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
tetapan.hooks ??= {}
|
|
64
|
+
tetapan.hooks.PostToolUse ??= []
|
|
65
|
+
|
|
66
|
+
const sedia = tetapan.hooks.PostToolUse.some((h) =>
|
|
67
|
+
JSON.stringify(h).includes('prpm-dbp gate'),
|
|
68
|
+
)
|
|
69
|
+
if (sedia) return { fail, status: 'sudah dipasang' }
|
|
70
|
+
|
|
71
|
+
tetapan.hooks.PostToolUse.push(HOOK)
|
|
72
|
+
await mkdir(dirname(fail), { recursive: true })
|
|
73
|
+
await writeFile(fail, JSON.stringify(tetapan, null, 2), 'utf8')
|
|
74
|
+
return { fail, status: 'dipasang', matcher: HOOK.matcher }
|
|
75
|
+
}
|
package/src/prpm.js
CHANGED
|
@@ -34,6 +34,17 @@ const DEFINISI = /<b>Definisi\s*:\s*<\/b>([\s\S]*?)(?=<b>\(|$)/
|
|
|
34
34
|
const SUMBER = /<b>\(([^)]+)\)<\/b>/
|
|
35
35
|
const TIADA_TESAURUS = /Tiada maklumat tesaurus untuk kata/
|
|
36
36
|
|
|
37
|
+
// PRPM mengisytiharkan keadaan ketiga dengan ayatnya sendiri. Perkataan
|
|
38
|
+
// seperti orang, itu dan sumber memulangkan halaman 35-42KB tanpa satu pun
|
|
39
|
+
// div.tab-pane, tetapi ia BUKAN "tiada entri": PRPM menyatakan perkataan itu
|
|
40
|
+
// tiada dalam kamus TERKINI, dan memaparkan Tesaurus sebagai gantinya.
|
|
41
|
+
//
|
|
42
|
+
// Menyamakan dua keadaan ini menghasilkan negatif palsu pada perkataan yang
|
|
43
|
+
// paling biasa, dan negatif palsu di situ merosakkan kepercayaan kepada
|
|
44
|
+
// seluruh laporan.
|
|
45
|
+
const BUKAN_KAMUS_TERKINI = /Carian kata tiada di dalam kamus terkini/
|
|
46
|
+
const ADA_TESAURUS = /Bersinonim dengan/
|
|
47
|
+
|
|
37
48
|
// Panel "Juga ditemukan dalam" menyenaraikan bilangan rujukan perkataan itu
|
|
38
49
|
// dalam setiap korpus DBP. Jumlahnya ialah isyarat kekerapan yang BERSUMBER,
|
|
39
50
|
// bukan tekaan: mengikut 2439 rujukan berbanding mengekot 6.
|
|
@@ -89,11 +100,16 @@ export function parse(kata, page) {
|
|
|
89
100
|
jawiVarian: unik.slice(1),
|
|
90
101
|
entri,
|
|
91
102
|
url: `${BASE_URL}?keyword=${encodeURIComponent(kata)}`,
|
|
103
|
+
// Tiga keadaan berbeza, bukan dua.
|
|
104
|
+
dalamKamusTerkini: entri.length > 0 ? true : !BUKAN_KAMUS_TERKINI.test(page),
|
|
105
|
+
adaTesaurus: ADA_TESAURUS.test(page),
|
|
92
106
|
nota: entri.length
|
|
93
107
|
? null
|
|
94
|
-
:
|
|
95
|
-
? 'tiada
|
|
96
|
-
:
|
|
108
|
+
: BUKAN_KAMUS_TERKINI.test(page)
|
|
109
|
+
? 'tiada dalam kamus terkini PRPM; ada dalam tesaurus'
|
|
110
|
+
: TIADA_TESAURUS.test(page)
|
|
111
|
+
? 'tiada entri dalam PRPM'
|
|
112
|
+
: 'halaman tidak dikenali, semak manual',
|
|
97
113
|
}
|
|
98
114
|
}
|
|
99
115
|
|
package/src/tukar.js
CHANGED
|
@@ -162,6 +162,36 @@ export async function cariDasar(kata, cache, opts = {}) {
|
|
|
162
162
|
* bilangan perkataan terpanjang supaya pembacaan songsang boleh memadan
|
|
163
163
|
* jujukan, bukan token tunggal sahaja.
|
|
164
164
|
*/
|
|
165
|
+
/**
|
|
166
|
+
* Jarak suntingan antara dua ejaan.
|
|
167
|
+
*
|
|
168
|
+
* MESTI menghormati susunan huruf. Ukuran berasaskan set huruf gagal dengan
|
|
169
|
+
* teruk: سروه (seruh) ialah anagram sempurna bagi سوره, jadi jaraknya sifar
|
|
170
|
+
* dan ia mengalahkan سورة (surah) yang sebenarnya betul. Susunan huruf itulah
|
|
171
|
+
* maklumat yang membezakannya.
|
|
172
|
+
*/
|
|
173
|
+
function jarakHuruf(a, b) {
|
|
174
|
+
const m = a.length
|
|
175
|
+
const n = b.length
|
|
176
|
+
if (!m) return n
|
|
177
|
+
if (!n) return m
|
|
178
|
+
let baris = Array.from({ length: n + 1 }, (_, i) => i)
|
|
179
|
+
for (let i = 1; i <= m; i++) {
|
|
180
|
+
let sebelum = baris[0]
|
|
181
|
+
baris[0] = i
|
|
182
|
+
for (let j = 1; j <= n; j++) {
|
|
183
|
+
const simpan = baris[j]
|
|
184
|
+
baris[j] = Math.min(
|
|
185
|
+
baris[j] + 1,
|
|
186
|
+
baris[j - 1] + 1,
|
|
187
|
+
sebelum + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
188
|
+
)
|
|
189
|
+
sebelum = simpan
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return baris[n]
|
|
193
|
+
}
|
|
194
|
+
|
|
165
195
|
/**
|
|
166
196
|
* Indeks rangka konsonan, untuk memadan ejaan bukan piawai.
|
|
167
197
|
*
|
|
@@ -455,7 +485,13 @@ export async function keJawi(teks, cache, opts = {}) {
|
|
|
455
485
|
export function keRumi(
|
|
456
486
|
teksJawi,
|
|
457
487
|
cache,
|
|
458
|
-
{
|
|
488
|
+
{
|
|
489
|
+
penanda = ['«', '»'],
|
|
490
|
+
dataset = null,
|
|
491
|
+
pilihLazim = false,
|
|
492
|
+
padanRangka = false,
|
|
493
|
+
glosari = null,
|
|
494
|
+
} = {},
|
|
459
495
|
) {
|
|
460
496
|
const peta = petaSongsang(cache, dataset)
|
|
461
497
|
const tidakDikenali = []
|
|
@@ -463,6 +499,7 @@ export function keRumi(
|
|
|
463
499
|
const arab = []
|
|
464
500
|
const sendiDipisah = []
|
|
465
501
|
const bukanPiawai = []
|
|
502
|
+
const dariGlosari = []
|
|
466
503
|
const rangka = padanRangka ? petaRangka(cache, dataset) : null
|
|
467
504
|
const bahagian = teksJawi.split(PECAH_JAWI)
|
|
468
505
|
const keluar = []
|
|
@@ -470,6 +507,20 @@ export function keRumi(
|
|
|
470
507
|
for (let i = 0; i < bahagian.length; i++) {
|
|
471
508
|
if (i % 2 === 0) { keluar.push(bahagian[i]); continue }
|
|
472
509
|
|
|
510
|
+
// Glosari domain yang dibekalkan pemanggil MENGATASI segalanya.
|
|
511
|
+
// Kekerapan global gagal tepat di tempat paling penting: dalam teks
|
|
512
|
+
// domain, setiap istilah khusus kalah kepada kata lazim yang berkongsi
|
|
513
|
+
// rangka. اسم menjadi asam, فعل menjadi faal, رسم menjadi rasam.
|
|
514
|
+
// Hanya pengetahuan domain boleh membetulkannya, dan pemanggil ada.
|
|
515
|
+
if (glosari) {
|
|
516
|
+
const paksa = glosari[bahagian[i]] ?? glosari[normalisasiJawi(bahagian[i])]
|
|
517
|
+
if (paksa) {
|
|
518
|
+
dariGlosari.push({ jawi: bahagian[i], rumi: paksa })
|
|
519
|
+
keluar.push(paksa)
|
|
520
|
+
continue
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
473
524
|
// Teks Arab bertanda baris dibiarkan seadanya. Cuba menukarnya akan
|
|
474
525
|
// merosakkan petikan al-Quran dan hadis dalam dokumen Melayu.
|
|
475
526
|
if (HARAKAT.test(bahagian[i])) {
|
|
@@ -525,12 +576,33 @@ export function keRumi(
|
|
|
525
576
|
if (rangka) {
|
|
526
577
|
const padananRangka = rangka.get(rangkaKonsonan(bahagian[i]))
|
|
527
578
|
if (padananRangka?.size) {
|
|
528
|
-
|
|
529
|
-
|
|
579
|
+
// Rangka pendek berlanggar teruk: رواية memberi 20+ calon. Isih
|
|
580
|
+
// ikut kedekatan huruf sebenar dahulu, kemudian kekerapan korpus,
|
|
581
|
+
// dan hadkan senarai supaya teks kekal boleh dibaca.
|
|
582
|
+
const asal = bahagian[i]
|
|
583
|
+
const senarai = [...padananRangka.entries()]
|
|
584
|
+
.map(([rumi, jawiDbp]) => ({
|
|
585
|
+
rumi,
|
|
586
|
+
jawiDbp,
|
|
587
|
+
jarak: jarakHuruf(asal, jawiDbp),
|
|
588
|
+
kerap: cache[rumi]?.rujukanKorpus ?? 0,
|
|
589
|
+
}))
|
|
590
|
+
.sort((a, b) => a.jarak - b.jarak || b.kerap - a.kerap)
|
|
591
|
+
.slice(0, 6)
|
|
592
|
+
|
|
593
|
+
bukanPiawai.push({ jawi: asal, calon: senarai })
|
|
594
|
+
|
|
530
595
|
if (senarai.length === 1) {
|
|
531
596
|
keluar.push(senarai[0].rumi)
|
|
532
597
|
continue
|
|
533
598
|
}
|
|
599
|
+
// --pilih mesti meleraikan ini juga, jika tidak seluruh senarai
|
|
600
|
+
// calon dibuang ke dalam teks dan ayat menjadi tidak boleh dibaca.
|
|
601
|
+
if (pilihLazim) {
|
|
602
|
+
bukanPiawai[bukanPiawai.length - 1].dipilih = senarai[0].rumi
|
|
603
|
+
keluar.push(senarai[0].rumi)
|
|
604
|
+
continue
|
|
605
|
+
}
|
|
534
606
|
keluar.push(penanda[0] + senarai.map((x) => x.rumi).join('|') + penanda[1])
|
|
535
607
|
continue
|
|
536
608
|
}
|
|
@@ -567,6 +639,7 @@ export function keRumi(
|
|
|
567
639
|
arab: [...new Set(arab)],
|
|
568
640
|
sendiDipisah,
|
|
569
641
|
ejaanBukanPiawai: bukanPiawai,
|
|
642
|
+
dariGlosari,
|
|
570
643
|
saizPeta: peta.size,
|
|
571
644
|
lengkap: tidakDikenali.length === 0,
|
|
572
645
|
}
|