prpm-dbp 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -128,6 +128,7 @@ Pengguna kemudian menarik versi baharu dengan `npx skills update`.
128
128
  | `jawi <fail>` | tukar teks Rumi ke Jawi ikut ejaan DBP |
129
129
  | `rumi <fail>` | tukar teks Jawi ke Rumi dari cache |
130
130
  | `import-tsv <fail>` | import dataset rumi/jawi tambahan |
131
+ | `nama <rumi> <jawi>` | sahkan satu nama khas |
131
132
  | `banding <kata...>` | silang semak PRPM lawan KamusDBP |
132
133
  | `cache` | status cache, `--buang <kata>` untuk padam satu |
133
134
 
package/SKILL.md CHANGED
@@ -91,6 +91,31 @@ Sesetengah entri memulangkan **dua perkataan Jawi untuk satu perkataan Rumi**: `
91
91
 
92
92
  **Teks Arab yang dipetik dibiarkan sepenuhnya.** Jawi Melayu tidak menggunakan baris tanda vokal, jadi kehadiran fathah, kasrah, dammah, sukun atau tanwin bermakna potongan itu Arab, bukan Jawi. Ayat al-Quran dan hadis dalam dokumen Melayu lulus tanpa disentuh dan dilaporkan dalam medan `arab`.
93
93
 
94
+ ## Nama khas
95
+
96
+ Diuji terhadap PRPM: **nama tokoh dan nama surah tiada sumber berwibawa.** `muhammad`, `ibrahim`, `musa`, `umar`, `aisyah`, `khadijah` semuanya tiada entri. Nama surah seperti `fatihah`, `baqarah`, `yasin` ada entri tetapi tanpa ejaan Jawi. Dataset komuniti meliputi nama tempat (`mekah` مکة, `mesir` مسير, `pahang` ڤاهڠ) tetapi bukan nama orang.
97
+
98
+ Alat ini **tidak mereka ejaan nama**. Sebaliknya:
99
+
100
+ - Perkataan berhuruf besar di **tengah** ayat dikira nama khas dan dilaporkan dalam medan `namaKhas`, berasingan daripada `belumDisahkan`. Nama bukan salah eja.
101
+ - Sahkan sekali, guna selamanya:
102
+
103
+ ```bash
104
+ npx prpm-dbp nama Muhammad <ejaan-jawi>
105
+ npx prpm-dbp nama # senaraikan yang sudah disahkan
106
+ ```
107
+
108
+ Ejaan yang disimpan begini ditandakan datang daripada pengguna, bukan daripada DBP. Sahkan dengan guru atau rujukan bertulis dahulu.
109
+
110
+ **Jangan keliru antara dua benda:**
111
+
112
+ | | Layanan |
113
+ |---|---|
114
+ | Petikan bahasa Arab (ayat, doa, hadis) | kekal Arab, tidak disentuh |
115
+ | Nama khas dalam ayat Melayu | **ditulis dalam Jawi**, bukan dikekalkan |
116
+
117
+ 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 `کوالا لومڤور`.
118
+
94
119
  ## Rantai kepercayaan
95
120
 
96
121
  Setiap ejaan Jawi yang dikeluarkan membawa asal-usulnya. Jangan runtuhkan lapisan ini.
package/bin/cli.js CHANGED
@@ -6,6 +6,7 @@ import { pasangSkill } from '../src/install.js'
6
6
  import { banding, cariKamus } from '../src/kamus.js'
7
7
  import { keJawi, keRumi } from '../src/tukar.js'
8
8
  import { huraiTsv, muatDataset, simpanDataset } from '../src/dataset.js'
9
+ import { NAMA_PATH, muatNama, tambahNama } from '../src/nama.js'
9
10
  import {
10
11
  CACHE_PATH,
11
12
  auditJawi,
@@ -26,6 +27,8 @@ const BANTUAN = `prpm-dbp — semak Bahasa Melayu dan Jawi terhadap PRPM DBP
26
27
  npx prpm-dbp jawi <fail.txt> tukar teks Rumi ke Jawi ikut ejaan DBP
27
28
  npx prpm-dbp rumi <fail.txt> tukar teks Jawi ke Rumi dari cache
28
29
  npx prpm-dbp import-tsv <fail.tsv> import dataset rumi<TAB>jawi (dilabel bukan DBP)
30
+ npx prpm-dbp nama <rumi> <jawi> sahkan satu nama khas (tokoh, surah, tempat)
31
+ npx prpm-dbp nama senaraikan nama khas yang disahkan
29
32
  npx prpm-dbp banding <kata...> silang semak PRPM lawan KamusDBP
30
33
  npx prpm-dbp cache [--buang <kata>] status cache
31
34
  npx prpm-dbp install [--link] pasang sebagai Claude Code skill
@@ -160,6 +163,12 @@ async function main() {
160
163
  h.adaEntriTanpaJawi.join(', '),
161
164
  )
162
165
  }
166
+ if (h.namaKhas.length) {
167
+ console.error(
168
+ `nama khas belum disahkan (${h.namaKhas.length}): ${h.namaKhas.join(', ')}\n` +
169
+ ` sahkan dengan: npx prpm-dbp nama <rumi> <jawi>`,
170
+ )
171
+ }
163
172
  if (h.terbitanSah.length) {
164
173
  console.error(
165
174
  `terbitan sah tanpa ejaan Jawi (${h.terbitanSah.length}): ` +
@@ -183,6 +192,17 @@ async function main() {
183
192
  break
184
193
  }
185
194
 
195
+ case 'nama': {
196
+ if (baki.length >= 2) {
197
+ const rec = await tambahNama(baki[0], baki[1], { jenis: pilihan.jenis ?? null })
198
+ keluar({ disimpan: rec, fail: NAMA_PATH, nota: 'Ejaan ini datang dari anda, bukan dari DBP.' })
199
+ break
200
+ }
201
+ const peta = await muatNama()
202
+ keluar({ fail: NAMA_PATH, jumlah: Object.keys(peta).length, nama: peta })
203
+ break
204
+ }
205
+
186
206
  case 'import-tsv': {
187
207
  if (!baki[0]) throw new Error('perlu fail TSV: rumi<TAB>jawi setiap baris')
188
208
  const { peta, dilangkau, jumlah } = huraiTsv(await readFile(baki[0], 'utf8'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prpm-dbp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Semak ejaan Bahasa Melayu dan Jawi terhadap PRPM Dewan Bahasa dan Pustaka. CLI dan skill untuk AI agent.",
5
5
  "keywords": [
6
6
  "bahasa-melayu",
package/src/nama.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Simpanan nama khas yang disahkan pengguna.
3
+ *
4
+ * Kenapa ini wujud: nama tokoh dan nama surah tiada sumber berwibawa yang
5
+ * boleh dicapai. Diuji terhadap PRPM, muhammad, ibrahim, musa, umar, aisyah
6
+ * dan khadijah semuanya TIADA entri. Dataset komuniti pula meliputi nama
7
+ * tempat (مکة, مسير, ڤاهڠ) tetapi bukan nama orang atau nama surah.
8
+ *
9
+ * Alat ini TIDAK mereka ejaan nama. Ia menyimpan apa yang pengguna sahkan
10
+ * sendiri, dengan sumber dicatat, supaya kerja pengesahan itu dibuat sekali
11
+ * sahaja dan boleh diaudit kemudian.
12
+ */
13
+
14
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
15
+ import { dirname, join } from 'node:path'
16
+
17
+ import { CACHE_PATH } from './prpm.js'
18
+
19
+ export const NAMA_PATH = process.env.PRPM_NAMA ?? join(dirname(CACHE_PATH), 'nama-khas.json')
20
+
21
+ let dimuat = null
22
+
23
+ export async function muatNama() {
24
+ if (dimuat !== null) return dimuat
25
+ try {
26
+ dimuat = JSON.parse(await readFile(NAMA_PATH, 'utf8'))
27
+ } catch {
28
+ dimuat = {}
29
+ }
30
+ return dimuat
31
+ }
32
+
33
+ export async function simpanNama(peta) {
34
+ await mkdir(dirname(NAMA_PATH), { recursive: true })
35
+ const tmp = `${NAMA_PATH}.tmp`
36
+ await writeFile(tmp, JSON.stringify(peta, null, 1), 'utf8')
37
+ await rename(tmp, NAMA_PATH)
38
+ dimuat = peta
39
+ return NAMA_PATH
40
+ }
41
+
42
+ export async function tambahNama(rumi, jawi, { sumber = 'pengguna', jenis = null } = {}) {
43
+ const peta = { ...(await muatNama()) }
44
+ const kunci = String(rumi).trim().toLowerCase()
45
+ if (!kunci || !jawi) throw new Error('perlu rumi dan jawi')
46
+ peta[kunci] = { rumi: kunci, jawi: String(jawi).trim(), sumber, jenis }
47
+ await simpanNama(peta)
48
+ return peta[kunci]
49
+ }
50
+
51
+ export async function cariNama(kata) {
52
+ const peta = await muatNama()
53
+ const rec = peta[String(kata).trim().toLowerCase()]
54
+ if (!rec) return null
55
+ return { ...rec, dijumpai: true, disahkanDbp: false }
56
+ }
57
+
58
+ /**
59
+ * Teka sama ada perkataan itu nama khas.
60
+ *
61
+ * Huruf besar di TENGAH ayat ialah isyarat yang munasabah dalam teks Melayu.
62
+ * Ia sengaja tidak digunakan pada perkataan pertama ayat, kerana di situ
63
+ * huruf besar tidak bermakna apa-apa.
64
+ */
65
+ export function mungkinNamaKhas(kata, sebelumnya) {
66
+ if (!/^[A-Z]/.test(kata)) return false
67
+ // Awal teks, atau selepas noktah, tanda soal, tanda seru, atau baris baru.
68
+ const awalAyat = !sebelumnya || /[.!?:\n]\s*$/.test(sebelumnya)
69
+ return !awalAyat
70
+ }
package/src/tukar.js CHANGED
@@ -11,6 +11,7 @@
11
11
  import { lookup } from './prpm.js'
12
12
  import { normalisasiJawi } from './kamus.js'
13
13
  import { cariDataset, muatDataset } from './dataset.js'
14
+ import { cariNama, mungkinNamaKhas } from './nama.js'
14
15
 
15
16
  // Baris tanda vokal Arab (fathah, kasrah, dammah, sukun, syaddah, tanwin,
16
17
  // alef superscript). Jawi Melayu TIDAK menggunakannya; teks al-Quran dan
@@ -130,6 +131,8 @@ export async function keJawi(teks, cache, opts = {}) {
130
131
  const dariDataset = []
131
132
  const dbp = []
132
133
  const adaEntriTanpaJawi = []
134
+ const dariNama = []
135
+ const namaKhas = []
133
136
  const bahagian = teks.split(PECAH_RUMI)
134
137
  const keluar = []
135
138
 
@@ -155,6 +158,14 @@ export async function keJawi(teks, cache, opts = {}) {
155
158
  }
156
159
  }
157
160
 
161
+ // Nama khas yang pengguna sudah sahkan sendiri.
162
+ const nm = await cariNama(cebis)
163
+ if (nm?.jawi) {
164
+ dariNama.push({ kata: cebis, jawi: nm.jawi })
165
+ keluar.push(nm.jawi)
166
+ continue
167
+ }
168
+
158
169
  if (cubaDasar) {
159
170
  const dasar = await cariDasar(cebis.toLowerCase(), cache, opts)
160
171
  if (dasar) {
@@ -166,7 +177,11 @@ export async function keJawi(teks, cache, opts = {}) {
166
177
  }
167
178
  }
168
179
 
169
- belumDisahkan.push(cebis)
180
+ // Nama khas yang belum disahkan bukan salah eja. Ia dilaporkan
181
+ // berasingan supaya pengguna tahu apa yang perlu disahkan sekali, dan
182
+ // bukan mengulang menyemak ejaan yang memang betul.
183
+ if (mungkinNamaKhas(cebis, bahagian[i - 1])) namaKhas.push(cebis)
184
+ else belumDisahkan.push(cebis)
170
185
  keluar.push(penanda[0] + cebis + penanda[1])
171
186
  }
172
187
 
@@ -174,10 +189,12 @@ export async function keJawi(teks, cache, opts = {}) {
174
189
  teks: keluar.join(''),
175
190
  disahkanDbp: dbp.length,
176
191
  dariDataset,
192
+ dariNama,
193
+ namaKhas: [...new Set(namaKhas)],
177
194
  adaEntriTanpaJawi: [...new Set(adaEntriTanpaJawi)],
178
195
  belumDisahkan: [...new Set(belumDisahkan)],
179
196
  terbitanSah,
180
- lengkap: belumDisahkan.length === 0 && terbitanSah.length === 0,
197
+ lengkap: belumDisahkan.length === 0 && terbitanSah.length === 0 && namaKhas.length === 0,
181
198
  // Sepenuhnya DBP bermakna setiap perkataan datang dari sumber rasmi.
182
199
  semuanyaDbp: dariDataset.length === 0 && belumDisahkan.length === 0 && terbitanSah.length === 0,
183
200
  }