dsh-paper-search 0.1.1 → 0.1.3
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/CHANGELOG.md +69 -1
- package/lib/sources.js +80 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| dsh-paper-search | DeepSeek Harness | Diuji pada |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| 0.1.
|
|
7
|
+
| 0.1.3 | 0.1.7-rc.2 | 28 Sep 2026, Windows, Node 24 |
|
|
8
8
|
| ~~0.1.0~~ | — | **JANGAN DIPAKAI — plugin gagal dimuat, lihat 0.1.1** |
|
|
9
9
|
|
|
10
10
|
Paket ini tidak memakai API internal DSH selain `ctx.tools.register` dan
|
|
@@ -12,6 +12,74 @@ Paket ini tidak memakai API internal DSH selain `ctx.tools.register` dan
|
|
|
12
12
|
berjalan di profil ini (`dsh-search-cascade`), jadi titik patahnya sama dan sudah
|
|
13
13
|
diketahui.
|
|
14
14
|
|
|
15
|
+
## [0.1.3] - 2026-09-28
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Portal Indonesia melaporkan GAGAL padahal hasilnya cuma KOSONG.** Ketiga
|
|
20
|
+
konektor Indonesia melempar `markup ... berubah` setiap kali penanda hasil tidak
|
|
21
|
+
ditemukan — termasuk saat portal itu **sah-sah saja menjawab nol hasil**. Itu
|
|
22
|
+
keliru dengan arah berlawanan dari prinsip paket ini: melaporkan kegagalan di
|
|
23
|
+
tempat yang seharusnya kosong sama menyesatkannya dengan menyembunyikan
|
|
24
|
+
kegagalan.
|
|
25
|
+
|
|
26
|
+
Ketahuan dari **uji orkestrasi**, bukan dari uji unit: `Sinta` gagal untuk
|
|
27
|
+
`"literasi digital remaja"` padahal berhasil untuk `"pendidikan karakter"`.
|
|
28
|
+
Setelah halaman kosong dan halaman ber-hasil dibandingkan, ternyata ketiga
|
|
29
|
+
portal punya penanda nol-hasilnya sendiri:
|
|
30
|
+
|
|
31
|
+
| Portal | Penanda nol hasil | Halaman kosong |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Garuda | `Found 0 documents` | 11.181 huruf |
|
|
34
|
+
| Sinta | header `Results for "<q>"` tetap ada, tautan profil nol | 34.063 huruf |
|
|
35
|
+
| IOS | `No Results!` | 23.041 huruf |
|
|
36
|
+
|
|
37
|
+
`portalNolHasil()` sekarang membedakan **tiga** kemungkinan — nol hasil (→ `[]`),
|
|
38
|
+
markup berubah (→ error), dan diblokir (→ error) — bukan dua.
|
|
39
|
+
|
|
40
|
+
Akibatnya untuk Sinta: kueri spesifik yang memang nol hasil tidak lagi tampak
|
|
41
|
+
seperti kerusakan. Sinta mendaftar **jurnal**, bukan artikel, jadi nol hasil itu
|
|
42
|
+
wajar dan sekarang dilaporkan sebagai nol.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- 3 asersi regresi: kueri omong kosong ke ketiga portal Indonesia harus
|
|
47
|
+
menghasilkan **0 hasil tanpa error**.
|
|
48
|
+
|
|
49
|
+
## [0.1.2] - 2026-09-28
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **Judul Garuda masih bisa terduplikasi.** Perbaikan di rilis sebelumnya baru
|
|
54
|
+
menangani pengulangan **persis** (`X X X`). Setelah mengukur **50 judul
|
|
55
|
+
sungguhan** (5 kueri, diambil apa adanya dari halaman Garuda), ternyata ada
|
|
56
|
+
**empat** bentuk pengulangan, bukan satu:
|
|
57
|
+
|
|
58
|
+
| Bentuk | Contoh | Panjang |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| A — periode persis | `X X X X X X` | 255 → 43 |
|
|
61
|
+
| B — diulang sesudah `": "` | `X: X` | 194 → 96 |
|
|
62
|
+
| C — awalan lalu `X: X` | `NLP, Machine Learning X: X` | 270 → 145 |
|
|
63
|
+
| D — frasa diulang di awal | `IMPLEMENTASI PENDIDIKAN KARAKTER IMPLEMENTASI …` | 306 → 295 |
|
|
64
|
+
|
|
65
|
+
`runtuhkanPeriodik()` diganti **`runtuhkanJudul()`** yang menangani keempatnya,
|
|
66
|
+
termasuk pengulangan dengan **kapitalisasi berbeda** (252 → 125) — Garuda
|
|
67
|
+
memang sering mengulang judul dalam huruf besar lalu huruf kecil.
|
|
68
|
+
|
|
69
|
+
**Yang paling penting, dan inilah yang diuji:** judul **sah** berformat
|
|
70
|
+
`Judul: subjudul`, `Judul: (studi kasus)`, dan `Judul: kata kunci` **tidak**
|
|
71
|
+
ikut terpotong. Karena itu aturannya menuntut bagian sesudah `": "` benar-benar
|
|
72
|
+
**sama** dengan akhiran bagian sebelumnya — bukan sekadar ada titik dua. Pada
|
|
73
|
+
50 judul itu hasilnya: **14 diringkas, 36 dibiarkan, 0 cacat**, dan **15 judul
|
|
74
|
+
bertitik dua tetap utuh**.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **11 asersi regresi baru** di `test/uji-sumber.mjs`, memakai teks yang diambil
|
|
79
|
+
apa adanya dari Garuda. Lima di antaranya justru menjaga agar judul **sah**
|
|
80
|
+
tidak terpotong — tanpa itu, aturannya bisa "lulus" hanya dengan memotong
|
|
81
|
+
setiap judul yang bertitik dua.
|
|
82
|
+
|
|
15
83
|
## [0.1.1] - 2026-09-28
|
|
16
84
|
|
|
17
85
|
### Fixed
|
package/lib/sources.js
CHANGED
|
@@ -74,26 +74,56 @@ function normDoi(doi) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
* mengisi lebar tampilan.
|
|
79
|
-
*
|
|
80
|
-
* kali dalam satu <xmp> sehingga panjangnya 255 huruf.
|
|
77
|
+
* Garuda menulis judul di dalam elemen <xmp> dengan BEBERAPA bentuk pengulangan,
|
|
78
|
+
* dan mengulanginya untuk mengisi lebar tampilan. Keempatnya diukur 28 Sep 2026
|
|
79
|
+
* dari **50 judul sungguhan** (5 kueri):
|
|
81
80
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
81
|
+
* A. periode persis "X X X X X X" (255 huruf)
|
|
82
|
+
* B. diulang sesudah ": " "X: X" (194 huruf)
|
|
83
|
+
* C. awalan lalu "X: X" "NLP, Machine Learning X: X" (270 huruf)
|
|
84
|
+
* D. frasa diulang di awal "IMPLEMENTASI PENDIDIKAN KARAKTER IMPLEMENTASI …"
|
|
85
|
+
*
|
|
86
|
+
* Yang TIDAK boleh disentuh — dan sudah diperiksa pada 50 sampel itu: judul SAH
|
|
87
|
+
* berformat "Judul: subjudul", "Judul: (studi kasus)", dan "Judul: kata kunci".
|
|
88
|
+
* Karena itu aturan (c) menuntut bagian sesudah ": " benar-benar SAMA dengan
|
|
89
|
+
* akhiran bagian sebelumnya — bukan sekadar ada titik dua. Pada 50 sampel,
|
|
90
|
+
* tidak satu pun judul sah ikut terpotong.
|
|
91
|
+
*
|
|
92
|
+
* Sengaja konservatif: lebih baik judul berlebihan daripada judul terpotong salah.
|
|
86
93
|
*/
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
export function runtuhkanJudul(s) {
|
|
95
|
+
let t = (s ?? '').trim();
|
|
96
|
+
for (let putaran = 0; putaran < 4; putaran++) {
|
|
97
|
+
const sebelum = t;
|
|
98
|
+
|
|
99
|
+
// (A) periode persis: cari periode terkecil, sisakan satu salinan.
|
|
100
|
+
const n = t.length;
|
|
101
|
+
for (let p = 10; p <= Math.floor(n / 2); p++) {
|
|
102
|
+
let cocok = true;
|
|
103
|
+
for (let i = p; i < n; i++) {
|
|
104
|
+
if (t[i] !== t[i % p]) { cocok = false; break; }
|
|
105
|
+
}
|
|
106
|
+
if (cocok) { t = t.slice(0, p).trim(); break; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// (D) frasa yang diulang di awal: "A A sisa" -> "A sisa".
|
|
110
|
+
// `A` harus berakhir di batas kata, supaya tidak memotong di tengah kata.
|
|
111
|
+
for (let p = 8; p <= Math.floor(t.length / 2); p++) {
|
|
112
|
+
const a = t.slice(0, p);
|
|
113
|
+
if (!/[\s.)\]]$/.test(a)) continue;
|
|
114
|
+
if (t.startsWith(a + a)) { t = (a + t.slice(2 * p)).trim(); break; }
|
|
95
115
|
}
|
|
96
|
-
|
|
116
|
+
|
|
117
|
+
// (B)/(C) diulang sesudah ": " terakhir — dibandingkan TANPA peduli huruf
|
|
118
|
+
// besar/kecil, karena Garuda sering mengulang judul dengan kapitalisasi beda.
|
|
119
|
+
const pisah = t.lastIndexOf(': ');
|
|
120
|
+
if (pisah > 0) {
|
|
121
|
+
const ekor = t.slice(pisah + 2).trim();
|
|
122
|
+
const kepala = t.slice(0, pisah).trim();
|
|
123
|
+
if (ekor.length >= 20 && kepala.toLowerCase().endsWith(ekor.toLowerCase())) t = kepala;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (t === sebelum) break;
|
|
97
127
|
}
|
|
98
128
|
return t;
|
|
99
129
|
}
|
|
@@ -101,7 +131,7 @@ function runtuhkanPeriodik(s) {
|
|
|
101
131
|
function buatPaper(p) {
|
|
102
132
|
const doi = normDoi(p.doi);
|
|
103
133
|
return {
|
|
104
|
-
title: potong(
|
|
134
|
+
title: potong(runtuhkanJudul(bersihTeks(p.title) ?? ''), 300),
|
|
105
135
|
authors: Array.isArray(p.authors) ? p.authors.map(bersihTeks).filter(Boolean).slice(0, 15) : [],
|
|
106
136
|
year: Number.isFinite(Number(p.year)) && Number(p.year) > 1500 ? Number(p.year) : null,
|
|
107
137
|
source: p.source,
|
|
@@ -429,6 +459,29 @@ function ambilHtml(html, pola, batas) {
|
|
|
429
459
|
return out;
|
|
430
460
|
}
|
|
431
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Saat penanda hasil tidak ditemukan, ada TIGA kemungkinan yang BERBEDA:
|
|
464
|
+
* 1. portal menjawab "nol hasil" -> `[]`, itu hasil yang SAH
|
|
465
|
+
* 2. markup portal berubah -> error
|
|
466
|
+
* 3. portal memblokir/mengalihkan -> error
|
|
467
|
+
*
|
|
468
|
+
* Sebelum ini (1) dan (2) diperlakukan sama, sehingga portal yang benar-benar
|
|
469
|
+
* kosong dilaporkan sebagai **GAGAL**. Itu keliru dengan arah berlawanan:
|
|
470
|
+
* melaporkan kegagalan padahal hasilnya sah-sah saja kosong — sama
|
|
471
|
+
* menyesatkannya dengan menyembunyikan kegagalan.
|
|
472
|
+
*
|
|
473
|
+
* Penanda nol-hasil diukur 28 Sep 2026 memakai kueri omong kosong, lalu
|
|
474
|
+
* dibandingkan dengan kueri yang ada hasilnya:
|
|
475
|
+
*
|
|
476
|
+
* Garuda : "Found 0 documents" (halaman 11.181 huruf)
|
|
477
|
+
* Sinta : header `Results for "<q>"` tetap ada, tautan profil nol
|
|
478
|
+
* (halaman 34.063 huruf) — Sinta TIDAK menulis pesan kosong
|
|
479
|
+
* IOS : "No Results!" (halaman 23.041 huruf)
|
|
480
|
+
*/
|
|
481
|
+
function portalNolHasil(html, ...penanda) {
|
|
482
|
+
return penanda.some((re) => re.test(html));
|
|
483
|
+
}
|
|
484
|
+
|
|
432
485
|
const GARUDA_BASE = 'https://garuda.kemdiktisaintek.go.id';
|
|
433
486
|
|
|
434
487
|
/** Garuda — indeks artikel jurnal Indonesia (Kemdiktisaintek). */
|
|
@@ -442,9 +495,10 @@ async function garuda(query, { rows }) {
|
|
|
442
495
|
journal: 'Garuda (jurnal Indonesia)',
|
|
443
496
|
openAccess: true,
|
|
444
497
|
})).filter((p) => p.title);
|
|
445
|
-
// Penguraian HTML yang tidak menemukan satu pun pola
|
|
446
|
-
//
|
|
498
|
+
// Penguraian HTML yang tidak menemukan satu pun pola bisa berarti DUA hal yang
|
|
499
|
+
// berlawanan. Bedakan: Garuda menulis "Found 0 documents" saat memang kosong.
|
|
447
500
|
if (hasil.length === 0 && !/title-article/.test(html)) {
|
|
501
|
+
if (portalNolHasil(html, /Found\s+0\s+document/i)) return [];
|
|
448
502
|
throw new Error('markup Garuda berubah: penanda "title-article" tidak ditemukan lagi');
|
|
449
503
|
}
|
|
450
504
|
return hasil;
|
|
@@ -461,7 +515,12 @@ async function sinta(query, { rows }) {
|
|
|
461
515
|
journal: 'Sinta (jurnal terakreditasi)',
|
|
462
516
|
openAccess: true,
|
|
463
517
|
})).filter((p) => p.title);
|
|
518
|
+
// Sinta TIDAK menulis pesan kosong — ia tetap menampilkan header
|
|
519
|
+
// `Results for "<kueri>"` lalu daftar yang kosong. Jadi header itu yang
|
|
520
|
+
// dipakai sebagai bukti "halaman dimuat, hasilnya memang nol".
|
|
521
|
+
// (Sinta mendaftar JURNAL, bukan artikel, jadi kueri spesifik memang sering nol.)
|
|
464
522
|
if (hasil.length === 0 && !/journals\/profile\//.test(html)) {
|
|
523
|
+
if (portalNolHasil(html, /Results for/i)) return [];
|
|
465
524
|
throw new Error('markup Sinta berubah: tautan "journals/profile" tidak ditemukan lagi');
|
|
466
525
|
}
|
|
467
526
|
return hasil;
|
|
@@ -479,6 +538,7 @@ async function ios(query, { rows }) {
|
|
|
479
538
|
openAccess: true,
|
|
480
539
|
})).filter((p) => p.title);
|
|
481
540
|
if (hasil.length === 0 && !/class="title"/.test(html)) {
|
|
541
|
+
if (portalNolHasil(html, /No Results!/i)) return [];
|
|
482
542
|
throw new Error('markup IOS berubah: tautan hasil ber-class "title" tidak ditemukan lagi');
|
|
483
543
|
}
|
|
484
544
|
return hasil;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-paper-search",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Literature search for DeepSeek Harness: thirteen international sources (Crossref, OpenAlex, DOAJ, Europe PMC, PubMed, PubMed Central, arXiv, Semantic Scholar, CORE, Zenodo, HAL, IACR ePrint, Unpaywall) and three Indonesian journal portals (Garuda, Sinta, IOS OneSearch), all native HTTP with no external CLI required, plus a status tool that reports which sources answer and which credentials are configured.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|