dsh-paper-search 0.1.0 → 0.1.2

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 CHANGED
@@ -4,13 +4,84 @@
4
4
 
5
5
  | dsh-paper-search | DeepSeek Harness | Diuji pada |
6
6
  |---|---|---|
7
- | 0.1.0 | 0.1.7-rc.2 | 28 Sep 2026, Windows, Node 24 |
7
+ | 0.1.2 | 0.1.7-rc.2 | 28 Sep 2026, Windows, Node 24 |
8
+ | ~~0.1.0~~ | — | **JANGAN DIPAKAI — plugin gagal dimuat, lihat 0.1.1** |
8
9
 
9
10
  Paket ini tidak memakai API internal DSH selain `ctx.tools.register` dan
10
11
  `ctx.get('skills').registerProvider` — keduanya sudah dipakai plugin lain yang
11
12
  berjalan di profil ini (`dsh-search-cascade`), jadi titik patahnya sama dan sudah
12
13
  diketahui.
13
14
 
15
+ ## [0.1.2] - 2026-09-28
16
+
17
+ ### Fixed
18
+
19
+ - **Judul Garuda masih bisa terduplikasi.** Perbaikan di rilis sebelumnya baru
20
+ menangani pengulangan **persis** (`X X X`). Setelah mengukur **50 judul
21
+ sungguhan** (5 kueri, diambil apa adanya dari halaman Garuda), ternyata ada
22
+ **empat** bentuk pengulangan, bukan satu:
23
+
24
+ | Bentuk | Contoh | Panjang |
25
+ |---|---|---|
26
+ | A — periode persis | `X X X X X X` | 255 → 43 |
27
+ | B — diulang sesudah `": "` | `X: X` | 194 → 96 |
28
+ | C — awalan lalu `X: X` | `NLP, Machine Learning X: X` | 270 → 145 |
29
+ | D — frasa diulang di awal | `IMPLEMENTASI PENDIDIKAN KARAKTER IMPLEMENTASI …` | 306 → 295 |
30
+
31
+ `runtuhkanPeriodik()` diganti **`runtuhkanJudul()`** yang menangani keempatnya,
32
+ termasuk pengulangan dengan **kapitalisasi berbeda** (252 → 125) — Garuda
33
+ memang sering mengulang judul dalam huruf besar lalu huruf kecil.
34
+
35
+ **Yang paling penting, dan inilah yang diuji:** judul **sah** berformat
36
+ `Judul: subjudul`, `Judul: (studi kasus)`, dan `Judul: kata kunci` **tidak**
37
+ ikut terpotong. Karena itu aturannya menuntut bagian sesudah `": "` benar-benar
38
+ **sama** dengan akhiran bagian sebelumnya — bukan sekadar ada titik dua. Pada
39
+ 50 judul itu hasilnya: **14 diringkas, 36 dibiarkan, 0 cacat**, dan **15 judul
40
+ bertitik dua tetap utuh**.
41
+
42
+ ### Added
43
+
44
+ - **11 asersi regresi baru** di `test/uji-sumber.mjs`, memakai teks yang diambil
45
+ apa adanya dari Garuda. Lima di antaranya justru menjaga agar judul **sah**
46
+ tidak terpotong — tanpa itu, aturannya bisa "lulus" hanya dengan memotong
47
+ setiap judul yang bertitik dua.
48
+
49
+ ## [0.1.1] - 2026-09-28
50
+
51
+ ### Fixed
52
+
53
+ - **Plugin gagal dimuat sama sekali — perbaikan yang wajib.** DSH mewajibkan
54
+ setiap tool mendeklarasikan `output: { schema, render }`. Versi 0.1.0 tidak
55
+ punya itu, sehingga `ctx.tools.register` **melempar**:
56
+
57
+ ```
58
+ TypeError: tool "paper_search" must declare output { schema, render, presentationMeta? }
59
+ ```
60
+
61
+ Karena lemparan itu terjadi di dalam `apply()`, **seluruh plugin batal
62
+ dimuat** — kedua tool **dan** skill-nya tidak pernah terdaftar. Pengguna 0.1.0
63
+ tidak akan melihat `paper_search` sama sekali.
64
+
65
+ Ditemukan lewat **uji boot DSH sungguhan**, bukan lewat uji `ctx` tiruan:
66
+ `register` tiruan hanya menampung definisinya, jadi tidak pernah menegakkan
67
+ syarat ini. Inilah bukti bahwa tiruan tidak bisa menggantikan harness nyata.
68
+
69
+ **Kalau Anda memasang 0.1.0, naikkan ke 0.1.1.**
70
+
71
+ ### Added
72
+
73
+ - `test/uji-di-dsh.mjs` — **uji boot DSH sungguhan.** Menyalin profil `headless`
74
+ ke profil uji (profil `desktop` yang hidup **tidak disentuh**), menyisipkan
75
+ plugin ini plus satu **plugin saksi**, menjalankan DSH nyata, lalu memeriksa
76
+ dari DALAM bahwa: kedua tool terdaftar, provider skill terdaftar, skill
77
+ muncul di katalog, dan `paper_search` benar-benar dipanggil sampai
78
+ mengembalikan paper sungguhan. **22 asersi, 0 gagal.**
79
+
80
+ Uji ini juga mencatat dua jebakan yang sudah terukur, supaya tidak diulang:
81
+ tanpa `inject` service tidak terlihat dari plugin lain, dan pada detik ke-4
82
+ harness sudah membongkar pluginnya sehingga pemeriksaan **harus** berjalan di
83
+ dalam `apply()`.
84
+
14
85
  ## [0.1.0] - 2026-09-28
15
86
 
16
87
  Rilis pertama.
package/README.md CHANGED
@@ -97,6 +97,33 @@ For Indonesian topics, **use Indonesian keywords**: `"pendidikan karakter"`
97
97
  returns real Indonesian journals, while the English equivalent returns
98
98
  international results that do not match the intent.
99
99
 
100
+ ## Verification status
101
+
102
+ Stated precisely, because "tested" on its own is not a useful claim:
103
+
104
+ **Verified — the connectors, against live APIs and pages.** `node test/uji-sumber.mjs`
105
+ runs all sixteen against the real services: 8/8 assertions. On the last run 15 of
106
+ 16 answered. The one that did not was PubMed Central returning a transient
107
+ `HTTP 500 error forwarding request` — and it was reported as a failure, which is
108
+ the behaviour this package exists to provide.
109
+
110
+ **Verified — the wiring, against a mock Cordis context.** `node test/uji-plugin.mjs`,
111
+ 29/29 assertions: both tools register, the skill provider reports rank 450,
112
+ the frontmatter parses within the 500-character catalogue limit, and both tools
113
+ reach the network end to end.
114
+
115
+ **Verified — loading the bundle inside a live harness.** `node test/uji-di-dsh.mjs`
116
+ copies the `headless` profile to a throwaway test profile, inserts this plugin
117
+ plus a witness plugin, boots a real DSH, and checks **from inside** that both
118
+ tools are registered, the skill provider is registered, the skill appears in the
119
+ catalogue, and `paper_search` is actually called and returns real papers.
120
+ **22 assertions, 0 failures.**
121
+
122
+ That last test earned its place: it caught a fatal bug the mock never could.
123
+ DSH requires every tool to declare `output: { schema, render }`, and 0.1.0 did
124
+ not — so registration threw, and because the throw happened inside `apply()`,
125
+ the whole plugin failed to load. **0.1.0 is unusable; use 0.1.1.**
126
+
100
127
  ## What makes the reporting honest
101
128
 
102
129
  Every result separates **sources that failed** from sources that answered:
package/lib/index.js CHANGED
@@ -188,6 +188,31 @@ function formatHasil(hasil) {
188
188
 
189
189
  // ── Tool ─────────────────────────────────────────────────────────────────────
190
190
 
191
+ /**
192
+ * Bentuk `output` yang DIWAJIBKAN DSH untuk setiap tool.
193
+ *
194
+ * Ditemukan lewat uji boot sungguhan (`test/uji-di-dsh.mjs`) — dan ini bug yang
195
+ * lolos dari `ctx` tiruan, karena `register` tiruan hanya menampung definisinya.
196
+ * DSH melempar:
197
+ *
198
+ * TypeError: tool "paper_search" must declare output { schema, render,
199
+ * presentationMeta? }
200
+ *
201
+ * Karena lemparan itu terjadi di dalam `apply()`, SELURUH plugin gagal dimuat —
202
+ * tool maupun skill-nya sama sekali tidak terdaftar.
203
+ *
204
+ * Kedua tool di sini mengembalikan TEKS yang sudah diformat, jadi `schema`
205
+ * menyatakan string dan `render` membungkusnya menjadi blok konten — bentuk
206
+ * yang sama dengan `txt()` milik `dsh-search-cascade`
207
+ * (`[{ type: 'text', text: '...' }]`).
208
+ */
209
+ function keluaranTeks(deskripsi) {
210
+ return {
211
+ schema: { type: 'string', description: deskripsi },
212
+ render: (_args, value) => [{ type: 'text', text: String(value ?? '') }],
213
+ };
214
+ }
215
+
191
216
  function toolSearch(kredensial) {
192
217
  return {
193
218
  name: 'paper_search',
@@ -207,6 +232,7 @@ function toolSearch(kredensial) {
207
232
  },
208
233
  required: ['query'],
209
234
  },
235
+ output: keluaranTeks('Laporan teks: query, jumlah sumber yang menjawab, jumlah paper setelah dedup, baris status per sumber (OK/GAGAL), lalu daftar paper.'),
210
236
  execute: async (args) => {
211
237
  const hasil = await jalankanPencarian(args ?? {}, kredensial);
212
238
  return formatHasil(hasil);
@@ -229,6 +255,7 @@ function toolStatus(kredensial) {
229
255
  },
230
256
  required: [],
231
257
  },
258
+ output: keluaranTeks('Laporan teks: daftar kredensial yang terbaca beserta asalnya, baris status tiap sumber (OK/GAGAL beserta waktu respons), dan catatan tambahan.'),
232
259
  execute: async (args) => {
233
260
  const probe = args?.probe !== false;
234
261
  const barisPilihan = pilihSumber(args?.sources);
package/lib/sources.js CHANGED
@@ -74,26 +74,56 @@ function normDoi(doi) {
74
74
  }
75
75
 
76
76
  /**
77
- * Beberapa portal menulis judul di dalam elemen <xmp> dan MENGULANGNYA untuk
78
- * mengisi lebar tampilan. Terukur di Garuda 28 Sep 2026: judul
79
- * "PENDIDIKAN KARAKTER ANAK USIA SEKOLAH DASAR" (44 huruf) muncul sekitar enam
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
- * Fungsi ini mencari periode terkecil dan menyisakan SATU salinan. Ia hanya
83
- * bertindak bila pengulangannya PERSIS dari awal sampai akhir — kalau tidak,
84
- * teks dibiarkan apa adanya. Sengaja konservatif: lebih baik judul berlebihan
85
- * daripada judul yang terpotong salah.
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 runtuhkanPeriodik(s) {
88
- const t = (s ?? '').trim();
89
- const n = t.length;
90
- if (n < 30) return t;
91
- for (let p = 10; p <= Math.floor(n / 2); p++) {
92
- let cocok = true;
93
- for (let i = p; i < n; i++) {
94
- if (t[i] !== t[i % p]) { cocok = false; break; }
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
- if (cocok) return t.slice(0, p).trim();
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(runtuhkanPeriodik(bersihTeks(p.title) ?? ''), 300),
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-paper-search",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",