rakit-cli 2.0.1__tar.gz

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,59 @@
1
+ # Python / uv / Django
2
+ celerybeat-schedule*
3
+ *.bak
4
+ *.dat
5
+ *.dir
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.egg-info/
10
+ .venv/
11
+ venv/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .mypy_cache/
15
+ .coverage
16
+ htmlcov/
17
+ db.sqlite3
18
+ db.sqlite3-journal
19
+ staticfiles/
20
+ media/
21
+
22
+ # Secrets & Environment
23
+ .env
24
+ .env.*
25
+ !.env.example
26
+ *.pem
27
+ *.key
28
+ *.jks
29
+ secrets/
30
+
31
+ # Flutter / Dart
32
+ mobile/.dart_tool/
33
+ mobile/.packages
34
+ mobile/build/
35
+ mobile/.flutter-plugins
36
+ mobile/.flutter-plugins-dependencies
37
+ mobile/coverage/
38
+
39
+ # Node / Playwright Runners
40
+ node_modules/
41
+ runners/node_modules/
42
+ runners/artifacts/
43
+ runners/playwright-report/
44
+ runners/test-results/
45
+ runners/.auth/
46
+
47
+ # OS / Editor
48
+ .DS_Store
49
+ Thumbs.db
50
+ .idea/
51
+
52
+ # Local RAKIT CLI workspace binding cache
53
+ .rakit/
54
+
55
+ # Binary APK rilis mobile (disajikan dari backend/media, bukan dari git)
56
+ mobile/dist/*.apk
57
+ mobile/android/key.properties
58
+ *.jks
59
+ *.keystore
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: rakit-cli
3
+ Version: 2.0.1
4
+ Summary: Pendamping folder notebook lokal & server MCP stdio untuk RAKIT v2.0
5
+ Project-URL: Homepage, https://github.com/radianapp/rakit
6
+ Project-URL: Documentation, https://github.com/radianapp/rakit
7
+ Project-URL: Repository, https://github.com/radianapp/rakit
8
+ Author-email: RAKIT Team <rakitsbs@gmail.com>
9
+ License: MIT
10
+ Keywords: agentic,ai,developer-tools,mcp,orchestration,workflow
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # rakit-cli
22
+
23
+ [![PyPI version](https://img.shields.io/pypi/v/rakit-cli.svg)](https://pypi.org/project/rakit-cli/)
24
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
26
+
27
+ **rakit-cli** adalah pendamping baris perintah (CLI) dan server MCP (Model Context Protocol) berbasis `stdio` untuk ekosistem **RAKIT v2.0 (Radian Agentic Kit & Interactive Tracking)**.
28
+
29
+ Alat ini digunakan oleh pengembang perangkat lunak dan asisten agen AI (seperti Cursor, Claude Code, Windsurf, Copilot, atau Antigravity) untuk mengikat folder notebook lokal ke peladen RAKIT, menyinkronkan progres tiket, dan menjalankan gerbang verifikasi kualitas.
30
+
31
+ ---
32
+
33
+ ## Fitur Utama
34
+
35
+ - 🔒 **SecretFilter Bawaan**: Mencegah kebocoran berkas sensitif (`.env*`, `*.pem`, `*.key`, token, dan kredensial) secara lokal sebelum dikirim ke server.
36
+ - 📦 **Zero Runtime Dependencies**: Murni menggunakan pustaka bawaan Python 3.12+ (*standard library*), sangat ringan dan cepat dipasang.
37
+ - 🤖 **Server MCP Stdio (`rakit-mcp`)**: Menyediakan perkakas `rakit_status`, `rakit_claim_ticket`, `rakit_sync_progress`, dan `rakit_ask_human` langsung ke agen AI di editor Anda.
38
+ - ⚡ **Offline-First Synchronization**: Perubahan status dan progres tiket dicatat di buffer lokal saat koneksi internet terputus dan disinkronkan otomatis saat kembali online.
39
+ - 🛡️ **Verifikasi Otomatis**: Integrasi linter dan test suite lokal (`rakit verify`) sebelum mengajukan tiket ke gerbang QA/UAT.
40
+
41
+ ---
42
+
43
+ ## Instalasi
44
+
45
+ ### Menggunakan `pip` (Standar)
46
+ ```bash
47
+ pip install rakit-cli
48
+ ```
49
+
50
+ ### Menggunakan `pipx` (Direkomendasikan untuk CLI Terisolasi)
51
+ ```bash
52
+ pipx install rakit-cli
53
+ ```
54
+
55
+ ### Menggunakan `uv tool`
56
+ ```bash
57
+ uv tool install rakit-cli
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Panduan Penggunaan Singkat
63
+
64
+ ### 1. Inisialisasi Proyek Lokal
65
+ Arahkan terminal ke direktori proyek lokal Anda, lalu hubungkan ke server RAKIT:
66
+
67
+ ```bash
68
+ cd /jalur/ke/folder/proyek-anda
69
+ rakit init --project siaga --api-url http://localhost:8000/api/v1 --token <TOKEN_API_ANDA>
70
+ ```
71
+
72
+ > **Tip Token API:** Dapatkan token autentikasi Anda dari Web Console RAKIT di menu **Profil & Token API** (`http://localhost:8000/accounts/profile/`).
73
+
74
+ ### 2. Memindai Struktur Proyek (Smart Scan)
75
+ ```bash
76
+ rakit scan
77
+ ```
78
+
79
+ ### 3. Mengklaim Tiket Kerja
80
+ ```bash
81
+ rakit start TIKET-101
82
+ ```
83
+
84
+ ### 4. Menjalankan Verifikasi Kualitas
85
+ ```bash
86
+ rakit verify
87
+ ```
88
+
89
+ ### 5. Mengaktifkan Server MCP untuk AI
90
+ Tambahkan konfigurasi berikut ke berkas MCP editor Anda (`claude_desktop_config.json` atau settings Cursor/VS Code):
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "rakit-dev": {
96
+ "command": "rakit-mcp",
97
+ "args": ["--workspace", "/jalur/ke/folder/proyek-anda"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Lisensi
106
+
107
+ Didistribusikan di bawah lisensi MIT. Lihat berkas `LICENSE` untuk rincian lengkap.
108
+
@@ -0,0 +1,88 @@
1
+ # rakit-cli
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/rakit-cli.svg)](https://pypi.org/project/rakit-cli/)
4
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **rakit-cli** adalah pendamping baris perintah (CLI) dan server MCP (Model Context Protocol) berbasis `stdio` untuk ekosistem **RAKIT v2.0 (Radian Agentic Kit & Interactive Tracking)**.
8
+
9
+ Alat ini digunakan oleh pengembang perangkat lunak dan asisten agen AI (seperti Cursor, Claude Code, Windsurf, Copilot, atau Antigravity) untuk mengikat folder notebook lokal ke peladen RAKIT, menyinkronkan progres tiket, dan menjalankan gerbang verifikasi kualitas.
10
+
11
+ ---
12
+
13
+ ## Fitur Utama
14
+
15
+ - 🔒 **SecretFilter Bawaan**: Mencegah kebocoran berkas sensitif (`.env*`, `*.pem`, `*.key`, token, dan kredensial) secara lokal sebelum dikirim ke server.
16
+ - 📦 **Zero Runtime Dependencies**: Murni menggunakan pustaka bawaan Python 3.12+ (*standard library*), sangat ringan dan cepat dipasang.
17
+ - 🤖 **Server MCP Stdio (`rakit-mcp`)**: Menyediakan perkakas `rakit_status`, `rakit_claim_ticket`, `rakit_sync_progress`, dan `rakit_ask_human` langsung ke agen AI di editor Anda.
18
+ - ⚡ **Offline-First Synchronization**: Perubahan status dan progres tiket dicatat di buffer lokal saat koneksi internet terputus dan disinkronkan otomatis saat kembali online.
19
+ - 🛡️ **Verifikasi Otomatis**: Integrasi linter dan test suite lokal (`rakit verify`) sebelum mengajukan tiket ke gerbang QA/UAT.
20
+
21
+ ---
22
+
23
+ ## Instalasi
24
+
25
+ ### Menggunakan `pip` (Standar)
26
+ ```bash
27
+ pip install rakit-cli
28
+ ```
29
+
30
+ ### Menggunakan `pipx` (Direkomendasikan untuk CLI Terisolasi)
31
+ ```bash
32
+ pipx install rakit-cli
33
+ ```
34
+
35
+ ### Menggunakan `uv tool`
36
+ ```bash
37
+ uv tool install rakit-cli
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Panduan Penggunaan Singkat
43
+
44
+ ### 1. Inisialisasi Proyek Lokal
45
+ Arahkan terminal ke direktori proyek lokal Anda, lalu hubungkan ke server RAKIT:
46
+
47
+ ```bash
48
+ cd /jalur/ke/folder/proyek-anda
49
+ rakit init --project siaga --api-url http://localhost:8000/api/v1 --token <TOKEN_API_ANDA>
50
+ ```
51
+
52
+ > **Tip Token API:** Dapatkan token autentikasi Anda dari Web Console RAKIT di menu **Profil & Token API** (`http://localhost:8000/accounts/profile/`).
53
+
54
+ ### 2. Memindai Struktur Proyek (Smart Scan)
55
+ ```bash
56
+ rakit scan
57
+ ```
58
+
59
+ ### 3. Mengklaim Tiket Kerja
60
+ ```bash
61
+ rakit start TIKET-101
62
+ ```
63
+
64
+ ### 4. Menjalankan Verifikasi Kualitas
65
+ ```bash
66
+ rakit verify
67
+ ```
68
+
69
+ ### 5. Mengaktifkan Server MCP untuk AI
70
+ Tambahkan konfigurasi berikut ke berkas MCP editor Anda (`claude_desktop_config.json` atau settings Cursor/VS Code):
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "rakit-dev": {
76
+ "command": "rakit-mcp",
77
+ "args": ["--workspace", "/jalur/ke/folder/proyek-anda"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Lisensi
86
+
87
+ Didistribusikan di bawah lisensi MIT. Lihat berkas `LICENSE` untuk rincian lengkap.
88
+
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "rakit-cli"
7
+ version = "2.0.1"
8
+ description = "Pendamping folder notebook lokal & server MCP stdio untuk RAKIT v2.0"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "RAKIT Team", email = "rakitsbs@gmail.com" }
14
+ ]
15
+ keywords = ["agentic", "ai", "mcp", "workflow", "developer-tools", "orchestration"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Build Tools",
23
+ "Topic :: Software Development :: Quality Assurance",
24
+ ]
25
+ dependencies = []
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/radianapp/rakit"
29
+ Documentation = "https://github.com/radianapp/rakit"
30
+ Repository = "https://github.com/radianapp/rakit"
31
+
32
+ [project.scripts]
33
+ rakit = "rakit_cli.main:main"
34
+ rakit-mcp = "rakit_cli.mcp_server:main"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest>=9.0.0",
39
+ "pytest-cov>=7.0.0",
40
+ "ruff>=0.15.0",
41
+ ]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/rakit_cli"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ addopts = "--cov=rakit_cli --cov-report=term-missing"
49
+
50
+ [tool.ruff]
51
+ line-length = 100
52
+ target-version = "py312"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "I", "B", "UP"]
@@ -0,0 +1,3 @@
1
+ """Paket pendamping folder notebook lokal (`rakit`) dan server MCP `stdio` (`rakit-mcp`)."""
2
+
3
+ __version__ = "2.0.1"
@@ -0,0 +1,21 @@
1
+ """Banner dan identitas visual terminal (ASCII/Unicode Logo) untuk `rakit-cli` & `rakit-mcp`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ RAKIT_CLI_VERSION = "2.0.1"
6
+
7
+ RAKIT_ASCII_LOGO = r"""
8
+ ____ _ _ _____ _____ ____ ___
9
+ | _ \ / \ | |/ /_ _|_ _| __ _|___ \ / _ \
10
+ | |_) | / _ \ | ' / | | | | \ \ / / __) | | | |
11
+ | _ < / ___ \| . \ | | | | \ V / / __/| |_| |
12
+ |_| \_\/_/ \_\_|\_\___| |_| \_/ |_____|\___/
13
+ [G1: Spec Gate] ---> [G2: E2E Video UAT] ---> [G3: Human Merge]
14
+ """.strip("\n")
15
+
16
+ RAKIT_COMPACT_BADGE = "◆ RAKIT v2.0 · Autonomous SDLC & Hybrid Workspace Engine"
17
+
18
+
19
+ def render_cli_banner() -> str:
20
+ """Mengembalikan banner teks logo RAKIT v2.0 untuk layar bantuan CLI (`rakit banner`)."""
21
+ return f"{RAKIT_ASCII_LOGO}\n {RAKIT_COMPACT_BADGE}\n"
@@ -0,0 +1,267 @@
1
+ """Pengelolaan berkas konfigurasi workspace lokal `.rakit/workspace.json` bebas rahasia."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import socket
8
+ from dataclasses import asdict, dataclass
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ DEFAULT_SECRET_BLOCKLIST: tuple[str, ...] = (
13
+ ".env",
14
+ ".env.*",
15
+ "*.pem",
16
+ "*.key",
17
+ "*.p12",
18
+ "*.pfx",
19
+ "id_rsa*",
20
+ "id_ed25519*",
21
+ "secrets/**",
22
+ "credentials.json",
23
+ )
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class WorkspaceConfig:
28
+ """
29
+ TUJUAN: Menyimpan identitas proyek, server RAKIT, sidik jari folder,
30
+ token API lokal, dan daftar blokir rahasia.
31
+
32
+ KEPUTUSAN TEKNIS: Token autentikasi disimpan di `.rakit/workspace.json` yang secara otomatis
33
+ masuk ke `.rakit/.gitignore` agar tidak pernah ter-commit ke repositori Git.
34
+ DIPANGGIL DARI: `rakit init`, `rakit status`, `rakit token`, dan server MCP `stdio`
35
+ DEPENDENSI: pathlib, hashlib, json
36
+ """
37
+
38
+ project_slug: str
39
+ api_base_url: str
40
+ machine_label: str
41
+ root_fingerprint: str
42
+ active_ticket_key: str = ""
43
+ api_token: str = ""
44
+ secret_blocklist: tuple[str, ...] = DEFAULT_SECRET_BLOCKLIST
45
+
46
+
47
+ def compute_workspace_fingerprint(workspace_root: Path) -> str:
48
+ """Menghasilkan hash SHA-256 satu arah dari jalur absolut folder kerja notebook."""
49
+ normalized = str(workspace_root.resolve()).lower().encode("utf-8")
50
+ return hashlib.sha256(normalized).hexdigest()
51
+
52
+
53
+ def load_workspace_config(workspace_root: Path) -> WorkspaceConfig | None:
54
+ """Membaca `.rakit/workspace.json` bila sudah diinisialisasi."""
55
+ config_file = workspace_root / ".rakit" / "workspace.json"
56
+ if not config_file.exists():
57
+ return None
58
+ raw_data = json.loads(config_file.read_text(encoding="utf-8"))
59
+ raw_blocklist = raw_data.get("secret_blocklist")
60
+ resolved_blocklist = (
61
+ tuple(str(item) for item in raw_blocklist)
62
+ if isinstance(raw_blocklist, list) and raw_blocklist
63
+ else DEFAULT_SECRET_BLOCKLIST
64
+ )
65
+ return WorkspaceConfig(
66
+ project_slug=str(raw_data["project_slug"]),
67
+ api_base_url=str(raw_data["api_base_url"]),
68
+ machine_label=str(raw_data.get("machine_label") or socket.gethostname()),
69
+ root_fingerprint=str(raw_data["root_fingerprint"]),
70
+ active_ticket_key=str(raw_data.get("active_ticket_key") or ""),
71
+ api_token=str(raw_data.get("api_token") or ""),
72
+ secret_blocklist=resolved_blocklist,
73
+ )
74
+
75
+
76
+ def save_workspace_config(workspace_root: Path, config: WorkspaceConfig) -> Path:
77
+ """Menyimpan `.rakit/workspace.json` dan memastikan `.rakit/.gitignore` dibuat."""
78
+ rakit_dir = workspace_root / ".rakit"
79
+ rakit_dir.mkdir(parents=True, exist_ok=True)
80
+
81
+ gitignore_file = rakit_dir / ".gitignore"
82
+ if not gitignore_file.exists():
83
+ gitignore_file.write_text(
84
+ "workspace.json\n*.token\ncache/\noffline_queue.json\n", encoding="utf-8"
85
+ )
86
+ else:
87
+ existing_gi = gitignore_file.read_text(encoding="utf-8")
88
+ if "workspace.json" not in existing_gi:
89
+ gitignore_file.write_text("workspace.json\n" + existing_gi, encoding="utf-8")
90
+
91
+ config_file = rakit_dir / "workspace.json"
92
+ payload = asdict(config)
93
+ payload["secret_blocklist"] = list(config.secret_blocklist)
94
+ config_file.write_text(
95
+ json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
96
+ encoding="utf-8",
97
+ )
98
+ return config_file
99
+
100
+
101
+ def get_effective_api_token(config: WorkspaceConfig | None) -> str:
102
+ """
103
+ Mengambil token API aktif dengan urutan prioritas:
104
+ 1. Environment variable `RAKIT_API_TOKEN`
105
+ 2. `api_token` tersimpan di `.rakit/workspace.json`
106
+ """
107
+ import os
108
+
109
+ env_token = os.environ.get("RAKIT_API_TOKEN", "").strip()
110
+ if env_token:
111
+ return env_token
112
+ if config and config.api_token:
113
+ return config.api_token.strip()
114
+ return ""
115
+
116
+
117
+ def enqueue_offline_sync_event(workspace_root: Path, event_payload: dict[str, Any]) -> Path:
118
+ """
119
+ TUJUAN: Mencatat progres TASKS.md ke antrean luring `.rakit/offline_queue.json` (US-011).
120
+
121
+ ALUR:
122
+ 1. Membaca daftar event yang belum terkirim di `.rakit/offline_queue.json`.
123
+ 2. Bila `idempotency_key` belum ada di antrean, menambahkan event baru.
124
+ 3. Menyimpan kembali berkas antrean luring.
125
+
126
+ DIPANGGIL DARI: `rakit sync` dan perkakas MCP `rakit_sync_progress`
127
+ DEPENDENSI: json, pathlib
128
+ """
129
+ rakit_dir = workspace_root / ".rakit"
130
+ rakit_dir.mkdir(parents=True, exist_ok=True)
131
+ queue_file = rakit_dir / "offline_queue.json"
132
+
133
+ existing_items: list[dict[str, Any]] = []
134
+ if queue_file.exists():
135
+ loaded = json.loads(queue_file.read_text(encoding="utf-8"))
136
+ if isinstance(loaded, list):
137
+ existing_items = loaded
138
+
139
+ idem_key = str(event_payload.get("idempotency_key") or "")
140
+ if not idem_key or all(item.get("idempotency_key") != idem_key for item in existing_items):
141
+ existing_items.append(event_payload)
142
+
143
+ queue_file.write_text(
144
+ json.dumps(existing_items, indent=2, ensure_ascii=False) + "\n",
145
+ encoding="utf-8",
146
+ )
147
+ return queue_file
148
+
149
+
150
+ def write_active_task_prompt(
151
+ workspace_root: Path,
152
+ ticket_data: dict[str, Any],
153
+ ) -> Path:
154
+ """
155
+ Menulis berkas `.rakit/PROMPT.md` yang memuat konteks instruksi kerja aktif
156
+ sehingga agen AI lokal dapat langsung mengeksekusi dengan perintah
157
+ 'Lanjutkan pekerjaan dari RAKIT'.
158
+ """
159
+ rakit_dir = workspace_root / ".rakit"
160
+ rakit_dir.mkdir(parents=True, exist_ok=True)
161
+ prompt_file = rakit_dir / "PROMPT.md"
162
+
163
+ key = str(ticket_data.get("key") or "UNKNOWN")
164
+ title = str(ticket_data.get("title") or "Tugas Aktif")
165
+ kind = str(ticket_data.get("kind") or "WORK_ITEM")
166
+ category = str(ticket_data.get("category") or "").strip()
167
+ category_md = f"- **Kategori Alur / Event**: `{category}`\n" if category else ""
168
+ desc = str(ticket_data.get("description") or "Tidak ada deskripsi spesifik.")
169
+ allowed_list = ticket_data.get("allowed_paths") or []
170
+ allowed = ", ".join(allowed_list) if allowed_list else "Semua jalur modul proyek"
171
+ forbidden_list = ticket_data.get("forbidden_paths") or []
172
+ forbidden = ", ".join(forbidden_list) if forbidden_list else "Tidak ada"
173
+
174
+ ac_items = ticket_data.get("acceptance_criteria") or []
175
+ ac_lines: list[str] = []
176
+ if isinstance(ac_items, list) and ac_items:
177
+ for idx, ac in enumerate(ac_items, 1):
178
+ if isinstance(ac, dict):
179
+ given = ac.get("given", "-")
180
+ when = ac.get("when", "-")
181
+ then = ac.get("then", "-")
182
+ ac_lines.append(
183
+ f"{idx}. **Given**: {given}\n **When**: {when}\n **Then**: {then}"
184
+ )
185
+ else:
186
+ ac_lines.append(f"{idx}. {ac}")
187
+ ac_formatted = (
188
+ "\n\n".join(ac_lines) if ac_lines else "- Mengikuti spesifikasi arsitektur standar modul."
189
+ )
190
+
191
+ content = (
192
+ f"# 📋 TUGAS AKTIF RAKIT: [{key}] {title}\n\n"
193
+ f"Kamu sedang mengerjakan tiket aktif dari orkestrator **RAKIT v2.0**.\n\n"
194
+ f"## 1. Identitas & Kontrak Tiket\n"
195
+ f"- **Kunci Tiket**: `{key}`\n"
196
+ f"- **Jenis**: `{kind}`\n"
197
+ f"{category_md}"
198
+ f"- **Pagar Direktori Diizinkan (`allowed_paths`)**: `{allowed}`\n"
199
+ f"- **Direktori Terlarang (`forbidden_paths`)**: `{forbidden}`\n\n"
200
+ f"## 2. Deskripsi Pekerjaan\n"
201
+ f"{desc}\n\n"
202
+ f"## 3. Kriteria Penerimaan (Acceptance Criteria / GWT)\n"
203
+ f"{ac_formatted}\n\n"
204
+ f"## 4. Instruksi Eksekusi untuk AI\n"
205
+ f"1. Periksa kode dan modul terkait di folder proyek ini sesuai `allowed_paths`.\n"
206
+ f"2. Tulis kode implementasi dan unit test pendukung hingga semua kriteria di atas "
207
+ f"terpenuhi dan tes lulus hijau.\n"
208
+ f"3. Selama pengerjaan, laporkan progresmu secara berkala ke Web Console RAKIT:\n"
209
+ f' `rakit sync --note "Deskripsi progres milestone aktif"`\n'
210
+ f"4. Bila kamu ragu atau butuh input arsitektur, panggil tool MCP `rakit_ask_human` "
211
+ f"atau laporkan via `rakit sync`.\n"
212
+ f"5. Setelah kode selesai dan semua pengujian lulus, jalankan:\n"
213
+ f" `rakit verify`\n"
214
+ f" untuk mengajukan tiket ke tahap QA & UAT Review (Gerbang G2).\n\n"
215
+ f"> **PENTING**: Kerjakan perubahan kode secara nyata di repositori ini. "
216
+ f"Dilarang hanya mensimulasikan hasil di chat!\n"
217
+ )
218
+ prompt_file.write_text(content, encoding="utf-8")
219
+ return prompt_file
220
+
221
+
222
+ def fetch_and_write_prompt(
223
+ workspace_root: Path,
224
+ cfg: WorkspaceConfig,
225
+ ticket_key: str | None = None,
226
+ ) -> Path | None:
227
+ """
228
+ TUJUAN: Mengambil data tiket dari peladen RAKIT dan menulis .rakit/PROMPT.md.
229
+
230
+ DIPANGGIL DARI: `rakit prompt`, `rakit sync` (bila belum ada), dan `rakit start`.
231
+ """
232
+ key = (ticket_key or "").strip() or (cfg.active_ticket_key or "").strip()
233
+ if not key:
234
+ return None
235
+
236
+ token = get_effective_api_token(cfg)
237
+ ticket_obj: dict[str, Any] = {
238
+ "key": key,
239
+ "title": f"Tugas aktif {key}",
240
+ "kind": "FEATURE",
241
+ "description": "Dikerjakan dari lingkungan lokal.",
242
+ "allowed_paths": [],
243
+ "forbidden_paths": [],
244
+ "acceptance_criteria": [],
245
+ }
246
+
247
+ if cfg.api_base_url:
248
+ try:
249
+ import urllib.request
250
+
251
+ url = f"{cfg.api_base_url}/tickets/{key}/?project_slug={cfg.project_slug}"
252
+ headers = {"Content-Type": "application/json"}
253
+ if token:
254
+ headers["Authorization"] = f"Token {token}"
255
+ req = urllib.request.Request(url, headers=headers, method="GET")
256
+ with urllib.request.urlopen(req, timeout=4) as resp:
257
+ if resp.status == 200:
258
+ raw_data = json.loads(resp.read().decode("utf-8"))
259
+ payload = raw_data.get("data") or {}
260
+ if "ticket" in payload and isinstance(payload["ticket"], dict):
261
+ ticket_obj = payload["ticket"]
262
+ elif "key" in payload:
263
+ ticket_obj = payload
264
+ except Exception:
265
+ pass
266
+
267
+ return write_active_task_prompt(workspace_root, ticket_obj)