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.
- rakit_cli-2.0.1/.gitignore +59 -0
- rakit_cli-2.0.1/PKG-INFO +108 -0
- rakit_cli-2.0.1/README.md +88 -0
- rakit_cli-2.0.1/pyproject.toml +55 -0
- rakit_cli-2.0.1/src/rakit_cli/__init__.py +3 -0
- rakit_cli-2.0.1/src/rakit_cli/branding.py +21 -0
- rakit_cli-2.0.1/src/rakit_cli/config.py +267 -0
- rakit_cli-2.0.1/src/rakit_cli/main.py +1397 -0
- rakit_cli-2.0.1/src/rakit_cli/mcp_server.py +325 -0
- rakit_cli-2.0.1/src/rakit_cli/security.py +90 -0
- rakit_cli-2.0.1/src/rakit_cli/skill_installer.py +218 -0
- rakit_cli-2.0.1/src/rakit_cli/tasks_parser.py +59 -0
- rakit_cli-2.0.1/src/rakit_cli/templates/SKILL.md +125 -0
- rakit_cli-2.0.1/tests/test_cli_and_mcp.py +465 -0
- rakit_cli-2.0.1/tests/test_security.py +31 -0
- rakit_cli-2.0.1/tests/test_tasks_parser.py +21 -0
- rakit_cli-2.0.1/uv.lock +235 -0
|
@@ -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
|
rakit_cli-2.0.1/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/rakit-cli/)
|
|
24
|
+
[](https://www.python.org/downloads/)
|
|
25
|
+
[](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
|
+
[](https://pypi.org/project/rakit-cli/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](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,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)
|