laporan-generator 2.8.0 → 2.9.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.
Files changed (52) hide show
  1. package/.agents/skills/laporan-generator/SKILL.md +14 -9
  2. package/.agents/skills/laporan-generator/references/campus-guide.md +8 -2
  3. package/.agents/skills/laporan-generator/references/metadata-schema.md +39 -11
  4. package/.agents/skills/laporan-generator/references/template-guide.md +20 -14
  5. package/.agents/skills/laporan-generator/resources/docx.lua +143 -5
  6. package/.agents/skills/laporan-generator/resources/laporan +2 -2
  7. package/.agents/skills/laporan-generator/resources/laporan.ps1 +55 -25
  8. package/.agents/skills/laporan-generator/resources/presets/ipb-ppki.yml +27 -0
  9. package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +6 -6
  10. package/.agents/skills/laporan-generator/resources/presets/telkom-ta.yml +26 -0
  11. package/.agents/skills/laporan-generator/resources/presets/ub-skripsi.yml +26 -0
  12. package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +6 -6
  13. package/.agents/skills/laporan-generator/resources/presets/unair-skripsi.yml +26 -0
  14. package/.agents/skills/laporan-generator/resources/presets/undip-skripsi.yml +26 -0
  15. package/.agents/skills/laporan-generator/resources/scripts/bundle.py +7 -1
  16. package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +6 -2
  17. package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +13 -0
  18. package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +9 -3
  19. package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +34 -1
  20. package/.agents/skills/laporan-generator/scripts/build-docx.sh +122 -0
  21. package/.agents/skills/laporan-generator/scripts/build.sh +32 -8
  22. package/.agents/skills/laporan-generator/scripts/bundle.py +7 -1
  23. package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +6 -2
  24. package/.agents/skills/laporan-generator/scripts/finalize-docx.py +13 -0
  25. package/.agents/skills/laporan-generator/scripts/laporan +55 -5
  26. package/.agents/skills/laporan-generator/scripts/laporan.ps1 +628 -529
  27. package/.agents/skills/laporan-generator/scripts/setup-deps.sh +9 -3
  28. package/.agents/skills/laporan-generator/scripts/test_scripts.py +34 -1
  29. package/README.md +24 -19
  30. package/bin/laporan-generator.js +34 -6
  31. package/docs/campus-guide.md +8 -2
  32. package/docs/metadata-schema.md +37 -11
  33. package/docs/template-guide.md +20 -14
  34. package/docx.lua +143 -5
  35. package/laporan +2 -2
  36. package/laporan.ps1 +55 -25
  37. package/lib/init.js +2 -0
  38. package/lib/mcp.js +11 -9
  39. package/lib/sync-hosts.js +26 -2
  40. package/package.json +3 -2
  41. package/presets/ipb-ppki.yml +27 -0
  42. package/presets/its-skripsi.yml +6 -6
  43. package/presets/telkom-ta.yml +26 -0
  44. package/presets/ub-skripsi.yml +26 -0
  45. package/presets/ui-skripsi.yml +6 -6
  46. package/presets/unair-skripsi.yml +26 -0
  47. package/presets/undip-skripsi.yml +26 -0
  48. package/scripts/bundle.py +7 -1
  49. package/scripts/docx-pagenum.py +6 -2
  50. package/scripts/finalize-docx.py +13 -0
  51. package/scripts/setup-deps.sh +9 -3
  52. package/scripts/test_scripts.py +34 -1
@@ -31,12 +31,12 @@ When available in your environment, **always prefer calling the native MCP tools
31
31
 
32
32
  | Action | Native MCP Tool | CLI Fallback |
33
33
  | :--- | :--- | :--- |
34
- | **Initialize Project** | `laporan_init({ preset, title, author_name, institution })` | `npx laporan-generator init` |
34
+ | **Initialize Project** | `laporan_init({ preset, title, author_name, author_id, institution, daftar_gambar, daftar_tabel })` | `npx laporan-generator init` |
35
35
  | **Audit & Health Check** | `laporan_doctor({ target_dir })` | `./laporan doctor` or `npx laporan-generator doctor --json` |
36
36
  | **Document Stats & Metrics** | `laporan_stats({ target_dir })` | `./laporan stats` or `npx laporan-generator stats --json` |
37
37
  | **Inspect Campus Presets** | `laporan_presets({ action: "list" \| "get", preset_name })` | `./laporan preset list` |
38
- | **Search & Add Citations** | `laporan_citations({ action: "search_doi" \| "add_citation", query, doi })` | Manual edit `references.bib` |
39
- | **Compile PDF & DOCX** | `laporan_build({ format: "all" \| "pdf" \| "docx" })` | `./laporan build` or `bash build.sh && make docx` |
38
+ | **Search & Add Citations** | `laporan_citations({ action: "search_doi" \| "get_bibtex" \| "add_citation" \| "validate", query, doi, bibtex })` | Manual edit `references.bib` |
39
+ | **Compile PDF & DOCX** | `laporan_build({ target_dir, format: "all" \| "pdf" \| "docx" })` | `./laporan build` or `npx laporan-generator build` |
40
40
 
41
41
  --------------------------------------------------------------------------------
42
42
 
@@ -51,12 +51,17 @@ Identify the document type and target university:
51
51
  3. **Makalah Kuliah / Paper Ilmiah**: Focus on research problem, methodology, findings, and discussion. (In Indonesia, course papers use standard Roman Bab headings: `BAB I`, `BAB II`, etc.).
52
52
  4. **Target University Preset**:
53
53
  - `skripsi-4433`: National Standard (Top 4cm, Left 4cm, Bottom 3cm, Right 3cm).
54
- - `ui-skripsi`: Universitas Indonesia (Buku Panduan UI).
55
- - `itb-ta`: Institut Teknologi Bandung (Pedoman TA ITB).
56
- - `ugm-skripsi`: Universitas Gadjah Mada (Format Skripsi UGM).
57
- - `its-skripsi`: Institut Teknologi Sepuluh Nopember.
58
- - `unpad-skripsi`: Universitas Padjadjaran.
59
- - `standard`: General University (2.5cm / 2cm).
54
+ - `ui-skripsi`: Universitas Indonesia (Left 4cm, Top/Right/Bottom 3cm).
55
+ - `itb-ta`: Institut Teknologi Bandung (Pedoman TA ITB 4-4-3-3, Minimalist Cover).
56
+ - `ugm-skripsi`: Universitas Gadjah Mada (Format Skripsi UGM 4-4-3-3).
57
+ - `its-skripsi`: Institut Teknologi Sepuluh Nopember (Top 3cm, Left 3cm, Bottom 2.5cm, Right 2cm).
58
+ - `unpad-skripsi`: Universitas Padjadjaran (4-4-3-3).
59
+ - `ub-skripsi`: Universitas Brawijaya (4-4-3-3).
60
+ - `undip-skripsi`: Universitas Diponegoro (4-4-3-3).
61
+ - `unair-skripsi`: Universitas Airlangga (4-4-3-3).
62
+ - `ipb-ppki`: IPB University - Pedoman PPKI (Left 4cm, Top/Right/Bottom 3cm, 1cm indent).
63
+ - `telkom-ta`: Telkom University (Left 4cm, Top/Right/Bottom 3cm).
64
+ - `standard`: General University (Top 2cm, Bottom 3cm, Left 2.5cm, Right 2.5cm).
60
65
 
61
66
  ### Stage 2: Document Structure Confirmation & Scaffolding
62
67
  1. **Confirm Document Structure**:
@@ -29,10 +29,16 @@ Atau gunakan bantuan CLI interaktif:
29
29
  |---|---|---|---|
30
30
  | `standard` | Standar Modern | 2.0 cm - 3.0 cm - 2.5 cm - 2.5 cm | Format default tugas akhir, magang, praktikum |
31
31
  | `skripsi-4433` | Skripsi Tradisional | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Hardcover jilid tebal margin kiri 4cm |
32
- | `ui-skripsi` | Universitas Indonesia (UI) | 2.5 cm - 2.5 cm - 2.5 cm - 2.5 cm | Sesuai Pedoman Karya Ilmiah UI |
32
+ | `ui-skripsi` | Universitas Indonesia (UI) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Sesuai Pedoman Teknis Tugas Akhir UI |
33
33
  | `itb-ta` | Institut Teknologi Bandung (ITB) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Cover minimalis (tanpa dosen pembimbing di cover luar) |
34
34
  | `ugm-skripsi` | Universitas Gadjah Mada (UGM) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Full academic cover |
35
- | `its-skripsi` | Institut Teknologi Sepuluh Nopember (ITS) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Standard Tugas Akhir & Tesis ITS |
35
+ | `its-skripsi` | Institut Teknologi Sepuluh Nopember (ITS) | 3.0 cm - 2.5 cm - 3.0 cm - 2.0 cm | Sesuai Buku Pedoman TA & Tesis ITS |
36
+ | `unpad-skripsi` | Universitas Padjadjaran (UNPAD) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Format skripsi & karya ilmiah UNPAD |
37
+ | `ub-skripsi` | Universitas Brawijaya (UB) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman penulisan skripsi UB |
38
+ | `undip-skripsi` | Universitas Diponegoro (UNDIP) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman akademik skripsi UNDIP |
39
+ | `unair-skripsi` | Universitas Airlangga (UNAIR) | 4.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman penulisan karya ilmiah UNAIR |
40
+ | `ipb-ppki` | IPB University (PPKI) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman PPKI IPB (Indent 1cm, sub-bab tanpa titik akhir) |
41
+ | `telkom-ta` | Telkom University (Tel-U) | 3.0 cm - 3.0 cm - 4.0 cm - 3.0 cm | Pedoman Buku Tugas Akhir Telkom University |
36
42
 
37
43
  ---
38
44
 
@@ -20,14 +20,33 @@ lecturer_label: "Dosen Pengampu:" # Opsional: "Dosen Pembimbing:", "Pembimbing
20
20
  course: "Pengolahan Citra Digital dan Pembelajaran Mesin"
21
21
  institution: "Universitas Gadjah Mada"
22
22
  faculty: "Departemen Teknik Elektro dan Teknologi Informasi"
23
+ department: "Teknologi Informasi"
24
+ city: "Yogyakarta"
23
25
  year: "2025/2026"
24
26
  date: "Juli 2026"
25
- abstract: |
26
- Laporan ini membahas pipeline otomatisasi dokumen akademik
27
- menggunakan Markdown, Pandoc, Typst, dan Bash. Pipeline ini
28
- memungkinkan penulisan konten laporan dalam format Markdown
29
- yang kemudian dikonversi menjadi PDF berkualitas percetakan
30
- melalui Pandoc dan Typst serta berkas Microsoft Word (.docx).
27
+ preset: "ugm-skripsi"
28
+ daftar_gambar: false
29
+ daftar_tabel: false
30
+ # Komponen opsional (aktifkan sesuai kebutuhan):
31
+ # approval:
32
+ # city: "Yogyakarta"
33
+ # date: "20 Juli 2026"
34
+ # supervisors:
35
+ # - name: "Dr. Ir. Hendra Wijaya, M.T."
36
+ # nip: "197501012000031001"
37
+ # role: "Pembimbing Utama"
38
+ # dean:
39
+ # name: "Prof. Dr. Eng. Dekan Fakultas, M.T."
40
+ # nip: "196803031992031003"
41
+ # role: "Dekan Fakultas Teknik"
42
+ # abstract_id: |
43
+ # Laporan ini membahas pipeline otomatisasi dokumen akademik
44
+ # menggunakan Markdown, Pandoc, Typst, dan Bash.
45
+ # keywords_id: ["Pandoc", "Typst", "OpenXML", "Otomatisasi Dokumen"]
46
+ # abstract_en: |
47
+ # This report discusses an automated academic document pipeline
48
+ # built with Markdown, Pandoc, Typst, and Bash.
49
+ # keywords_en: ["Pandoc", "Typst", "OpenXML", "Document Automation"]
31
50
  ...
32
51
  ```
33
52
 
@@ -37,19 +56,28 @@ abstract: |
37
56
 
38
57
  | Field | Tipe Data | Wajib? | Keterangan |
39
58
  |---|---|---|---|
40
- | `title` | String | **Ya** | Judul utama laporan (tampil tebal di halaman sampul). |
59
+ | `title` | String | **Ya** | Judul utama laporan (tampil tebal piramida terbalik di halaman sampul). |
41
60
  | `subtitle` | String | Opsional | Sub-judul atau keterangan tambahan laporan. |
42
61
  | `author` | List of Objects | **Ya** | Daftar penulis. Setiap entri memiliki `name` dan `nim`. |
43
62
  | `author[].name` | String | **Ya** | Nama lengkap mahasiswa / penyusun. |
44
63
  | `author[].nim` | String | **Ya** | Nomor Induk Mahasiswa (NIM / NPM / NISN). |
45
64
  | `lecturer` | String | **Ya** | Nama lengkap dan gelar Dosen / Guru Pengampu / Pembimbing. |
46
65
  | `lecturer_label` | String | Opsional | Label peran dosen (default: `"Dosen Pengampu:"`, bisa `"Dosen Pembimbing:"`). |
47
- | `course` | String | **Ya** | Nama Mata Kuliah atau Mata Pelajaran. |
66
+ | `course` | String | **Ya** | Nama Mata Kuliah atau Jenis Karya Tulis. |
48
67
  | `institution` | String | **Ya** | Nama Universitas, Institut, Politeknik, atau Sekolah. |
49
- | `faculty` | String | **Ya** | Nama Fakultas, Departemen, atau Program Studi. |
50
- | `year` | String | **Ya** | Tahun Ajaran (contoh: `2025/2026`). |
68
+ | `faculty` | String | **Ya** | Nama Fakultas, Sekolah, atau Departemen. |
69
+ | `department` | String | Opsional | Nama Program Studi atau Jurusan. |
70
+ | `city` | String | Opsional | Kota tempat institusi berada (digunakan pada lembar pengesahan). |
71
+ | `year` | String | **Ya** | Tahun Ajaran atau Tahun Kelulusan (contoh: `2026` atau `2025/2026`). |
51
72
  | `date` | String | Opsional | Bulan dan Tahun pembuatan (contoh: `Juli 2026`). |
52
- | `abstract` | Multiline String | Opsional | Ringkasan/Abstrak laporan dalam Bahasa Indonesia. |
73
+ | `preset` | String | Opsional | ID preset format kampus (`standard`, `skripsi-4433`, `ui-skripsi`, `itb-ta`, `ugm-skripsi`, `its-skripsi`, `unpad-skripsi`, `ub-skripsi`, `undip-skripsi`, `unair-skripsi`, `ipb-ppki`, `telkom-ta`). |
74
+ | `daftar_gambar` | Boolean | Opsional | Aktifkan halaman DAFTAR GAMBAR (`true`/`false`, default: `false`). |
75
+ | `daftar_tabel` | Boolean | Opsional | Aktifkan halaman DAFTAR TABEL (`true`/`false`, default: `false`). |
76
+ | `approval` | Object | Opsional | Konfigurasi Lembar Pengesahan (`city`, `date`, `supervisors[]`, `dean`). |
77
+ | `abstract_id` | Multiline String | Opsional | Ringkasan/Abstrak laporan dalam Bahasa Indonesia (Opt-In). |
78
+ | `keywords_id` | List of Strings | Opsional | Daftar kata kunci Bahasa Indonesia. |
79
+ | `abstract_en` | Multiline String | Opsional | Ringkasan/Abstract laporan dalam Bahasa Inggris (Opt-In). |
80
+ | `keywords_en` | List of Strings | Opsional | Daftar kata kunci Bahasa Inggris (Keywords). |
53
81
 
54
82
  ---
55
83
 
@@ -17,16 +17,15 @@ File `template.typ` merupakan jantung dari pemformatan visual PDF pada **Laporan
17
17
  ```typst
18
18
  #set page(
19
19
  paper: "a4",
20
- margin: (top: 2cm, bottom: 3cm, left: 2.5cm, right: 2.5cm),
20
+ margin: doc-margin,
21
+ numbering: none,
21
22
  )
22
23
  #set text(font: "Libertinus Serif", size: 12pt)
23
- #set par(justify: true, leading: 1.5em)
24
+ #set par(justify: true, leading: 0.75em, first-line-indent: 1.25cm)
24
25
  ```
25
- * Margin Kiri & Kanan: 2.5 cm
26
- * Margin Atas: 2.0 cm
27
- * Margin Bawah: 3.0 cm
28
- * Spasi Antar Baris: 1.5 spasi (*one half spacing*)
29
- * Paragraf rata kanan-kiri (*justify*)
26
+ * Margin Default (`standard`): Atas 2.0 cm, Bawah 3.0 cm, Kiri 2.5 cm, Kanan 2.5 cm (atau mengikuti `presets/*.yml` aktif).
27
+ * Spasi Antar Baris: `0.75em` (setara 1.5 spasi di Typst).
28
+ * Paragraf rata kanan-kiri (*justify*) dengan indentasi baris pertama `1.25cm`.
30
29
 
31
30
  ---
32
31
 
@@ -36,28 +35,35 @@ File `template.typ` merupakan jantung dari pemformatan visual PDF pada **Laporan
36
35
  ```typst
37
36
  #set heading(numbering: (..ns) => {
38
37
  if ns.len() == 1 {
39
- "BAB " + numbering("I", ns.at(0)) + linebreak()
38
+ if chapter-prefix != "" {
39
+ chapter-prefix + format-chapter-num(ns.at(0))
40
+ } else {
41
+ format-chapter-num(ns.at(0))
42
+ }
40
43
  } else if ns.len() == 2 {
41
- numbering("1.1", ..ns)
44
+ numbering("1.1.", ..ns)
42
45
  } else {
43
46
  numbering("1.1.1", ..ns)
44
47
  }
45
48
  })
46
49
  ```
47
- * **Bab**: Diformat dengan Angka Romawi (`BAB I`, `BAB II`, `BAB III`, dst). Angka Romawi dan judul dipisahkan oleh *line break*.
48
- * **Sub-bab**: Diformat dengan Angka Arab (`1.1`, `1.2`, `2.1`).
50
+ * **Bab**: Diformat dengan Angka Romawi (`BAB I`, `BAB II`, `BAB III`, dst) atau Angka Arab sesuai preset (`heading_chapter_num_format`). Nomor bab dan judul dipisahkan oleh *line break* pada `#show heading.where(level: 1)`.
51
+ * **Sub-bab**: Diformat dengan Angka Arab (`1.1.`, `1.2.`, `2.1.`) sesuai flag `heading_sub_dot`.
49
52
  * **Sub-sub-bab**: Diformat dengan Angka Arab 3 tingkat (`1.1.1`, `1.1.2`).
50
53
  * **Heading tanpa nomor** (Kata Pengantar, Daftar Isi, Daftar Pustaka): menggunakan `{-}` di Markdown yang diterjemahkan Pandoc menjadi `#heading(level: 1, numbering: none)`.
51
54
 
52
55
  > **Catatan**: Jangan menulis pola `"BAB I"` langsung sebagai format string `numbering(...)`. Typst menginterpretasikan rangkaian huruf `BAB` sebagai pola huruf dan memotong angka Romawinya. Selalu concat literal secara manual: `"BAB " + numbering("I", n)`.
53
56
 
54
57
  ### 2. Penomoran Halaman (frontmatter vs mainmatter)
55
- * **Bagian Depan** (Kata Pengantar, Daftar Isi): Menggunakan penomoran Romawi kapital (`I`, `II`, `III`) di bagian tengah bawah. Diatur di `cover.md`:
58
+ * **Bagian Depan** (Lembar Pengesahan, Abstrak, Kata Pengantar, Daftar Isi): Menggunakan penomoran Romawi kecil (`i`, `ii`, `iii`) di bagian tengah bawah. Diatur di `template.typ` tepat setelah sampul depan:
56
59
  ```typst
57
- #set page(numbering: "I")
60
+ #pagebreak()
61
+ #set page(numbering: "i")
62
+ #counter(page).update(1)
58
63
  ```
59
- * **Bagian Utama** (BAB I hingga BAB V): Menggunakan penomoran Angka Arab (`1`, `2`, `3`) berurutan, di-reset ulang:
64
+ * **Bagian Utama** (BAB I hingga BAB V & Daftar Pustaka): Menggunakan penomoran Angka Arab (`1`, `2`, `3`) berurutan, di-reset ulang di akhir `cover.md`:
60
65
  ```typst
66
+ #pagebreak()
61
67
  #set page(numbering: "1")
62
68
  #counter(page).update(1)
63
69
  ```
@@ -14,6 +14,28 @@ local subsub = 0
14
14
  local toc_entries = {}
15
15
  local chapter_prefix = "BAB "
16
16
  local chapter_num_fmt = "roman"
17
+ local sub_dot = true
18
+ local subsub_dot = false
19
+ local doc_margins = { top = 1134, right = 1417, bottom = 1701, left = 1417 }
20
+
21
+ local function dim_to_twips(val, fallback)
22
+ if not val or val == "" then return fallback end
23
+ local num, unit = val:match("^%s*([0-9]+%.?[0-9]*)%s*([a-zA-Z]+)%s*$")
24
+ if not num then return fallback end
25
+ local n = tonumber(num)
26
+ if not n then return fallback end
27
+ unit = unit:lower()
28
+ if unit == "cm" then
29
+ return math.floor(n * 566.929133858 + 0.5)
30
+ elseif unit == "mm" then
31
+ return math.floor(n * 56.6929133858 + 0.5)
32
+ elseif unit == "in" then
33
+ return math.floor(n * 1440 + 0.5)
34
+ elseif unit == "pt" then
35
+ return math.floor(n * 20 + 0.5)
36
+ end
37
+ return fallback
38
+ end
17
39
 
18
40
  local function is_unnumbered(el)
19
41
  for _, cls in ipairs(el.attr.classes or {}) do
@@ -180,6 +202,20 @@ local function Meta(meta)
180
202
  if fmt ~= "" then
181
203
  chapter_num_fmt = fmt
182
204
  end
205
+ if meta["heading_sub_dot"] ~= nil then
206
+ sub_dot = meta_bool(meta, "heading_sub_dot")
207
+ end
208
+ if meta["heading_subsub_dot"] ~= nil then
209
+ subsub_dot = meta_bool(meta, "heading_subsub_dot")
210
+ end
211
+ local m_preset = meta_str(meta, "margin_preset")
212
+ if m_preset == "skripsi-4433" or m_preset == "4-4-3-3" then
213
+ doc_margins = { top = 2268, right = 1701, bottom = 1701, left = 2268 }
214
+ end
215
+ doc_margins.top = dim_to_twips(meta_str(meta, "margin_top"), doc_margins.top)
216
+ doc_margins.bottom = dim_to_twips(meta_str(meta, "margin_bottom"), doc_margins.bottom)
217
+ doc_margins.left = dim_to_twips(meta_str(meta, "margin_left"), doc_margins.left)
218
+ doc_margins.right = dim_to_twips(meta_str(meta, "margin_right"), doc_margins.right)
183
219
  end
184
220
 
185
221
  function Header(el)
@@ -228,20 +264,22 @@ function Header(el)
228
264
  elseif el.level == 2 then
229
265
  sub = sub + 1
230
266
  subsub = 0
267
+ local sep2 = sub_dot and ". " or " "
231
268
  toc_entries[#toc_entries + 1] = {
232
269
  level = 2,
233
270
  id = el.identifier,
234
- text = bab .. "." .. sub .. ". " .. title,
271
+ text = bab .. "." .. sub .. sep2 .. title,
235
272
  }
236
- el.content = pandoc.Str(bab .. "." .. sub .. ". " .. title)
273
+ el.content = pandoc.Str(bab .. "." .. sub .. sep2 .. title)
237
274
  elseif el.level == 3 then
238
275
  subsub = subsub + 1
276
+ local sep3 = subsub_dot and ". " or " "
239
277
  toc_entries[#toc_entries + 1] = {
240
278
  level = 3,
241
279
  id = el.identifier,
242
- text = bab .. "." .. sub .. "." .. subsub .. " " .. title,
280
+ text = bab .. "." .. sub .. "." .. subsub .. sep3 .. title,
243
281
  }
244
- el.content = pandoc.Str(bab .. "." .. sub .. "." .. subsub .. " " .. title)
282
+ el.content = pandoc.Str(bab .. "." .. sub .. "." .. subsub .. sep3 .. title)
245
283
  end
246
284
  return el
247
285
  end
@@ -434,9 +472,109 @@ function Pandoc(doc)
434
472
  -- Section break (Next Page) penutup cover: tanpa footer/nomor halaman
435
473
  blocks[#blocks + 1] = pandoc.RawBlock(
436
474
  "openxml",
437
- '<w:p><w:pPr><w:sectPr><w:footnotePr><w:numRestart w:val="eachSect"/></w:footnotePr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1134" w:right="1417" w:bottom="1701" w:left="1417" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr></w:pPr></w:p>'
475
+ string.format(
476
+ '<w:p><w:pPr><w:sectPr><w:footnotePr><w:numRestart w:val="eachSect"/></w:footnotePr><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="%d" w:right="%d" w:bottom="%d" w:left="%d" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr></w:pPr></w:p>',
477
+ doc_margins.top, doc_margins.right, doc_margins.bottom, doc_margins.left
478
+ )
438
479
  )
439
480
 
481
+ -- Optional Lembar Pengesahan (approval)
482
+ if meta["approval"] ~= nil and type(meta["approval"]) == "table" then
483
+ local app = meta["approval"]
484
+ local app_city = meta_str(app, "city")
485
+ local app_date = meta_str(app, "date")
486
+ if app_date == "" then app_date = meta_str(meta, "date") end
487
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("LEMBAR PENGESAHAN") }, pandoc.Attr("lembar-pengesahan", { "unnumbered" }, {}))
488
+ if title ~= "" then
489
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(string.upper(title))) }, "CoverLine")
490
+ end
491
+ local date_line = (app_city ~= "" and (app_city .. ", ") or "") .. app_date
492
+ if date_line ~= "" then
493
+ blocks[#blocks + 1] = para({ pandoc.Str("Telah diperiksa dan disetujui pada: " .. date_line) }, "CoverLine")
494
+ end
495
+ if app["supervisors"] ~= nil and type(app["supervisors"]) == "table" then
496
+ for _, sup in ipairs(app["supervisors"]) do
497
+ local s_role = pandoc.utils.stringify(sup["role"] or "Pembimbing")
498
+ local s_name = pandoc.utils.stringify(sup["name"] or "")
499
+ local s_nip = pandoc.utils.stringify(sup["nip"] or "")
500
+ blocks[#blocks + 1] = spacer(360)
501
+ blocks[#blocks + 1] = para({ pandoc.Str(s_role) }, "CoverLine")
502
+ blocks[#blocks + 1] = spacer(480)
503
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(s_name)) }, "CoverName")
504
+ if s_nip ~= "" then
505
+ blocks[#blocks + 1] = para({ pandoc.Str("NIP. " .. s_nip) }, "CoverLine")
506
+ end
507
+ end
508
+ end
509
+ if app["dean"] ~= nil and type(app["dean"]) == "table" then
510
+ local d_role = pandoc.utils.stringify(app["dean"]["role"] or "Dekan")
511
+ local d_name = pandoc.utils.stringify(app["dean"]["name"] or "")
512
+ local d_nip = pandoc.utils.stringify(app["dean"]["nip"] or "")
513
+ blocks[#blocks + 1] = spacer(360)
514
+ blocks[#blocks + 1] = para({ pandoc.Str("Mengetahui, " .. d_role) }, "CoverLine")
515
+ blocks[#blocks + 1] = spacer(480)
516
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(d_name)) }, "CoverName")
517
+ if d_nip ~= "" then
518
+ blocks[#blocks + 1] = para({ pandoc.Str("NIP. " .. d_nip) }, "CoverLine")
519
+ end
520
+ end
521
+ blocks[#blocks + 1] = pagebreak()
522
+ end
523
+
524
+ -- Optional Abstrak Bahasa Indonesia (abstract_id)
525
+ local abs_id = meta_str(meta, "abstract_id")
526
+ if abs_id ~= "" then
527
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("ABSTRAK") }, pandoc.Attr("abstrak", { "unnumbered" }, {}))
528
+ if title ~= "" then
529
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Str(string.upper(title))) }, "CoverLine")
530
+ end
531
+ blocks[#blocks + 1] = pandoc.Para({ pandoc.Str(abs_id) })
532
+ if meta["keywords_id"] ~= nil then
533
+ local kw_list = {}
534
+ if type(meta["keywords_id"]) == "table" and meta["keywords_id"][1] ~= nil then
535
+ for _, k in ipairs(meta["keywords_id"]) do
536
+ kw_list[#kw_list + 1] = pandoc.utils.stringify(k)
537
+ end
538
+ else
539
+ kw_list[#kw_list + 1] = meta_str(meta, "keywords_id")
540
+ end
541
+ if #kw_list > 0 then
542
+ blocks[#blocks + 1] = pandoc.Para({
543
+ pandoc.Strong(pandoc.Str("Kata Kunci: ")),
544
+ pandoc.Emph(pandoc.Str(table.concat(kw_list, ", ")))
545
+ })
546
+ end
547
+ end
548
+ blocks[#blocks + 1] = pagebreak()
549
+ end
550
+
551
+ -- Optional Abstract English (abstract_en)
552
+ local abs_en = meta_str(meta, "abstract_en")
553
+ if abs_en ~= "" then
554
+ blocks[#blocks + 1] = pandoc.Header(1, { pandoc.Str("ABSTRACT") }, pandoc.Attr("abstract", { "unnumbered" }, {}))
555
+ if title ~= "" then
556
+ blocks[#blocks + 1] = para({ pandoc.Strong(pandoc.Emph(pandoc.Str(string.upper(title)))) }, "CoverLine")
557
+ end
558
+ blocks[#blocks + 1] = pandoc.Para({ pandoc.Emph(pandoc.Str(abs_en)) })
559
+ if meta["keywords_en"] ~= nil then
560
+ local kw_en = {}
561
+ if type(meta["keywords_en"]) == "table" and meta["keywords_en"][1] ~= nil then
562
+ for _, k in ipairs(meta["keywords_en"]) do
563
+ kw_en[#kw_en + 1] = pandoc.utils.stringify(k)
564
+ end
565
+ else
566
+ kw_en[#kw_en + 1] = meta_str(meta, "keywords_en")
567
+ end
568
+ if #kw_en > 0 then
569
+ blocks[#blocks + 1] = pandoc.Para({
570
+ pandoc.Strong(pandoc.Str("Keywords: ")),
571
+ pandoc.Emph(pandoc.Str(table.concat(kw_en, ", ")))
572
+ })
573
+ end
574
+ end
575
+ blocks[#blocks + 1] = pagebreak()
576
+ end
577
+
440
578
  meta.title = nil
441
579
  meta.subtitle = nil
442
580
  meta.date = nil
@@ -27,7 +27,7 @@ export PYTHONUTF8=1
27
27
  show_banner() {
28
28
  echo -e "${CYAN}${BOLD}"
29
29
  echo " ========================================================"
30
- echo " LAPORAN GENERATOR CLI v2.8.0 "
30
+ echo " LAPORAN GENERATOR CLI v2.9.0 "
31
31
  echo " Otomatisasi Dokumen Akademik (Typst + DOCX Engine) "
32
32
  echo " ========================================================"
33
33
  echo -e "${NC}"
@@ -345,7 +345,7 @@ cmd_build() {
345
345
  ./build.sh
346
346
  echo ""
347
347
  echo -e "${BLUE}${BOLD}2. Membangun Dokumen Microsoft Word (DOCX Multi-Pass)...${NC}"
348
- make docx
348
+ ./build-docx.sh
349
349
  echo ""
350
350
  echo -e "${GREEN}${BOLD}Dokumen PDF dan DOCX berhasil dibuat!${NC}"
351
351
  echo -e " - ${CYAN}Laporan.pdf${NC}"
@@ -40,7 +40,7 @@ function Get-PythonCommand {
40
40
 
41
41
  function Show-Banner {
42
42
  Write-Host " ========================================================" -ForegroundColor Cyan
43
- Write-Host " LAPORAN GENERATOR CLI v2.8.0 " -ForegroundColor Cyan
43
+ Write-Host " LAPORAN GENERATOR CLI v2.9.0 " -ForegroundColor Cyan
44
44
  Write-Host " Otomatisasi Dokumen Akademik (Typst + DOCX Engine) " -ForegroundColor Cyan
45
45
  Write-Host " ========================================================" -ForegroundColor Cyan
46
46
  Write-Host ""
@@ -60,7 +60,9 @@ function Show-Help {
60
60
  Write-Host " doctor Audit kesehatan proyek (broken images, sitasi hilang, dll.)" -ForegroundColor Green
61
61
  Write-Host " bundle Kemas seluruh laporan (PDF, DOCX, MD) menjadi arsip zip" -ForegroundColor Green
62
62
  Write-Host " setup Pasang dependensi sistem otomatis Windows (Typst, Pandoc, Magick)" -ForegroundColor Green
63
- Write-Host " sync-skills Sinkronkan skill AI agent ke Antigravity, Claude, Grok" -ForegroundColor Green
63
+ Write-Host " mcp Jalankan Model Context Protocol (MCP) Server untuk AI Agent" -ForegroundColor Green
64
+ Write-Host " sync-skills Sinkronkan skill AI agent & MCP ke Antigravity, Claude, OpenCode" -ForegroundColor Green
65
+ Write-Host " uninstall Hapus integrasi skill dan perintah AI agent dari seluruh sistem" -ForegroundColor Green
64
66
  Write-Host " check Audit dependensi sistem dan struktur proyek" -ForegroundColor Green
65
67
  Write-Host " test Jalankan suite pengujian otomatis" -ForegroundColor Green
66
68
  Write-Host " view Buka dokumen Laporan.pdf di PDF viewer" -ForegroundColor Green
@@ -336,29 +338,29 @@ preset: "$escPreset"
336
338
  Write-Host "Jalankan '.\laporan.ps1 build' untuk mulai menghasilkan laporan." -ForegroundColor Cyan
337
339
  }
338
340
 
339
- function Cmd-Test {
340
- Show-Banner
341
- Write-Host "Menjalankan Test Suite..." -ForegroundColor Blue
342
- $gitBash = "C:\Program Files\Git\bin\bash.exe"
343
- $bashCmd = if (Test-Path $gitBash) { $gitBash } elseif ((Get-Command "bash" -ErrorAction SilentlyContinue) -and (Get-Command "bash").Source -notmatch "system32\\bash\.exe") { "bash" } else { "" }
344
- if ($bashCmd -and (Test-Path "test.sh")) {
345
- & $bashCmd test.sh
346
- } else {
347
- $pyCmd = Get-PythonCommand
348
- if ($pyCmd) {
349
- Write-Host "Menjalankan validasi skema preset..." -ForegroundColor Blue
350
- if (Test-Path "scripts/validate-preset.py") {
351
- & $pyCmd scripts/validate-preset.py --all
352
- }
353
- if (Test-Path "scripts/test_scripts.py") {
354
- Write-Host ""
355
- Write-Host "Menjalankan unit test skrip Python..." -ForegroundColor Blue
356
- & $pyCmd scripts/test_scripts.py
357
- }
358
- } else {
359
- Write-Host "Test suite membutuhkan bash atau python3." -ForegroundColor Yellow
360
- }
361
- }
341
+ function Cmd-Test {
342
+ Show-Banner
343
+ Write-Host "Menjalankan Test Suite..." -ForegroundColor Blue
344
+ $gitBash = "C:\Program Files\Git\bin\bash.exe"
345
+ $bashCmd = if (Test-Path $gitBash) { $gitBash } elseif ((Get-Command "bash" -ErrorAction SilentlyContinue) -and (Get-Command "bash").Source -notmatch "system32\\bash\.exe") { "bash" } else { "" }
346
+ if ($bashCmd -and (Test-Path "test.sh")) {
347
+ & $bashCmd test.sh
348
+ } else {
349
+ $pyCmd = Get-PythonCommand
350
+ if ($pyCmd) {
351
+ Write-Host "Menjalankan validasi skema preset..." -ForegroundColor Blue
352
+ if (Test-Path "scripts/validate-preset.py") {
353
+ & $pyCmd scripts/validate-preset.py --all
354
+ }
355
+ if (Test-Path "scripts/test_scripts.py") {
356
+ Write-Host ""
357
+ Write-Host "Menjalankan unit test skrip Python..." -ForegroundColor Blue
358
+ & $pyCmd scripts/test_scripts.py
359
+ }
360
+ } else {
361
+ Write-Host "Test suite membutuhkan bash atau python3." -ForegroundColor Yellow
362
+ }
363
+ }
362
364
  }
363
365
 
364
366
  function Cmd-Preset {
@@ -587,6 +589,34 @@ switch ($Command.ToLower()) {
587
589
  Write-Host "Node.js diperlukan untuk sinkronisasi multi-host AI agent." -ForegroundColor Yellow
588
590
  }
589
591
  }
592
+ "uninstall" {
593
+ if (Get-Command "node" -ErrorAction SilentlyContinue) {
594
+ $genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
595
+ if (Test-Path $genPath) {
596
+ node $genPath uninstall @extraArgs
597
+ } elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
598
+ npx -y laporan-generator uninstall @extraArgs
599
+ } else {
600
+ Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
601
+ }
602
+ } else {
603
+ Write-Host "Node.js diperlukan untuk membersihkan integrasi AI agent." -ForegroundColor Yellow
604
+ }
605
+ }
606
+ "clean-skills" {
607
+ if (Get-Command "node" -ErrorAction SilentlyContinue) {
608
+ $genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
609
+ if (Test-Path $genPath) {
610
+ node $genPath uninstall @extraArgs
611
+ } elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
612
+ npx -y laporan-generator uninstall @extraArgs
613
+ } else {
614
+ Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
615
+ }
616
+ } else {
617
+ Write-Host "Node.js diperlukan untuk membersihkan integrasi AI agent." -ForegroundColor Yellow
618
+ }
619
+ }
590
620
  "check" { Cmd-Check }
591
621
  "clean" { Cmd-Clean }
592
622
  "view" { Cmd-View }
@@ -0,0 +1,27 @@
1
+ # Preset: IPB University (Institut Pertanian Bogor)
2
+ preset_id: "ipb-ppki"
3
+ name: "IPB University (PPKI)"
4
+ description: "Format Tugas Akhir sesuai Pedoman Penulisan Karya Ilmiah (PPKI) IPB University (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm)."
5
+
6
+ # 1. Geometri & Margin Halaman (Standar Resmi PPKI IPB: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
7
+ margin_top: 3cm
8
+ margin_bottom: 3cm
9
+ margin_left: 4cm
10
+ margin_right: 3cm
11
+
12
+ # 2. Tipografi & Spasi
13
+ font_family: "Libertinus Serif"
14
+ font_size: 12pt
15
+ line_spacing: 0.75em
16
+ first_line_indent: 1.0cm
17
+
18
+ # 3. Penomoran Heading
19
+ heading_chapter_prefix: "BAB "
20
+ heading_chapter_num_format: "roman"
21
+ heading_sub_dot: false
22
+ heading_subsub_dot: false
23
+
24
+ # 4. Pengaturan Halaman Sampul
25
+ cover_show_lecturer: false
26
+ cover_hide_lecturer: true
27
+ cover_logo_width: 4cm
@@ -1,13 +1,13 @@
1
1
  # Preset: Institut Teknologi Sepuluh Nopember (ITS)
2
2
  preset_id: "its-skripsi"
3
3
  name: "Institut Teknologi Sepuluh Nopember (ITS)"
4
- description: "Format Tugas Akhir & Skripsi Institut Teknologi Sepuluh Nopember (Margin 4-4-3-3, Standard Academic)."
4
+ description: "Format Tugas Akhir & Skripsi sesuai Pedoman Resmi ITS (Margin Atas 3cm, Kiri 3cm, Bawah 2.5cm, Kanan 2cm)."
5
5
 
6
- # Margin halaman (Standar ITS: Kiri 4cm, Atas 4cm, Bawah 3cm, Kanan 3cm)
7
- margin_top: 4cm
8
- margin_bottom: 3cm
9
- margin_left: 4cm
10
- margin_right: 3cm
6
+ # Margin halaman (Standar Resmi Pedoman TA ITS: Atas 3.0cm, Bawah 2.5cm, Kiri 3.0cm, Kanan 2.0cm)
7
+ margin_top: 3cm
8
+ margin_bottom: 2.5cm
9
+ margin_left: 3cm
10
+ margin_right: 2cm
11
11
 
12
12
  # Tipografi
13
13
  font_family: "Libertinus Serif"
@@ -0,0 +1,26 @@
1
+ # Preset: Telkom University (Tel-U)
2
+ preset_id: "telkom-ta"
3
+ name: "Telkom University (Tel-U)"
4
+ description: "Format Buku Tugas Akhir sesuai Pedoman Akademik Telkom University (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm)."
5
+
6
+ # 1. Geometri & Margin Halaman (Standar TA Telkom University: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
7
+ margin_top: 3cm
8
+ margin_bottom: 3cm
9
+ margin_left: 4cm
10
+ margin_right: 3cm
11
+
12
+ # 2. Tipografi & Spasi
13
+ font_family: "Libertinus Serif"
14
+ font_size: 12pt
15
+ line_spacing: 0.75em
16
+ first_line_indent: 1.25cm
17
+
18
+ # 3. Penomoran Heading
19
+ heading_chapter_prefix: "BAB "
20
+ heading_chapter_num_format: "roman"
21
+ heading_sub_dot: true
22
+ heading_subsub_dot: false
23
+
24
+ # 4. Pengaturan Halaman Sampul
25
+ cover_show_lecturer: true
26
+ cover_logo_width: 4cm
@@ -0,0 +1,26 @@
1
+ # Preset: Universitas Brawijaya (UB)
2
+ preset_id: "ub-skripsi"
3
+ name: "Universitas Brawijaya (UB)"
4
+ description: "Format Skripsi & Tugas Akhir sesuai Pedoman Penulisan Karya Ilmiah Universitas Brawijaya (Margin 4-4-3-3)."
5
+
6
+ # 1. Geometri & Margin Halaman (Standar UB: Atas 4cm, Kiri 4cm, Bawah 3cm, Kanan 3cm)
7
+ margin_top: 4cm
8
+ margin_bottom: 3cm
9
+ margin_left: 4cm
10
+ margin_right: 3cm
11
+
12
+ # 2. Tipografi & Spasi
13
+ font_family: "Libertinus Serif"
14
+ font_size: 12pt
15
+ line_spacing: 0.75em
16
+ first_line_indent: 1.25cm
17
+
18
+ # 3. Penomoran Heading
19
+ heading_chapter_prefix: "BAB "
20
+ heading_chapter_num_format: "roman"
21
+ heading_sub_dot: true
22
+ heading_subsub_dot: false
23
+
24
+ # 4. Pengaturan Halaman Sampul
25
+ cover_show_lecturer: true
26
+ cover_logo_width: 4cm
@@ -1,13 +1,13 @@
1
1
  # Preset: Universitas Indonesia (UI)
2
2
  preset_id: "ui-skripsi"
3
3
  name: "Universitas Indonesia (UI)"
4
- description: "Format Tugas Akhir & Skripsi sesuai Pedoman Penulisan Karya Ilmiah Universitas Indonesia."
4
+ description: "Format Tugas Akhir & Skripsi sesuai Keputusan Rektor UI (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm, TNR 12pt)."
5
5
 
6
- # Margin halaman (Standar UI: Top 2.5cm / 2cm, Left 2.5cm, Bottom 2.5cm / 3cm, Right 2.5cm)
7
- margin_top: 2.5cm
8
- margin_bottom: 2.5cm
9
- margin_left: 2.5cm
10
- margin_right: 2.5cm
6
+ # Margin halaman (Standar Resmi Pedoman Teknis TA UI: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
7
+ margin_top: 3cm
8
+ margin_bottom: 3cm
9
+ margin_left: 4cm
10
+ margin_right: 3cm
11
11
 
12
12
  # Tipografi
13
13
  font_family: "Libertinus Serif"