traceback-id 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,19 @@
1
+ """traceback_id — penjelasan error/traceback pemrograman dalam Bahasa
2
+ Indonesia, dimulai dari Python (lihat PRD.md).
3
+
4
+ Mode in-process (khusus Python, DX terbaik untuk kasus ini)::
5
+
6
+ import traceback_id
7
+ traceback_id.activate()
8
+
9
+ Mode CLI (universal, dipakai bahasa apa pun yang punya adapter)::
10
+
11
+ traceback_id run script.py
12
+ traceback_id run script.js
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from traceback_id.hook import activate, deactivate, is_active
17
+
18
+ __all__ = ["activate", "deactivate", "is_active", "__version__"]
19
+ __version__ = "0.1.0"
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ from traceback_id.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Paket adapter bahasa. Lihat adapters/base.py untuk kontraknya."""
@@ -0,0 +1,36 @@
1
+ """Kontrak adapter bahasa (ARCHITECTURE.md §4).
2
+
3
+ Setiap adapter — built-in maupun plugin komunitas yang didaftarkan lewat
4
+ entry_points `traceback_id.adapters` — wajib mengimplementasikan interface
5
+ ini. `core/` hanya bergantung pada `LanguageAdapter` + `StructuredError`,
6
+ tidak pernah mengimpor adapter bahasa tertentu secara langsung.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from pathlib import Path
12
+
13
+ from traceback_id.core.models import StructuredError
14
+
15
+
16
+ class LanguageAdapter(ABC):
17
+ """Interface yang wajib dipenuhi setiap adapter bahasa."""
18
+
19
+ name: str
20
+ file_extensions: list[str]
21
+
22
+ @abstractmethod
23
+ def capture(self, command: list[str]) -> str:
24
+ """Jalankan command, kembalikan raw stderr/output sebagai teks."""
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def parse(self, raw_output: str) -> StructuredError:
29
+ """Ubah teks error mentah jadi StructuredError."""
30
+ raise NotImplementedError
31
+
32
+ @property
33
+ @abstractmethod
34
+ def rules_path(self) -> Path:
35
+ """Lokasi file rule khusus bahasa ini."""
36
+ raise NotImplementedError
@@ -0,0 +1 @@
1
+ """Adapter Python. Lihat adapter.py untuk implementasi, rules.yaml untuk rule."""
@@ -0,0 +1,140 @@
1
+ """Adapter Python — implementasi pertama & satu-satunya yang matang di v0.1
2
+ (PRD.md §4). Semua kekhususan Python (capture via hook/subprocess, parsing
3
+ traceback) hidup di sini, terisolasi dari adapter bahasa lain.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import yaml
13
+
14
+ from traceback_id.adapters.base import LanguageAdapter
15
+ from traceback_id.core.models import ErrorCategory, StructuredError
16
+
17
+ RULES_PATH = Path(__file__).resolve().parent / "rules.yaml"
18
+
19
+ # ` File "script.py", line 3, in <module>`
20
+ _FRAME_RE = re.compile(r'^\s*File "(?P<file>.+?)", line (?P<line>\d+)(?:, in (?P<func>.*))?$')
21
+
22
+ # Baris terakhir traceback Python, mis. `NameError: name 'x' is not defined`.
23
+ # StopIteration/SystemExit/KeyboardInterrupt/GeneratorExit tidak selalu
24
+ # punya prefix + pesan (mis. `StopIteration` bisa muncul sendirian tanpa
25
+ # titik dua sama sekali), jadi didaftarkan sebagai alternatif berdiri
26
+ # sendiri, terpisah dari pola "<prefix>Error/Exception/Warning".
27
+ _ERROR_LINE_RE = re.compile(
28
+ r"^(?P<error_type>[\w.]*(?:Error|Exception|Warning)"
29
+ r"|StopIteration|SystemExit|KeyboardInterrupt|GeneratorExit)"
30
+ r":?\s*(?P<message>.*)$"
31
+ )
32
+
33
+
34
+ def _looks_like_traceback_line(text: str) -> bool:
35
+ """True kalau `text` adalah baris "File ...", baris "ErrorType: ...",
36
+ atau header "Traceback (most recent call last):" — dipakai untuk
37
+ membedakan baris source-code asli dari baris struktur traceback."""
38
+ return (
39
+ bool(_FRAME_RE.match(text))
40
+ or bool(_ERROR_LINE_RE.match(text))
41
+ or text.startswith("Traceback (most recent call last)")
42
+ )
43
+
44
+
45
+ class PythonAdapter(LanguageAdapter):
46
+ """Adapter untuk error/traceback Python.
47
+
48
+ Selain kontrak dasar `capture()`/`parse()`, adapter ini juga dipakai
49
+ langsung oleh `traceback_id/hook.py` untuk mode in-process
50
+ (`sys.excepthook`) — satu-satunya adapter yang punya mode ini, karena
51
+ Python adalah bahasa implementasi library ini sendiri (ARCHITECTURE.md §4).
52
+ """
53
+
54
+ name = "python"
55
+ file_extensions = [".py", ".pyw"]
56
+
57
+ def __init__(self) -> None:
58
+ self._rules_cache: dict | None = None
59
+ self.last_returncode: int | None = None
60
+
61
+ @property
62
+ def rules_path(self) -> Path:
63
+ return RULES_PATH
64
+
65
+ def capture(self, command: list) -> str:
66
+ """Jalankan `command` sebagai subprocess Python, kembalikan stderr.
67
+
68
+ stdout program tetap diteruskan langsung ke terminal (real-time),
69
+ sedangkan stderr ditangkap untuk di-parse — kalau program tidak
70
+ error, stderr akan kosong dan tidak ada apa pun yang "hilang".
71
+ """
72
+ result = subprocess.run(
73
+ [sys.executable, *command],
74
+ stdout=None,
75
+ stderr=subprocess.PIPE,
76
+ text=True,
77
+ )
78
+ self.last_returncode = result.returncode
79
+ return result.stderr
80
+
81
+ def parse(self, raw_output: str) -> StructuredError:
82
+ lines = raw_output.rstrip("\n").splitlines()
83
+
84
+ frames = []
85
+ for i, line in enumerate(lines):
86
+ match = _FRAME_RE.match(line)
87
+ if not match:
88
+ continue
89
+ code_context = None
90
+ if i + 1 < len(lines):
91
+ candidate = lines[i + 1].strip()
92
+ if candidate and not _looks_like_traceback_line(candidate):
93
+ code_context = candidate
94
+ frames.append(
95
+ {
96
+ "file": match.group("file"),
97
+ "line": int(match.group("line")),
98
+ "code_context": code_context,
99
+ }
100
+ )
101
+
102
+ error_type = "UnknownError"
103
+ message = raw_output.strip() or "(tidak ada output error)"
104
+ for line in reversed(lines):
105
+ match = _ERROR_LINE_RE.match(line)
106
+ if match:
107
+ error_type = match.group("error_type")
108
+ message = match.group("message").strip()
109
+ break
110
+
111
+ last_frame = frames[-1] if frames else {}
112
+
113
+ return StructuredError(
114
+ language=self.name,
115
+ error_type=error_type,
116
+ category=self._category_for(error_type),
117
+ message=message,
118
+ file=last_frame.get("file"),
119
+ line=last_frame.get("line"),
120
+ code_context=last_frame.get("code_context"),
121
+ raw_output=raw_output,
122
+ )
123
+
124
+ def _rules(self) -> dict:
125
+ if self._rules_cache is None:
126
+ if self.rules_path.exists():
127
+ with open(self.rules_path, encoding="utf-8") as fh:
128
+ self._rules_cache = yaml.safe_load(fh) or {}
129
+ else: # pragma: no cover - seharusnya tidak terjadi di instalasi normal
130
+ self._rules_cache = {}
131
+ return self._rules_cache
132
+
133
+ def _category_for(self, error_type: str) -> ErrorCategory:
134
+ rule = self._rules().get(error_type)
135
+ if rule and "category" in rule:
136
+ try:
137
+ return ErrorCategory(rule["category"])
138
+ except ValueError:
139
+ pass
140
+ return ErrorCategory.UNKNOWN
@@ -0,0 +1,174 @@
1
+ # Rule khusus Python (PRD.md §4 — 15 error type MVP).
2
+ #
3
+ # Key setiap entri = error_type persis seperti yang muncul di traceback
4
+ # (mis. "NameError"). Field:
5
+ # category -> salah satu nilai ErrorCategory di core/models.py
6
+ # pattern -> regex opsional dengan named group, dicocokkan ke
7
+ # StructuredError.message untuk mengisi variabel di
8
+ # template `penjelasan`/`saran` (format {nama_group})
9
+ # penjelasan -> kenapa error ini terjadi
10
+ # saran -> cara konkret memperbaikinya
11
+ #
12
+ # Kalau error_type tidak ada di sini, explainer fallback ke
13
+ # rules/universal_categories.yaml berdasarkan `category`.
14
+
15
+ NameError:
16
+ category: UNDEFINED_VARIABLE
17
+ pattern: "name '(?P<var>.+?)' is not defined"
18
+ penjelasan: >
19
+ Kode kamu mencoba memakai nama `{var}`, tapi Python belum pernah
20
+ melihat nama itu didefinisikan sebelumnya di titik ini.
21
+ saran: >
22
+ Periksa apakah `{var}` sudah kamu isi nilainya sebelum baris ini,
23
+ atau cek kemungkinan salah ketik nama variabel/fungsi.
24
+
25
+ UnboundLocalError:
26
+ category: UNDEFINED_VARIABLE
27
+ pattern: "local variable '(?P<var>.+?)'"
28
+ penjelasan: >
29
+ Di dalam fungsi ini, `{var}` dianggap sebagai variabel lokal (karena
30
+ ada baris yang meng-assign nilai ke situ di dalam fungsi), tapi kamu
31
+ memakainya sebelum baris assignment itu benar-benar dijalankan.
32
+ saran: >
33
+ Pastikan `{var}` sudah diberi nilai sebelum dipakai — lewat parameter
34
+ fungsi, inisialisasi di awal fungsi, atau `global {var}` kalau memang
35
+ ingin memakai variabel dari luar fungsi.
36
+
37
+ TypeError:
38
+ category: TYPE_MISMATCH
39
+ penjelasan: >
40
+ Kamu mencoba melakukan operasi pada nilai dengan tipe data yang tidak
41
+ cocok satu sama lain, atau memanggil sesuatu dengan cara yang tidak
42
+ sesuai tipe datanya.
43
+ saran: >
44
+ Cek tipe data tiap nilai yang terlibat (pakai `type(nilai)` kalau
45
+ perlu), lalu sesuaikan — misalnya ubah angka jadi teks dengan `str()`,
46
+ atau sebaliknya dengan `int()`/`float()`.
47
+
48
+ IndexError:
49
+ category: INDEX_OUT_OF_BOUNDS
50
+ penjelasan: >
51
+ Kamu mencoba mengakses posisi/index pada list atau tuple yang tidak
52
+ ada — index yang kamu pakai berada di luar jangkauan panjang data
53
+ tersebut.
54
+ saran: >
55
+ Cek dulu panjang list-nya dengan `len(...)` sebelum mengakses index
56
+ tertentu, dan ingat index di Python dimulai dari 0.
57
+
58
+ KeyError:
59
+ category: MISSING_KEY
60
+ pattern: "'(?P<key>.*)'"
61
+ penjelasan: >
62
+ Kamu mencoba mengambil nilai dari dictionary memakai key `{key}`,
63
+ tapi key itu tidak ada di dalam dictionary tersebut.
64
+ saran: >
65
+ Cek dulu apakah key `{key}` benar-benar ada, misalnya dengan
66
+ `if {key} in dict_kamu:`, atau pakai `dict_kamu.get({key})` yang
67
+ tidak error kalau key-nya tidak ketemu.
68
+
69
+ AttributeError:
70
+ category: ATTRIBUTE_ERROR
71
+ pattern: "'(?P<tipe>.+?)' object has no attribute '(?P<atribut>.+?)'"
72
+ penjelasan: >
73
+ Kamu mencoba memakai atribut atau method `{atribut}` pada objek
74
+ bertipe `{tipe}`, tapi tipe data itu tidak punya atribut/method
75
+ dengan nama tersebut.
76
+ saran: >
77
+ Cek lagi tipe data objeknya (`type(objek)`) dan pastikan nama
78
+ method/atributnya benar. Ini juga sering terjadi kalau nilainya
79
+ ternyata `None` — misalnya karena sebuah fungsi lupa `return`.
80
+
81
+ ZeroDivisionError:
82
+ category: DIVISION_BY_ZERO
83
+ penjelasan: >
84
+ Kode kamu mencoba membagi sebuah angka dengan 0, yang secara
85
+ matematika tidak terdefinisikan sehingga Python melempar error.
86
+ saran: >
87
+ Tambahkan pengecekan sebelum melakukan pembagian, misalnya
88
+ `if pembagi != 0:`, atau pastikan nilai pembagi memang tidak pernah
89
+ nol di kasus kamu.
90
+
91
+ ImportError:
92
+ category: MODULE_NOT_FOUND
93
+ pattern: "cannot import name '(?P<nama>.+?)' from '(?P<modul>.+?)'"
94
+ penjelasan: >
95
+ Kamu mencoba meng-import `{nama}` dari modul `{modul}`, tapi Python
96
+ tidak menemukan nama tersebut di dalam modul itu.
97
+ saran: >
98
+ Cek lagi ejaan nama yang di-import, dan pastikan modul `{modul}`
99
+ memang punya `{nama}` — bisa jadi versi library-nya berbeda dari
100
+ yang kamu kira.
101
+
102
+ ModuleNotFoundError:
103
+ category: MODULE_NOT_FOUND
104
+ pattern: "No module named '(?P<modul>.+?)'"
105
+ penjelasan: >
106
+ Python tidak menemukan modul/package bernama `{modul}` — kemungkinan
107
+ besar package itu belum ter-install, atau nama modulnya salah ketik.
108
+ saran: >
109
+ Coba install dengan `pip install {modul}` (sesuaikan nama package-nya
110
+ kalau beda dari nama modulnya), dan pastikan virtual environment yang
111
+ aktif memang sudah menginstall package tersebut.
112
+
113
+ ValueError:
114
+ category: VALUE_ERROR
115
+ penjelasan: >
116
+ Tipe data yang kamu pakai sudah benar, tapi nilainya tidak masuk
117
+ akal atau tidak valid untuk operasi yang sedang dilakukan.
118
+ saran: >
119
+ Periksa nilai yang dikirim ke fungsi/operasi ini — misalnya kalau
120
+ ini hasil `int(teks)`, pastikan `teks` benar-benar berisi angka.
121
+
122
+ FileNotFoundError:
123
+ category: FILE_NOT_FOUND
124
+ pattern: "No such file or directory: '(?P<berkas>.+?)'"
125
+ penjelasan: >
126
+ Kode kamu mencoba membuka file `{berkas}`, tapi Python tidak
127
+ menemukan file tersebut di lokasi yang dicari.
128
+ saran: >
129
+ Cek lagi path/nama file `{berkas}` — termasuk apakah kamu menjalankan
130
+ program dari folder yang benar, karena path relatif dihitung dari
131
+ folder tempat kamu menjalankan perintah, bukan dari lokasi file .py.
132
+
133
+ IndentationError:
134
+ category: SYNTAX_ERROR
135
+ penjelasan: >
136
+ Python sangat bergantung pada indentasi (spasi/tab di awal baris)
137
+ untuk mengetahui blok kode mana yang satu level. ada baris yang
138
+ indentasinya tidak konsisten dengan baris di sekitarnya.
139
+ saran: >
140
+ Pastikan kode di blok yang sama (misalnya di dalam satu `if`, `for`,
141
+ atau fungsi) punya jumlah indentasi yang sama persis. Hindari
142
+ mencampur tab dan spasi.
143
+
144
+ SyntaxError:
145
+ category: SYNTAX_ERROR
146
+ penjelasan: >
147
+ Ada bagian kode yang tidak mengikuti aturan penulisan (grammar)
148
+ Python, sehingga Python tidak bisa mem-parsing/membaca kodenya sama
149
+ sekali sebelum sempat dijalankan.
150
+ saran: >
151
+ Lihat tanda panah (^) di traceback di atas — itu menunjukkan kira-kira
152
+ di mana Python berhenti memahami kodenya. Cek tanda kurung, titik dua,
153
+ atau kutip yang mungkin belum ditutup.
154
+
155
+ RecursionError:
156
+ category: RECURSION_ERROR
157
+ penjelasan: >
158
+ Fungsi kamu memanggil dirinya sendiri (rekursi) terlalu banyak kali
159
+ tanpa pernah berhenti, sampai melebihi batas kedalaman rekursi yang
160
+ diizinkan Python.
161
+ saran: >
162
+ Pastikan fungsi rekursifmu punya base case (kondisi berhenti) yang
163
+ pasti tercapai, dan cek apakah tiap pemanggilan rekursif benar-benar
164
+ membuat progres menuju base case itu.
165
+
166
+ StopIteration:
167
+ category: VALUE_ERROR
168
+ penjelasan: >
169
+ Kamu memanggil `next()` pada sebuah iterator yang sudah habis — tidak
170
+ ada lagi elemen yang bisa diambil.
171
+ saran: >
172
+ Kalau ini terjadi di dalam loop manual dengan `next()`, tangkap
173
+ dengan `try/except StopIteration`, atau pertimbangkan memakai `for`
174
+ biasa yang otomatis berhenti saat iterator habis.
traceback_id/cli.py ADDED
@@ -0,0 +1,78 @@
1
+ """Mode CLI, universal lintas bahasa (ARCHITECTURE.md §2, §6; FR8).
2
+
3
+ `traceback_id run script.py` mendeteksi bahasa dari ekstensi file, mengambil
4
+ adapter dari `core/registry.py`, lalu `capture()` menjalankan file sebagai
5
+ subprocess dan `parse()` menghasilkan `StructuredError` yang diteruskan ke
6
+ `core/explainer.py` dan `core/formatter.py` seperti biasa.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+
13
+ from traceback_id import __version__
14
+ from traceback_id.core.explainer import Explainer
15
+ from traceback_id.core.formatter import print_output
16
+ from traceback_id.core.registry import AdapterNotFoundError, get_registry
17
+
18
+
19
+ def build_parser() -> argparse.ArgumentParser:
20
+ parser = argparse.ArgumentParser(
21
+ prog="traceback_id",
22
+ description="Jalankan sebuah file dan dapatkan penjelasan error dalam Bahasa Indonesia.",
23
+ )
24
+ parser.add_argument("--version", action="version", version=f"traceback_id {__version__}")
25
+
26
+ subparsers = parser.add_subparsers(dest="command", required=True)
27
+
28
+ run_parser = subparsers.add_parser("run", help="Jalankan sebuah file dan tangkap errornya.")
29
+ run_parser.add_argument("file", help="Path ke file yang akan dijalankan, mis. script.py")
30
+ run_parser.add_argument(
31
+ "--lang",
32
+ default=None,
33
+ help="Paksa bahasa tertentu (default: deteksi otomatis dari ekstensi file).",
34
+ )
35
+ run_parser.add_argument(
36
+ "args",
37
+ nargs=argparse.REMAINDER,
38
+ help="Argumen tambahan yang diteruskan ke file/program.",
39
+ )
40
+
41
+ return parser
42
+
43
+
44
+ def main(argv: list | None = None) -> int:
45
+ parser = build_parser()
46
+ args = parser.parse_args(argv)
47
+
48
+ if args.command == "run":
49
+ return _run(args)
50
+
51
+ parser.print_help() # pragma: no cover - argparse `required=True` sudah menutup jalur ini
52
+ return 1
53
+
54
+
55
+ def _run(args: argparse.Namespace) -> int:
56
+ registry = get_registry()
57
+ try:
58
+ adapter = registry.get_by_name(args.lang) if args.lang else registry.get_by_extension(args.file)
59
+ except AdapterNotFoundError as exc:
60
+ print(f"traceback_id: {exc}", file=sys.stderr)
61
+ return 2
62
+
63
+ raw_output = adapter.capture([args.file, *args.args])
64
+ returncode = getattr(adapter, "last_returncode", None)
65
+ if returncode is None:
66
+ returncode = 1 if raw_output.strip() else 0
67
+
68
+ if not raw_output.strip():
69
+ return returncode
70
+
71
+ structured = adapter.parse(raw_output)
72
+ explanation = Explainer().explain(structured, adapter)
73
+ print_output(structured, explanation)
74
+ return returncode
75
+
76
+
77
+ if __name__ == "__main__":
78
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Mesin inti traceback_id — language-agnostic (ARCHITECTURE.md §3)."""
@@ -0,0 +1,108 @@
1
+ """Mencocokkan StructuredError ke rule penjelasan (ARCHITECTURE.md §3, §5).
2
+
3
+ Prioritas pencarian rule:
4
+ 1. Rule spesifik-bahasa (`adapters/<lang>/rules.yaml`), key = error_type
5
+ 2. Fallback rule universal (`rules/universal_categories.yaml`), key = category
6
+ 3. Fallback generik kalau keduanya tidak ada
7
+
8
+ Modul ini tidak tahu dan tidak perlu tahu bahasa apa yang sedang diproses —
9
+ hanya bekerja lewat field di `StructuredError` dan `LanguageAdapter.rules_path`.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from dataclasses import dataclass
15
+ from functools import lru_cache
16
+ from pathlib import Path
17
+ from typing import TYPE_CHECKING
18
+
19
+ import yaml
20
+
21
+ from traceback_id.core.models import ErrorCategory, StructuredError
22
+
23
+ if TYPE_CHECKING:
24
+ from traceback_id.adapters.base import LanguageAdapter
25
+
26
+ UNIVERSAL_RULES_PATH = Path(__file__).resolve().parent.parent / "rules" / "universal_categories.yaml"
27
+
28
+
29
+ @dataclass
30
+ class Explanation:
31
+ """Hasil akhir penjelasan sebuah StructuredError, siap ditampilkan."""
32
+
33
+ penjelasan: str
34
+ saran: str | None = None
35
+ source: str = "fallback" # "language-specific" | "universal" | "fallback"
36
+
37
+
38
+ class _SafeDict(dict):
39
+ """dict yang tidak melempar KeyError saat template.format_map dipakai —
40
+ supaya rule.yaml yang polanya sedikit meleset tidak sampai membuat
41
+ traceback_id sendiri crash."""
42
+
43
+ def __missing__(self, key: str) -> str:
44
+ return f"<{key}>"
45
+
46
+
47
+ @lru_cache(maxsize=None)
48
+ def _load_yaml(path: Path) -> dict:
49
+ if not path.exists():
50
+ return {}
51
+ with open(path, encoding="utf-8") as fh:
52
+ return yaml.safe_load(fh) or {}
53
+
54
+
55
+ def _extract_variables(pattern: str | None, message: str) -> dict:
56
+ if not pattern:
57
+ return {}
58
+ match = re.search(pattern, message)
59
+ return match.groupdict() if match else {}
60
+
61
+
62
+ def _render(template: str | None, variables: dict) -> str | None:
63
+ if not template:
64
+ return None
65
+ try:
66
+ return template.format_map(_SafeDict(variables)).strip()
67
+ except Exception:
68
+ return template.strip()
69
+
70
+
71
+ class Explainer:
72
+ """Cocokkan StructuredError ke rule & render template penjelasannya."""
73
+
74
+ def __init__(self, universal_rules_path: Path | None = None) -> None:
75
+ self._universal_rules_path = universal_rules_path or UNIVERSAL_RULES_PATH
76
+
77
+ def explain(self, error: StructuredError, adapter: "LanguageAdapter") -> Explanation:
78
+ # 1. Rule spesifik-bahasa, key = error_type native (mis. "NameError")
79
+ language_rules = _load_yaml(adapter.rules_path)
80
+ rule = language_rules.get(error.error_type)
81
+
82
+ if rule:
83
+ variables = _extract_variables(rule.get("pattern"), error.message)
84
+ penjelasan = _render(rule.get("penjelasan"), variables)
85
+ saran = _render(rule.get("saran"), variables)
86
+ if penjelasan:
87
+ return Explanation(penjelasan=penjelasan, saran=saran, source="language-specific")
88
+
89
+ # 2. Fallback rule universal, key = category
90
+ universal_rules = _load_yaml(self._universal_rules_path)
91
+ category_key = error.category.value if isinstance(error.category, ErrorCategory) else str(error.category)
92
+ universal_rule = universal_rules.get(category_key)
93
+
94
+ if universal_rule:
95
+ penjelasan = _render(universal_rule.get("penjelasan"), {})
96
+ saran = _render(universal_rule.get("saran"), {})
97
+ if penjelasan:
98
+ return Explanation(penjelasan=penjelasan, saran=saran, source="universal")
99
+
100
+ # 3. Tidak ada rule yang cocok sama sekali
101
+ return Explanation(
102
+ penjelasan=(
103
+ "traceback_id belum punya penjelasan khusus untuk jenis error "
104
+ f"'{error.error_type}' ini."
105
+ ),
106
+ saran="Coba baca pesan error asli di atas, atau cari error_type ini di dokumentasi resmi bahasa terkait.",
107
+ source="fallback",
108
+ )
@@ -0,0 +1,54 @@
1
+ """Susun raw_output asli + penjelasan jadi satu output akhir (ARCHITECTURE.md §3).
2
+
3
+ Pewarnaan lewat `rich` bersifat opsional (lihat pyproject.toml) — kalau tidak
4
+ ter-install, fallback otomatis ke teks polos.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import sys
9
+
10
+ from traceback_id.core.explainer import Explanation
11
+ from traceback_id.core.models import StructuredError
12
+
13
+ try:
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+
17
+ _HAS_RICH = True
18
+ except ImportError:
19
+ _HAS_RICH = False
20
+
21
+
22
+ _DIVIDER = "-" * 60
23
+
24
+
25
+ def format_output(error: StructuredError, explanation: Explanation) -> str:
26
+ """Versi teks polos, deterministic — dipakai formatter tanpa `rich`
27
+ dan gampang di-assert langsung di test."""
28
+ parts = [
29
+ error.raw_output.rstrip("\n"),
30
+ _DIVIDER,
31
+ "penjelasan traceback_id (Bahasa Indonesia):",
32
+ explanation.penjelasan,
33
+ ]
34
+ if explanation.saran:
35
+ parts.extend(["", f"Saran: {explanation.saran}"])
36
+ return "\n".join(parts)
37
+
38
+
39
+ def print_output(error: StructuredError, explanation: Explanation, use_color: bool | None = None) -> None:
40
+ """Cetak output akhir ke stderr — dengan warna lewat `rich` kalau
41
+ tersedia, fallback ke teks polos kalau tidak."""
42
+ if use_color is None:
43
+ use_color = _HAS_RICH
44
+
45
+ if use_color and _HAS_RICH:
46
+ console = Console(stderr=True)
47
+ console.print(error.raw_output.rstrip("\n"), style="dim")
48
+ body = f"[bold]{explanation.penjelasan}[/bold]"
49
+ if explanation.saran:
50
+ body += f"\n\n[green]Saran:[/green] {explanation.saran}"
51
+ console.print(Panel(body, title="penjelasan traceback_id", border_style="cyan"))
52
+ return
53
+
54
+ print(format_output(error, explanation), file=sys.stderr)
@@ -0,0 +1,52 @@
1
+ """Struktur data seragam lintas bahasa (ARCHITECTURE.md §3).
2
+
3
+ `core/` (matcher, explainer, formatter) hanya boleh bergantung pada
4
+ struktur data di file ini — tidak boleh tahu apa pun tentang Python,
5
+ JavaScript, atau bahasa spesifik lainnya.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+
12
+
13
+ class ErrorCategory(str, Enum):
14
+ """Konsep error universal, dipetakan dari error_type native tiap bahasa.
15
+
16
+ Ini yang memungkinkan `rules/universal_categories.yaml` dipakai sebagai
17
+ fallback penjelasan lintas bahasa (lihat ARCHITECTURE.md §5).
18
+ """
19
+
20
+ UNDEFINED_VARIABLE = "UNDEFINED_VARIABLE"
21
+ TYPE_MISMATCH = "TYPE_MISMATCH"
22
+ INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"
23
+ MISSING_KEY = "MISSING_KEY"
24
+ DIVISION_BY_ZERO = "DIVISION_BY_ZERO"
25
+ MODULE_NOT_FOUND = "MODULE_NOT_FOUND"
26
+ SYNTAX_ERROR = "SYNTAX_ERROR"
27
+ ATTRIBUTE_ERROR = "ATTRIBUTE_ERROR"
28
+ FILE_NOT_FOUND = "FILE_NOT_FOUND"
29
+ RECURSION_ERROR = "RECURSION_ERROR"
30
+ VALUE_ERROR = "VALUE_ERROR"
31
+ UNKNOWN = "UNKNOWN"
32
+
33
+
34
+ @dataclass
35
+ class StructuredError:
36
+ """Hasil parsing error mentah, seragam untuk bahasa apa pun.
37
+
38
+ Field ini adalah kontrak antara adapter (yang mengisinya lewat
39
+ `parse()`) dan `core/explainer.py` + `core/formatter.py` (yang
40
+ membacanya tanpa perlu tahu bahasa asalnya).
41
+ """
42
+
43
+ language: str # "python", "javascript", "java", ...
44
+ error_type: str # nama native, mis. "NameError", "ReferenceError"
45
+ category: ErrorCategory # konsep semantik universal, lihat ErrorCategory
46
+ message: str # pesan asli dari runtime/compiler
47
+ file: str | None
48
+ line: int | None
49
+ column: int | None = None
50
+ code_context: str | None = None
51
+ variables: dict = field(default_factory=dict)
52
+ raw_output: str = "" # traceback/stack trace asli, untuk ditampilkan utuh
@@ -0,0 +1,81 @@
1
+ """Daftar adapter yang tersedia — built-in + plugin komunitas (ARCHITECTURE.md §3).
2
+
3
+ Built-in didaftarkan manual di `_register_builtin_adapters()`. Plugin
4
+ komunitas ditemukan otomatis lewat `importlib.metadata.entry_points` dengan
5
+ grup `traceback_id.adapters`, tanpa perlu mengubah file ini (ARCHITECTURE.md
6
+ §1, "Dibuka untuk plugin komunitas").
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from importlib import metadata
11
+ from pathlib import Path
12
+
13
+ from traceback_id.adapters.base import LanguageAdapter
14
+
15
+ ENTRY_POINT_GROUP = "traceback_id.adapters"
16
+
17
+
18
+ class AdapterNotFoundError(Exception):
19
+ """Tidak ada adapter yang cocok untuk bahasa/ekstensi file yang diminta."""
20
+
21
+
22
+ class Registry:
23
+ def __init__(self) -> None:
24
+ self._adapters: dict = {}
25
+ self._register_builtin_adapters()
26
+ self._discover_plugin_adapters()
27
+
28
+ def _register_builtin_adapters(self) -> None:
29
+ from traceback_id.adapters.python.adapter import PythonAdapter
30
+
31
+ self.register(PythonAdapter())
32
+
33
+ def _discover_plugin_adapters(self) -> None:
34
+ try:
35
+ entry_points = metadata.entry_points(group=ENTRY_POINT_GROUP)
36
+ except TypeError:
37
+ # Python 3.8/3.9: entry_points() tidak menerima kwarg `group`.
38
+ entry_points = metadata.entry_points().get(ENTRY_POINT_GROUP, [])
39
+
40
+ for ep in entry_points:
41
+ try:
42
+ adapter_cls = ep.load()
43
+ self.register(adapter_cls())
44
+ except Exception as exc: # pragma: no cover - defensif, jangan sampai plugin rusak matikan CLI
45
+ print(f"[traceback_id] gagal memuat plugin adapter '{ep.name}': {exc}")
46
+
47
+ def register(self, adapter: LanguageAdapter) -> None:
48
+ self._adapters[adapter.name] = adapter
49
+
50
+ def get_by_name(self, name: str) -> LanguageAdapter:
51
+ try:
52
+ return self._adapters[name]
53
+ except KeyError as exc:
54
+ raise AdapterNotFoundError(
55
+ f"Adapter untuk bahasa '{name}' tidak ditemukan. "
56
+ f"Bahasa yang tersedia: {', '.join(self.available_languages()) or '(tidak ada)'}"
57
+ ) from exc
58
+
59
+ def get_by_extension(self, path) -> LanguageAdapter:
60
+ ext = Path(path).suffix.lower()
61
+ for adapter in self._adapters.values():
62
+ if ext in adapter.file_extensions:
63
+ return adapter
64
+ raise AdapterNotFoundError(
65
+ f"Tidak ada adapter yang mendukung ekstensi file '{ext}'. "
66
+ f"Bahasa yang tersedia: {', '.join(self.available_languages()) or '(tidak ada)'}"
67
+ )
68
+
69
+ def available_languages(self) -> list:
70
+ return sorted(self._adapters)
71
+
72
+
73
+ _default_registry: Registry | None = None
74
+
75
+
76
+ def get_registry() -> Registry:
77
+ """Registry singleton default, dipakai oleh cli.py."""
78
+ global _default_registry
79
+ if _default_registry is None:
80
+ _default_registry = Registry()
81
+ return _default_registry
traceback_id/hook.py ADDED
@@ -0,0 +1,53 @@
1
+ """Mode in-process, khusus Python (ARCHITECTURE.md §2, §6).
2
+
3
+ Memasang `sys.excepthook` supaya exception yang tidak tertangani otomatis
4
+ lewat `adapters/python/adapter.py` -> `core/explainer.py` -> `core/formatter.py`
5
+ sebelum ditampilkan. Ini satu-satunya adapter yang punya mode in-process,
6
+ karena Python adalah bahasa implementasi library ini sendiri.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ import traceback as _traceback_module
12
+
13
+ from traceback_id.adapters.python.adapter import PythonAdapter
14
+ from traceback_id.core.explainer import Explainer
15
+ from traceback_id.core.formatter import print_output
16
+
17
+ _adapter = PythonAdapter()
18
+ _explainer = Explainer()
19
+ _original_excepthook = None
20
+
21
+
22
+ def _excepthook(exc_type, exc_value, exc_tb) -> None:
23
+ raw_output = "".join(_traceback_module.format_exception(exc_type, exc_value, exc_tb))
24
+ try:
25
+ structured = _adapter.parse(raw_output)
26
+ explanation = _explainer.explain(structured, _adapter)
27
+ print_output(structured, explanation)
28
+ except Exception:
29
+ # Kalau traceback_id sendiri gagal, jangan sampai menelan traceback
30
+ # asli pengguna — selalu jatuh balik ke excepthook bawaan Python.
31
+ sys.__excepthook__(exc_type, exc_value, exc_tb)
32
+
33
+
34
+ def activate() -> None:
35
+ """Aktifkan penjelasan error otomatis untuk exception yang tidak
36
+ tertangani (FR4). Tidak mengubah exit code program (FR1)."""
37
+ global _original_excepthook
38
+ if _original_excepthook is not None:
39
+ return # sudah aktif
40
+ _original_excepthook = sys.excepthook
41
+ sys.excepthook = _excepthook
42
+
43
+
44
+ def deactivate() -> None:
45
+ """Kembalikan sys.excepthook ke behavior sebelum activate() dipanggil (FR6)."""
46
+ global _original_excepthook
47
+ if _original_excepthook is not None:
48
+ sys.excepthook = _original_excepthook
49
+ _original_excepthook = None
50
+
51
+
52
+ def is_active() -> bool:
53
+ return _original_excepthook is not None
@@ -0,0 +1,99 @@
1
+ # Penjelasan generik per ErrorCategory (ARCHITECTURE.md §5), dipakai sebagai
2
+ # fallback kalau tidak ada rule spesifik-bahasa yang cocok di
3
+ # adapters/<lang>/rules.yaml. Karena dipakai lintas bahasa pemrograman,
4
+ # penjelasannya sengaja netral — tidak menyebut istilah spesifik satu
5
+ # bahasa saja.
6
+
7
+ UNDEFINED_VARIABLE:
8
+ penjelasan: >
9
+ Kamu mencoba memakai sesuatu (variabel, fungsi, atau nama lain) yang
10
+ belum didefinisikan, atau sudah keluar dari jangkauan (scope)-nya di
11
+ titik kode ini.
12
+ saran: >
13
+ Periksa penulisan namanya, dan pastikan sudah didefinisikan sebelum
14
+ dipakai di baris ini.
15
+
16
+ TYPE_MISMATCH:
17
+ penjelasan: >
18
+ Ada operasi yang dilakukan pada nilai dengan tipe data yang tidak
19
+ cocok atau tidak didukung untuk operasi tersebut.
20
+ saran: >
21
+ Cek tipe data nilai-nilai yang terlibat, lalu konversi ke tipe yang
22
+ sesuai sebelum melakukan operasinya.
23
+
24
+ INDEX_OUT_OF_BOUNDS:
25
+ penjelasan: >
26
+ Kamu mencoba mengakses posisi/index pada kumpulan data (list, array,
27
+ dsb.) yang berada di luar jangkauan panjang data tersebut.
28
+ saran: >
29
+ Cek dulu panjang/ukuran datanya sebelum mengakses index tertentu.
30
+
31
+ MISSING_KEY:
32
+ penjelasan: >
33
+ Kamu mencoba mengambil nilai dari struktur data (seperti dictionary
34
+ atau map) memakai kunci yang ternyata tidak ada di dalamnya.
35
+ saran: >
36
+ Pastikan kunci yang dipakai benar-benar ada sebelum mengambil
37
+ nilainya, atau sediakan nilai default kalau kunci tidak ditemukan.
38
+
39
+ DIVISION_BY_ZERO:
40
+ penjelasan: >
41
+ Ada pembagian dengan angka 0, yang secara matematika tidak
42
+ terdefinisikan.
43
+ saran: >
44
+ Tambahkan pengecekan supaya pembagi tidak pernah bernilai 0 sebelum
45
+ melakukan pembagian.
46
+
47
+ MODULE_NOT_FOUND:
48
+ penjelasan: >
49
+ Program mencoba memakai modul/package/library yang tidak ditemukan —
50
+ kemungkinan belum ter-install, salah nama, atau belum diimpor dengan
51
+ benar.
52
+ saran: >
53
+ Pastikan nama modulnya benar dan sudah ter-install di environment
54
+ yang kamu pakai untuk menjalankan program.
55
+
56
+ SYNTAX_ERROR:
57
+ penjelasan: >
58
+ Ada bagian kode yang tidak mengikuti aturan penulisan bahasa
59
+ pemrogramannya, sehingga kode tidak bisa diproses sama sekali sebelum
60
+ sempat dijalankan.
61
+ saran: >
62
+ Periksa tanda kurung, titik dua/titik koma, kutip, dan indentasi di
63
+ sekitar lokasi error yang ditunjukkan.
64
+
65
+ ATTRIBUTE_ERROR:
66
+ penjelasan: >
67
+ Kamu mencoba memakai atribut, properti, atau method yang tidak
68
+ dimiliki oleh tipe data/objek tersebut.
69
+ saran: >
70
+ Cek lagi tipe data objeknya dan pastikan nama atribut/method-nya
71
+ benar.
72
+
73
+ FILE_NOT_FOUND:
74
+ penjelasan: >
75
+ Program mencoba membuka atau mengakses file yang tidak ditemukan di
76
+ lokasi yang dicari.
77
+ saran: >
78
+ Cek lagi nama dan lokasi (path) file-nya, termasuk folder tempat
79
+ kamu menjalankan program.
80
+
81
+ RECURSION_ERROR:
82
+ penjelasan: >
83
+ Sebuah fungsi memanggil dirinya sendiri terlalu banyak kali tanpa
84
+ pernah berhenti, sampai melebihi batas kedalaman yang diizinkan.
85
+ saran: >
86
+ Pastikan ada kondisi berhenti (base case) yang pasti tercapai dalam
87
+ fungsi rekursifmu.
88
+
89
+ VALUE_ERROR:
90
+ penjelasan: >
91
+ Tipe datanya sudah sesuai, tapi nilainya tidak valid atau tidak masuk
92
+ akal untuk operasi yang sedang dilakukan.
93
+ saran: >
94
+ Periksa lagi nilai yang dipakai, terutama kalau nilainya berasal dari
95
+ input pengguna atau konversi tipe data.
96
+
97
+ # Sengaja tidak ada entri UNKNOWN di sini — kalau category-nya benar-benar
98
+ # UNKNOWN (tidak ada rule bahasa maupun mapping kategori yang cocok),
99
+ # core/explainer.py jatuh ke fallback generik bawaan kode (tier ke-3).
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceback-id
3
+ Version: 0.1.0
4
+ Summary: Penjelasan error/traceback pemrograman dalam Bahasa Indonesia, untuk pemula.
5
+ Author: traceback_id contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/traceback-id/traceback_id
8
+ Project-URL: Issues, https://github.com/traceback-id/traceback_id/issues
9
+ Keywords: error,traceback,bahasa indonesia,belajar coding,education,debugging
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Intended Audience :: Education
18
+ Classifier: Natural Language :: Indonesian
19
+ Classifier: Topic :: Software Development :: Debuggers
20
+ Classifier: Topic :: Education
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: PyYAML>=6.0
25
+ Provides-Extra: color
26
+ Requires-Dist: rich>=13.0; extra == "color"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: rich>=13.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # traceback_id
33
+
34
+ Penjelasan error/traceback pemrograman dalam **Bahasa Indonesia**, untuk
35
+ pemula — dimulai dari Python.
36
+
37
+
38
+ ## Kenapa traceback_id?
39
+
40
+ Traceback Python (atau stack trace/compiler error di bahasa lain) ditulis
41
+ dalam bahasa Inggris teknis yang sering bikin pemula mentok. traceback_id
42
+ tetap menampilkan error aslinya (developer berpengalaman tetap butuh itu),
43
+ lalu menambahkan penjelasan singkat dalam Bahasa Indonesia: **kenapa** error
44
+ ini terjadi, dan **apa** yang bisa dicoba untuk memperbaikinya.
45
+
46
+ ## Instalasi
47
+
48
+ ```bash
49
+ pip install -e ".[color]"
50
+ ```
51
+
52
+ `rich` (untuk output berwarna) sifatnya opsional — tanpa `[color]`,
53
+ traceback_id tetap berfungsi penuh dengan output teks polos.
54
+
55
+ ## Pemakaian
56
+
57
+ ### Mode in-process (khusus Python — DX terbaik)
58
+
59
+ ```python
60
+ import traceback_id
61
+ traceback_id.activate()
62
+
63
+ print(nilai_yang_tidak_ada)
64
+ ```
65
+
66
+ ```
67
+ Traceback (most recent call last):
68
+ File "contoh.py", line 4, in <module>
69
+ print(nilai_yang_tidak_ada)
70
+ NameError: name 'nilai_yang_tidak_ada' is not defined
71
+ ------------------------------------------------------------
72
+ penjelasan traceback_id (Bahasa Indonesia):
73
+ Kode kamu mencoba memakai nama `nilai_yang_tidak_ada`, tapi Python belum
74
+ pernah melihat nama itu didefinisikan sebelumnya di titik ini.
75
+
76
+ Saran: Periksa apakah `nilai_yang_tidak_ada` sudah kamu isi nilainya
77
+ sebelum baris ini, atau cek kemungkinan salah ketik nama variabel/fungsi.
78
+ ```
79
+
80
+ Nonaktifkan kapan saja dengan `traceback_id.deactivate()` — traceback
81
+ kembali ke behavior default Python, dan exit code program tidak berubah.
82
+
83
+ ### Mode CLI (universal — dipakai bahasa apa pun yang punya adapter)
84
+
85
+ ```bash
86
+ traceback_id run script.py
87
+ ```
88
+
89
+ Ekstensi file (`.py`, nanti `.js`, `.java`, dst) dipakai untuk memilih
90
+ adapter secara otomatis, atau paksa lewat `--lang`:
91
+
92
+ ```bash
93
+ traceback_id run script.mjs --lang javascript
94
+ ```
95
+
96
+ ### Contoh untuk dicoba
97
+
98
+ ```bash
99
+ traceback_id run examples/demo_errors.py name_error
100
+ traceback_id run examples/syntax_error_demo.py
101
+ traceback_id run examples/indentation_error_demo.py
102
+ ```
103
+
104
+ Lihat `examples/demo_errors.py` untuk daftar lengkap 15 jenis error yang
105
+ di-cover di v0.1 (`name_error`, `type_error`, `index_error`, `key_error`,
106
+ `attribute_error`, `zero_division_error`, `import_error`,
107
+ `module_not_found_error`, `value_error`, `file_not_found_error`,
108
+ `recursion_error`, `stop_iteration`, `unbound_local_error`, plus
109
+ `syntax_error_demo.py` & `indentation_error_demo.py` yang terpisah).
110
+
111
+ ## Struktur Proyek
112
+
113
+ ```
114
+ traceback_id/
115
+ ├── traceback_id/ # package yang diinstall
116
+ │ ├── __init__.py # activate() / deactivate() publik
117
+ │ ├── hook.py # sys.excepthook, mode in-process
118
+ │ ├── cli.py # `traceback_id run <file>`
119
+ │ ├── core/ # mesin inti — language-agnostic
120
+ │ │ ├── models.py # StructuredError, ErrorCategory
121
+ │ │ ├── explainer.py # cocokkan rule, render template
122
+ │ │ ├── formatter.py # gabung traceback asli + penjelasan
123
+ │ │ └── registry.py # daftar adapter (built-in + plugin)
124
+ │ ├── adapters/
125
+ │ │ ├── base.py # LanguageAdapter (kontrak)
126
+ │ │ └── python/
127
+ │ │ ├── adapter.py # capture (hook + subprocess), parser
128
+ │ │ └── rules.yaml # 15 error type Python v0.1
129
+ │ └── rules/
130
+ │ └── universal_categories.yaml # fallback lintas bahasa
131
+ ├── tests/
132
+ │ ├── test_core/
133
+ │ ├── test_adapters/
134
+ │ └── test_contract.py # semua adapter wajib lolos test ini
135
+ ├── examples/
136
+ │ └── demo_errors.py
137
+ ├── pyproject.toml
138
+ └── README.md
139
+ ```
140
+
141
+ Menambah bahasa baru = buat folder `traceback_id/adapters/<bahasa>/` berisi
142
+ `adapter.py` (implementasi `LanguageAdapter`) + `rules.yaml` — `core/` tidak
143
+ perlu disentuh sama sekali (lihat `ARCHITECTURE.md` §7).
144
+
145
+ ## Menambah Rule Baru (Python)
146
+
147
+ Rule ada di `traceback_id/adapters/python/rules.yaml`, key-nya = nama
148
+ error native (mis. `NameError`). Tidak perlu ubah kode inti:
149
+
150
+ ```yaml
151
+ NamaErrorBaru:
152
+ category: KATEGORI_UNIVERSAL_YANG_SESUAI # lihat ErrorCategory di core/models.py
153
+ pattern: "regex opsional dengan (?P<nama_variabel>...)"
154
+ penjelasan: >
155
+ Penjelasan kenapa error ini terjadi. Bisa pakai {nama_variabel} yang
156
+ diambil dari `pattern` di atas.
157
+ saran: >
158
+ Saran konkret cara memperbaikinya.
159
+ ```
160
+
161
+ ## Kontribusi Adapter Bahasa Baru (fase komunitas)
162
+
163
+ Setelah kontrak `LanguageAdapter` dibuka untuk plugin (roadmap Fase 3 —
164
+ lihat `PRD.md` §10), adapter bahasa baru bisa didistribusikan sebagai
165
+ package Python terpisah dan otomatis terdeteksi lewat `entry_points`:
166
+
167
+ ```toml
168
+ # pyproject.toml package plugin kamu, mis. traceback-id-javascript
169
+ [project.entry-points."traceback_id.adapters"]
170
+ javascript = "traceback_id_javascript.adapter:JavaScriptAdapter"
171
+ ```
172
+
173
+ Semua adapter — built-in maupun plugin — wajib lolos `tests/test_contract.py`.
174
+
175
+ ## Menjalankan Test
176
+
177
+ ```bash
178
+ pip install -e ".[dev]"
179
+ pytest
180
+ ```
181
+
182
+ ## Status & Roadmap
183
+
184
+ Draft v0.1 (MVP): arsitektur core + adapter diterapkan, adapter Python
185
+ matang (15 jenis error, mode in-process & CLI). Roadmap lengkap
186
+ (JavaScript di v0.3, Java + `entry_points` publik di v0.4, dst) ada di
187
+ `PRD.md` §10 dan `ARCHITECTURE.md` §10.
188
+
189
+ ## Lisensi
190
+
191
+ MIT — lihat [`LICENSE`](LICENSE).
@@ -0,0 +1,21 @@
1
+ traceback_id/__init__.py,sha256=22TRl9vvLO576GNgjsxdAl-5GahwcAdGV-5SzdqhQEY,555
2
+ traceback_id/__main__.py,sha256=qkzyvM0j9bKmEouFAkHPVuhvWr92D4mWPxGqGYl5r1A,131
3
+ traceback_id/cli.py,sha256=E_Vo0oT-NMi92dYcEINcy4h6SpOsAenNzC4aWR2X-xk,2594
4
+ traceback_id/hook.py,sha256=KB-uOM8DY5V5FKdy-a3FbEneiJfEW69qqL64dTN84-w,1909
5
+ traceback_id/adapters/__init__.py,sha256=w__gKur4jxqGACU_le9uDrmZO5IBZaKQeCtkaz4MyS4,69
6
+ traceback_id/adapters/base.py,sha256=DmKesegCNvEYK5D_JncU_5XfZ6ha4g32v2wDhOfNsL0,1138
7
+ traceback_id/adapters/python/__init__.py,sha256=yLFuuLlP94WCw8dw3H8p7TBLKaJpi-jBKxf4j91MePo,82
8
+ traceback_id/adapters/python/adapter.py,sha256=4fbm19ZEjxCea1ZkHSUcHqWOCll3FlYtqkQ8B0fw90A,5085
9
+ traceback_id/adapters/python/rules.yaml,sha256=162lhcpBsvOQdmeveNyKRATReuMa29AH0Qz_yvpzmL4,6990
10
+ traceback_id/core/__init__.py,sha256=sIYlxO174AEpdCu6OsWFUNl4tHtUqBatCXwFW5J4btw,75
11
+ traceback_id/core/explainer.py,sha256=fUBx7_ZPnlUgXiNosBj_MR0i6PmVrevDlG7BwUrA0lw,3896
12
+ traceback_id/core/formatter.py,sha256=0EZsZ584bDy1V0PfoDeOLlYv9CpUe8Ua3y--pVGMvSU,1755
13
+ traceback_id/core/models.py,sha256=5ZwaxPzbg-RCQWG4SlfhRBsXEYTV_LGteTWWLqX5uuc,1899
14
+ traceback_id/core/registry.py,sha256=iPnPQSO4y-Q2cds-iRgj86pPMTqc2zE4sp8IQTWEXOA,2907
15
+ traceback_id/rules/universal_categories.yaml,sha256=PLlSiD7lcP5RneE-l1cVl7OXXqFiJ9P6-HGi-dbuRUY,3580
16
+ traceback_id-0.1.0.dist-info/licenses/LICENSE,sha256=s-eFVE2OqauKQO6tSOc7MbhKc5E3LDvLb2GJ1A5ARVs,1082
17
+ traceback_id-0.1.0.dist-info/METADATA,sha256=SpxdMFgtT1FfJsxYLNL1FZFeZIiAza6gYrPE8mvqqqQ,6828
18
+ traceback_id-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
19
+ traceback_id-0.1.0.dist-info/entry_points.txt,sha256=SYVIIM8s6dQvQjwYnyWuSO8F5lvJrnfBZSf9hL1F6hc,55
20
+ traceback_id-0.1.0.dist-info/top_level.txt,sha256=VkG5K7QhrUMJaQO5iZOOOA6Uv1gE_mA7b2gZ9KSFS70,13
21
+ traceback_id-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ traceback_id = traceback_id.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 traceback_id contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ traceback_id