yncli 1.0.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.
- yncli/__init__.py +5 -0
- yncli/agent.py +277 -0
- yncli/built_in_skills/architect.md +10 -0
- yncli/built_in_skills/code_review.md +8 -0
- yncli/built_in_skills/debug_oracle.md +10 -0
- yncli/built_in_skills/debugger.md +11 -0
- yncli/built_in_skills/polyglot.md +17 -0
- yncli/built_in_skills/refactor_master.md +16 -0
- yncli/built_in_skills/security_auditor.md +18 -0
- yncli/built_in_skills/system_architect.md +10 -0
- yncli/built_in_skills/test_runner.md +14 -0
- yncli/built_in_skills/ultrabrain.md +21 -0
- yncli/clean_text.py +70 -0
- yncli/client.py +146 -0
- yncli/config.py +44 -0
- yncli/language_detector.py +206 -0
- yncli/main.py +137 -0
- yncli/skills_manager.py +61 -0
- yncli/system_prompt.py +102 -0
- yncli/tools/__init__.py +266 -0
- yncli/tools/file_tools.py +206 -0
- yncli/tools/polyglot_tools.py +133 -0
- yncli/tools/search_tools.py +68 -0
- yncli/tools/system_tools.py +140 -0
- yncli/tui.py +536 -0
- yncli/workspace_memory.py +97 -0
- yncli/workspace_scanner.py +99 -0
- yncli-1.0.0.dist-info/METADATA +63 -0
- yncli-1.0.0.dist-info/RECORD +32 -0
- yncli-1.0.0.dist-info/WHEEL +5 -0
- yncli-1.0.0.dist-info/entry_points.txt +2 -0
- yncli-1.0.0.dist-info/top_level.txt +1 -0
yncli/config.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
DEFAULT_BASE_URL = "https://eyay.afdaan.web.id/v1"
|
|
6
|
+
DEFAULT_API_KEY = "sk-8e3b65f406c3bd98-a0ejmb-62ab1e83"
|
|
7
|
+
DEFAULT_MODEL = "ag/gemini-3.7-flash-high"
|
|
8
|
+
DEFAULT_MODE = "build" # Modes: 'plan', 'build', 'ask'
|
|
9
|
+
|
|
10
|
+
CONFIG_DIR = Path.home() / ".yncli"
|
|
11
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_config() -> dict:
|
|
15
|
+
config = {
|
|
16
|
+
"base_url": os.getenv("YNCLI_BASE_URL", DEFAULT_BASE_URL),
|
|
17
|
+
"api_key": os.getenv("YNCLI_API_KEY", DEFAULT_API_KEY),
|
|
18
|
+
"model": os.getenv("YNCLI_MODEL", DEFAULT_MODEL),
|
|
19
|
+
"mode": os.getenv("YNCLI_MODE", DEFAULT_MODE),
|
|
20
|
+
"temperature": 0.2,
|
|
21
|
+
"max_tokens": 16384,
|
|
22
|
+
"auto_validate_syntax": True,
|
|
23
|
+
"show_thinking": False,
|
|
24
|
+
"auto_approve": False,
|
|
25
|
+
"theme": "minimal-dark",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if CONFIG_FILE.exists():
|
|
29
|
+
try:
|
|
30
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
31
|
+
saved = json.load(f)
|
|
32
|
+
config.update(saved)
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
return config
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def save_config(updates: dict) -> None:
|
|
40
|
+
config = load_config()
|
|
41
|
+
config.update(updates)
|
|
42
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
44
|
+
json.dump(config, f, indent=2)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
LANGUAGE_SIGNATURES = {
|
|
7
|
+
"python": {
|
|
8
|
+
"files": ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile", "poetry.lock", "tox.ini", ".flake8", "ruff.toml"],
|
|
9
|
+
"extensions": [".py", ".pyi", ".ipynb"],
|
|
10
|
+
"name": "Python",
|
|
11
|
+
"idioms": [
|
|
12
|
+
"Use PEP 8 naming conventions and explicit type hints (typing / PEP 484).",
|
|
13
|
+
"Prefer standard library and modern idioms (dataclasses, pydantic, path objects from pathlib).",
|
|
14
|
+
"Use async/await with asyncio when doing I/O-bound operations.",
|
|
15
|
+
"Write modular, testable functions with pytest docstrings and robust exception handling."
|
|
16
|
+
],
|
|
17
|
+
"syntax_command": "python -m py_compile {file}"
|
|
18
|
+
},
|
|
19
|
+
"typescript": {
|
|
20
|
+
"files": ["tsconfig.json", "package.json"],
|
|
21
|
+
"extensions": [".ts", ".tsx"],
|
|
22
|
+
"name": "TypeScript / JavaScript",
|
|
23
|
+
"idioms": [
|
|
24
|
+
"Use strict TypeScript types without implicit 'any'. Use type interfaces and generics where appropriate.",
|
|
25
|
+
"Prefer ES modules (import/export) and modern ES2022+ features (optional chaining, nullish coalescing).",
|
|
26
|
+
"Write clean functional or class-based components/services with comprehensive error boundaries.",
|
|
27
|
+
"Ensure proper promise rejection handling and async/await."
|
|
28
|
+
],
|
|
29
|
+
"syntax_command": "npx tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"javascript": {
|
|
32
|
+
"files": ["package.json", "jsconfig.json"],
|
|
33
|
+
"extensions": [".js", ".jsx", ".mjs", ".cjs"],
|
|
34
|
+
"name": "JavaScript (Node/Browser)",
|
|
35
|
+
"idioms": [
|
|
36
|
+
"Write modern ES6+ JavaScript with const/let, arrow functions, and destructuring.",
|
|
37
|
+
"Use modern async/await patterns and clean error handling.",
|
|
38
|
+
"Maintain clean separation of concerns and avoid global state pollution."
|
|
39
|
+
],
|
|
40
|
+
"syntax_command": "node --check {file}"
|
|
41
|
+
},
|
|
42
|
+
"rust": {
|
|
43
|
+
"files": ["Cargo.toml", "Cargo.lock"],
|
|
44
|
+
"extensions": [".rs"],
|
|
45
|
+
"name": "Rust",
|
|
46
|
+
"idioms": [
|
|
47
|
+
"Respect Rust ownership, borrowing rules, and lifetimes. Minimize unnecessary cloning.",
|
|
48
|
+
"Use idiomatic Result<T, E> and Option<T> pattern matching or the ? operator for error propagation.",
|
|
49
|
+
"Leverage traits, zero-cost abstractions, and cargo clippy conventions.",
|
|
50
|
+
"Write unit tests inside #[cfg(test)] modules."
|
|
51
|
+
],
|
|
52
|
+
"syntax_command": "cargo check"
|
|
53
|
+
},
|
|
54
|
+
"golang": {
|
|
55
|
+
"files": ["go.mod", "go.sum"],
|
|
56
|
+
"extensions": [".go"],
|
|
57
|
+
"name": "Go (Golang)",
|
|
58
|
+
"idioms": [
|
|
59
|
+
"Follow standard Go formatting (gofmt) and naming conventions (camelCase/PascalCase).",
|
|
60
|
+
"Handle errors explicitly with 'if err != nil' right after calls.",
|
|
61
|
+
"Use goroutines and channels carefully, preventing goroutine leaks with context.Context.",
|
|
62
|
+
"Keep packages focused and interfaces small and composable."
|
|
63
|
+
],
|
|
64
|
+
"syntax_command": "go vet ./..."
|
|
65
|
+
},
|
|
66
|
+
"cpp": {
|
|
67
|
+
"files": ["CMakeLists.txt", "Makefile", ".clang-format"],
|
|
68
|
+
"extensions": [".cpp", ".cc", ".cxx", ".hpp", ".h"],
|
|
69
|
+
"name": "C / C++",
|
|
70
|
+
"idioms": [
|
|
71
|
+
"Use modern C++ (C++17/20/23) idioms: RAII, smart pointers (std::unique_ptr, std::shared_ptr).",
|
|
72
|
+
"Avoid raw owning pointers and manual memory allocation (new/delete).",
|
|
73
|
+
"Use const references for non-primitive function arguments and constexpr for compile-time evaluation."
|
|
74
|
+
],
|
|
75
|
+
"syntax_command": "g++ -fsyntax-only {file}"
|
|
76
|
+
},
|
|
77
|
+
"csharp": {
|
|
78
|
+
"files": ["*.csproj", "*.sln", "NuGet.Config"],
|
|
79
|
+
"extensions": [".cs"],
|
|
80
|
+
"name": "C# (.NET)",
|
|
81
|
+
"idioms": [
|
|
82
|
+
"Use modern C# (C# 11/12) features: top-level statements, record types, pattern matching, nullable reference types.",
|
|
83
|
+
"Follow standard .NET PascalCase/camelCase naming conventions and Dependency Injection patterns.",
|
|
84
|
+
"Leverage async/await with Task and LINQ for declarative data querying."
|
|
85
|
+
],
|
|
86
|
+
"syntax_command": "dotnet build --no-incremental"
|
|
87
|
+
},
|
|
88
|
+
"java": {
|
|
89
|
+
"files": ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
90
|
+
"extensions": [".java", ".kt"],
|
|
91
|
+
"name": "Java / Kotlin",
|
|
92
|
+
"idioms": [
|
|
93
|
+
"Use modern Java (17/21) records, sealed classes, pattern matching, and streams.",
|
|
94
|
+
"Follow SOLID principles, clear package hierarchies, and Spring/Jakarta idioms if applicable.",
|
|
95
|
+
"Use Lombok or record classes to reduce boilerplate."
|
|
96
|
+
],
|
|
97
|
+
"syntax_command": "javac {file}"
|
|
98
|
+
},
|
|
99
|
+
"php": {
|
|
100
|
+
"files": ["composer.json", "composer.lock", "artisan"],
|
|
101
|
+
"extensions": [".php"],
|
|
102
|
+
"name": "PHP",
|
|
103
|
+
"idioms": [
|
|
104
|
+
"Always declare strict types: `declare(strict_types=1);` at the top of PHP files.",
|
|
105
|
+
"Use modern PHP 8.2+ features (enums, readonly properties, constructor property promotion, match expressions).",
|
|
106
|
+
"Follow PSR-12 coding standard and PSR-4 autoloading conventions."
|
|
107
|
+
],
|
|
108
|
+
"syntax_command": "php -l {file}"
|
|
109
|
+
},
|
|
110
|
+
"ruby": {
|
|
111
|
+
"files": ["Gemfile", "Rakefile", ".rubocop.yml"],
|
|
112
|
+
"extensions": [".rb", ".erb"],
|
|
113
|
+
"name": "Ruby / Rails",
|
|
114
|
+
"idioms": [
|
|
115
|
+
"Follow Ruby style guide: 2 spaces indentation, snake_case for methods, CamelCase for classes.",
|
|
116
|
+
"Use idiomatic blocks, iterators, and enumerable methods.",
|
|
117
|
+
"Embrace DRY and convention over configuration."
|
|
118
|
+
],
|
|
119
|
+
"syntax_command": "ruby -c {file}"
|
|
120
|
+
},
|
|
121
|
+
"dart": {
|
|
122
|
+
"files": ["pubspec.yaml", "pubspec.lock"],
|
|
123
|
+
"extensions": [".dart"],
|
|
124
|
+
"name": "Dart / Flutter",
|
|
125
|
+
"idioms": [
|
|
126
|
+
"Follow sound null safety rules and effective Dart style guidelines.",
|
|
127
|
+
"Structure Flutter widgets into clean, composable sub-widgets.",
|
|
128
|
+
"Use proper state management patterns (Riverpod, Bloc, Provider)."
|
|
129
|
+
],
|
|
130
|
+
"syntax_command": "dart analyze"
|
|
131
|
+
},
|
|
132
|
+
"shell": {
|
|
133
|
+
"files": [],
|
|
134
|
+
"extensions": [".sh", ".bash", ".zsh", ".ps1", ".bat", ".cmd"],
|
|
135
|
+
"name": "Shell / PowerShell",
|
|
136
|
+
"idioms": [
|
|
137
|
+
"For bash: use `set -euo pipefail` and double quote all variable expansions.",
|
|
138
|
+
"For PowerShell: use `$ErrorActionPreference = 'Stop'` and standard Verb-Noun cmdlet naming.",
|
|
139
|
+
"Handle paths with spaces and check exit codes for all external tool invocations."
|
|
140
|
+
],
|
|
141
|
+
"syntax_command": None
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def detect_workspace_languages(workspace_dir: str = ".") -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Scans the workspace directory and identifies detected languages, frameworks, and manifests.
|
|
149
|
+
"""
|
|
150
|
+
workspace_path = Path(workspace_dir).resolve()
|
|
151
|
+
detected = []
|
|
152
|
+
file_counts = {}
|
|
153
|
+
found_manifests = []
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Check files in root and first depth
|
|
157
|
+
for root, dirs, files in os.walk(workspace_path):
|
|
158
|
+
# Skip hidden, node_modules, git, venv, cache dirs
|
|
159
|
+
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ("node_modules", "venv", "__pycache__", "target", "vendor", "dist", "build", ".git")]
|
|
160
|
+
|
|
161
|
+
for file in files:
|
|
162
|
+
ext = Path(file).suffix.lower()
|
|
163
|
+
file_counts[ext] = file_counts.get(ext, 0) + 1
|
|
164
|
+
|
|
165
|
+
# Check root manifests
|
|
166
|
+
if Path(root) == workspace_path:
|
|
167
|
+
for lang_key, data in LANGUAGE_SIGNATURES.items():
|
|
168
|
+
for manifest in data["files"]:
|
|
169
|
+
if manifest.startswith("*") and file.endswith(manifest[1:]):
|
|
170
|
+
found_manifests.append((lang_key, file))
|
|
171
|
+
elif file.lower() == manifest.lower():
|
|
172
|
+
found_manifests.append((lang_key, file))
|
|
173
|
+
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# Score languages
|
|
178
|
+
scored_languages = {}
|
|
179
|
+
for lang_key, data in LANGUAGE_SIGNATURES.items():
|
|
180
|
+
score = 0
|
|
181
|
+
# Manifest matches have high score
|
|
182
|
+
for m_lang, m_file in found_manifests:
|
|
183
|
+
if m_lang == lang_key:
|
|
184
|
+
score += 50
|
|
185
|
+
|
|
186
|
+
# File extension matches
|
|
187
|
+
for ext in data["extensions"]:
|
|
188
|
+
count = file_counts.get(ext, 0)
|
|
189
|
+
score += count * 5
|
|
190
|
+
|
|
191
|
+
if score > 0:
|
|
192
|
+
scored_languages[lang_key] = {
|
|
193
|
+
"name": data["name"],
|
|
194
|
+
"score": score,
|
|
195
|
+
"idioms": data["idioms"],
|
|
196
|
+
"syntax_command": data["syntax_command"]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
sorted_langs = sorted(scored_languages.items(), key=lambda x: x[1]["score"], reverse=True)
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
"primary_language": sorted_langs[0][0] if sorted_langs else "general",
|
|
203
|
+
"detected_languages": [item[1]["name"] for item in sorted_langs],
|
|
204
|
+
"details": scored_languages,
|
|
205
|
+
"manifests": [f"{m[1]} ({m[0]})" for m in found_manifests]
|
|
206
|
+
}
|
yncli/main.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Ensure UTF-8 output encoding across Windows consoles
|
|
7
|
+
if sys.platform == "win32":
|
|
8
|
+
try:
|
|
9
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
10
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
11
|
+
except Exception:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
from yncli.config import load_config, save_config
|
|
15
|
+
from yncli.client import LLMClient
|
|
16
|
+
from yncli.agent import Agent
|
|
17
|
+
from yncli.tui import TerminalUI
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="yncli",
|
|
23
|
+
description="[YNCLI] Autonomous Polyglot AI Coding Agent & TUI"
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"prompt",
|
|
27
|
+
nargs="*",
|
|
28
|
+
help="Optional prompt to execute in quick one-shot mode. If omitted, starts interactive TUI."
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-m", "--model",
|
|
32
|
+
type=str,
|
|
33
|
+
help="Specify the AI model to use (e.g. ag/gemini-3.7-flash-high, ag/claude-sonnet-4-6)"
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--mode",
|
|
37
|
+
type=str,
|
|
38
|
+
choices=["plan", "build", "ask"],
|
|
39
|
+
help="Set operating mode: 'plan' (architect/planner), 'build' (autonomous coding), 'ask' (Q&A/consult)"
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"-s", "--skill",
|
|
43
|
+
type=str,
|
|
44
|
+
help="Activate a specialized skill mode (ultrabrain, system_architect, debug_oracle, security_auditor, refactor_master, polyglot)"
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"-w", "--workspace",
|
|
48
|
+
type=str,
|
|
49
|
+
default=".",
|
|
50
|
+
help="Workspace directory path (default: current directory)"
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--show-thinking",
|
|
54
|
+
action="store_true",
|
|
55
|
+
help="Display live thinking/reasoning stream"
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--hide-thinking",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="Hide live thinking/reasoning stream"
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--auto-approve",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="Automatically approve and execute all tool actions without asking"
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--ask-approve",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Ask user confirmation before executing any tool action"
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--list-models",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="List available models and exit"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
args = parser.parse_args()
|
|
79
|
+
config = load_config()
|
|
80
|
+
|
|
81
|
+
base_url = config.get("base_url")
|
|
82
|
+
api_key = config.get("api_key")
|
|
83
|
+
model = args.model or config.get("model")
|
|
84
|
+
mode = args.mode or config.get("mode", "build")
|
|
85
|
+
|
|
86
|
+
client = LLMClient(base_url=base_url, api_key=api_key)
|
|
87
|
+
|
|
88
|
+
if args.list_models:
|
|
89
|
+
print(f"[INFO] Connecting to {base_url}...")
|
|
90
|
+
models = client.list_models()
|
|
91
|
+
if not models:
|
|
92
|
+
print("[ERROR] Failed to fetch models or no models returned.")
|
|
93
|
+
else:
|
|
94
|
+
print(f"[INFO] Available Models ({len(models)}):")
|
|
95
|
+
for m in models:
|
|
96
|
+
print(f" - {m.get('id')} ({m.get('owned_by')})")
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
|
|
99
|
+
# Determine thinking visibility
|
|
100
|
+
show_thinking = config.get("show_thinking", False)
|
|
101
|
+
if args.show_thinking:
|
|
102
|
+
show_thinking = True
|
|
103
|
+
elif args.hide_thinking:
|
|
104
|
+
show_thinking = False
|
|
105
|
+
|
|
106
|
+
# Determine approval mode
|
|
107
|
+
auto_approve = config.get("auto_approve", False)
|
|
108
|
+
if args.auto_approve:
|
|
109
|
+
auto_approve = True
|
|
110
|
+
elif args.ask_approve:
|
|
111
|
+
auto_approve = False
|
|
112
|
+
|
|
113
|
+
agent = Agent(
|
|
114
|
+
client=client,
|
|
115
|
+
model=model,
|
|
116
|
+
workspace_dir=args.workspace,
|
|
117
|
+
mode=mode,
|
|
118
|
+
auto_approve=auto_approve,
|
|
119
|
+
show_thinking=show_thinking
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if args.skill:
|
|
123
|
+
agent.skills_mgr.set_active_skill(args.skill)
|
|
124
|
+
else:
|
|
125
|
+
agent.skills_mgr.set_active_skill("ultrabrain")
|
|
126
|
+
|
|
127
|
+
tui = TerminalUI(agent=agent)
|
|
128
|
+
|
|
129
|
+
if args.prompt:
|
|
130
|
+
query = " ".join(args.prompt).strip()
|
|
131
|
+
tui.execute_turn(query)
|
|
132
|
+
else:
|
|
133
|
+
tui.run_interactive_loop()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
yncli/skills_manager.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
PACKAGE_SKILLS_DIR = Path(__file__).parent / "built_in_skills"
|
|
6
|
+
ROOT_SKILLS_DIR = Path(__file__).parent.parent / "built_in_skills"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SkillsManager:
|
|
10
|
+
def __init__(self, workspace_dir: str = "."):
|
|
11
|
+
self.workspace_dir = Path(workspace_dir).resolve()
|
|
12
|
+
self.user_skills_dir = self.workspace_dir / ".skills"
|
|
13
|
+
self.active_skill: Optional[str] = "ultrabrain"
|
|
14
|
+
self._skills_cache: Dict[str, str] = {}
|
|
15
|
+
self.load_skills()
|
|
16
|
+
|
|
17
|
+
def load_skills(self) -> None:
|
|
18
|
+
self._skills_cache.clear()
|
|
19
|
+
|
|
20
|
+
# 1. Load built-in skills from package directory or root
|
|
21
|
+
skills_dirs = [PACKAGE_SKILLS_DIR, ROOT_SKILLS_DIR]
|
|
22
|
+
for sdir in skills_dirs:
|
|
23
|
+
if sdir.exists():
|
|
24
|
+
for file in sdir.glob("*.md"):
|
|
25
|
+
skill_name = file.stem.lower()
|
|
26
|
+
if skill_name not in self._skills_cache:
|
|
27
|
+
try:
|
|
28
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
29
|
+
self._skills_cache[skill_name] = f.read().strip()
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# 2. Load user workspace custom skills (.skills/*.md)
|
|
34
|
+
if self.user_skills_dir.exists():
|
|
35
|
+
for file in self.user_skills_dir.glob("*.md"):
|
|
36
|
+
skill_name = file.stem.lower()
|
|
37
|
+
try:
|
|
38
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
39
|
+
self._skills_cache[skill_name] = f.read().strip()
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def list_skills(self) -> List[str]:
|
|
44
|
+
return sorted(list(self._skills_cache.keys()))
|
|
45
|
+
|
|
46
|
+
def get_skill_content(self, skill_name: str) -> Optional[str]:
|
|
47
|
+
return self._skills_cache.get(skill_name.lower())
|
|
48
|
+
|
|
49
|
+
def set_active_skill(self, skill_name: str) -> bool:
|
|
50
|
+
if skill_name.lower() in self._skills_cache:
|
|
51
|
+
self.active_skill = skill_name.lower()
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def get_active_skill_prompt(self) -> str:
|
|
56
|
+
if not self.active_skill:
|
|
57
|
+
return ""
|
|
58
|
+
content = self._skills_cache.get(self.active_skill, "")
|
|
59
|
+
if content:
|
|
60
|
+
return f"\n\n--- ACTIVE SKILL MODE: [{self.active_skill.upper()}] ---\n{content}\n"
|
|
61
|
+
return ""
|
yncli/system_prompt.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from yncli.tools.system_tools import get_system_info
|
|
3
|
+
from yncli.language_detector import detect_workspace_languages
|
|
4
|
+
from yncli.skills_manager import SkillsManager
|
|
5
|
+
from yncli.workspace_memory import WorkspaceMemory
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_system_prompt(workspace_dir: str = ".", skills_mgr: SkillsManager = None, mode: str = "build", memory: WorkspaceMemory = None) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Builds a dynamic, date-aware, mode-aware polyglot system prompt with full pre-loaded project source files.
|
|
11
|
+
"""
|
|
12
|
+
sys_info = get_system_info(workspace_dir)
|
|
13
|
+
lang_info = detect_workspace_languages(workspace_dir)
|
|
14
|
+
skill_prompt = skills_mgr.get_active_skill_prompt() if skills_mgr else ""
|
|
15
|
+
|
|
16
|
+
if memory is None:
|
|
17
|
+
memory = WorkspaceMemory(workspace_dir)
|
|
18
|
+
workspace_context = memory.get_context_for_prompt()
|
|
19
|
+
|
|
20
|
+
primary_lang = lang_info["primary_language"]
|
|
21
|
+
detected_langs = ", ".join(lang_info["detected_languages"]) if lang_info["detected_languages"] else "None (General)"
|
|
22
|
+
manifests = ", ".join(lang_info["manifests"]) if lang_info["manifests"] else "None"
|
|
23
|
+
|
|
24
|
+
idioms_text = []
|
|
25
|
+
for l_key, data in lang_info["details"].items():
|
|
26
|
+
idioms_text.append(f"### {data['name']} Best Practices:")
|
|
27
|
+
for idiom in data["idioms"]:
|
|
28
|
+
idioms_text.append(f"- {idiom}")
|
|
29
|
+
|
|
30
|
+
idioms_block = "\n".join(idioms_text) if idioms_text else "- Follow standard clean code, SOLID, and YAGNI principles."
|
|
31
|
+
|
|
32
|
+
if mode.lower() == "plan":
|
|
33
|
+
mode_instruction = """## OPERATING MODE: [PLAN MODE - ULTRABRAIN PRODUCT & ARCHITECTURE PLANNER]
|
|
34
|
+
- You are an elite Principal Software Architect and Senior Product Manager.
|
|
35
|
+
- Your primary mission in PLAN MODE:
|
|
36
|
+
1. Deeply investigate existing workspace files using `read_file`, `list_directory`, `grep_search`, and `find_files`.
|
|
37
|
+
2. Synthesize all user requirements and architect an ultra-smart, production-grade **PRD (Product Requirements Document)**.
|
|
38
|
+
3. You MUST write the complete PRD into a file named `plan.md` in the workspace directory using the `save_plan_document` tool.
|
|
39
|
+
- The `plan.md` PRD MUST contain the following comprehensive sections:
|
|
40
|
+
- **1. Project Overview & Problem Statement**: Clear goals, scope, and target architecture.
|
|
41
|
+
- **2. Technical Stack & Architecture**: Frameworks, design patterns, dependencies, directory structure.
|
|
42
|
+
- **3. UI/UX Wireframes & ASCII Visual Mockups**: High-fidelity ASCII/text layouts of every screen, component hierarchy, color schemes, typography, and interactive behaviors.
|
|
43
|
+
- **4. Database & Data Models**: Table schemas, fields, types, relationships (ERD), migrations.
|
|
44
|
+
- **5. Step-by-Step Implementation Roadmap**: Ordered checklist of files to create/modify with exact specifications.
|
|
45
|
+
- **6. Verification & Test Strategy**: Unit tests, integration tests, edge-case coverage.
|
|
46
|
+
- After saving `plan.md`, provide a concise executive summary in your response. The system will prompt the user to transition to BUILD mode."""
|
|
47
|
+
elif mode.lower() == "ask":
|
|
48
|
+
mode_instruction = """## OPERATING MODE: [ASK MODE]
|
|
49
|
+
- You are a Senior Technical Consultant and Pair Programmer.
|
|
50
|
+
- You CANNOT write code files or execute terminal commands.
|
|
51
|
+
- Focus on answering questions, explaining architecture, searching documentation, and providing clear explanations.
|
|
52
|
+
- If the user wants to implement code, advise them to switch to BUILD mode using `/build` or PLAN mode using `/plan`."""
|
|
53
|
+
else: # 'build'
|
|
54
|
+
mode_instruction = """## OPERATING MODE: [BUILD MODE - AUTONOMOUS CODE EXECUTION]
|
|
55
|
+
- You are an elite, proactive autonomous Software Engineer in BUILD MODE.
|
|
56
|
+
- You have FULL AUTHORITY to create, modify, and update code files directly using `write_file` and `edit_file_replace`.
|
|
57
|
+
- **CRITICAL ANTI-STALLING RULES**:
|
|
58
|
+
1. All files and their contents are ALREADY LOADED below.
|
|
59
|
+
2. **NEVER ASK STALLING QUESTIONS** like:
|
|
60
|
+
- "Di mana letak web Anda?"
|
|
61
|
+
- "Teknologi apa yang digunakan?"
|
|
62
|
+
- "Bisa jelaskan apa yang ingin di-upgrade?"
|
|
63
|
+
- "Apakah proyeknya sudah ada di workspace ini?"
|
|
64
|
+
3. When the user says "benerin web saya", "upgrade web", "buat fitur", or "fix bug":
|
|
65
|
+
- Look at the pre-loaded files below (`index.html`, `style.css`, `script.js`, etc.).
|
|
66
|
+
- IMMEDIATELY call `write_file` or `edit_file_replace` to apply modern UI, upgraded styling, new features, and bug fixes directly.
|
|
67
|
+
- Never return only conversational text when an action is requested. Always take action!"""
|
|
68
|
+
|
|
69
|
+
prompt = f"""You are **YNCLI Agent**, an elite, autonomous Polyglot AI Coding Assistant and Senior Software Engineer.
|
|
70
|
+
|
|
71
|
+
## STRICT FORMATTING RULE
|
|
72
|
+
- **NEVER USE EMOJIS OR UNICODE EMOJI PICTOGRAPHS**. Do not use emoji characters in your answers. Use clean text markers like [INFO], [SUCCESS], [WARNING], 1., 2., -.
|
|
73
|
+
|
|
74
|
+
## SYSTEM ENVIRONMENT & REAL-TIME CONTEXT
|
|
75
|
+
- **Current Date & Time**: {sys_info['datetime']} (ISO: {sys_info['date_iso']})
|
|
76
|
+
- **Operating System**: {sys_info['os']}
|
|
77
|
+
- **Default Shell**: {sys_info['shell']}
|
|
78
|
+
- **Workspace Directory**: {sys_info['cwd']}
|
|
79
|
+
- **User**: {sys_info['user']}
|
|
80
|
+
- **Detected Workspace Stack**: {detected_langs}
|
|
81
|
+
- **Detected Manifests**: {manifests}
|
|
82
|
+
- **Primary Language Focus**: {primary_lang.upper()}
|
|
83
|
+
|
|
84
|
+
## PRE-LOADED PROJECT MEMORY & SOURCE CODE (ACTIVE DIRECTORY SNAPSHOT)
|
|
85
|
+
{workspace_context}
|
|
86
|
+
|
|
87
|
+
{mode_instruction}
|
|
88
|
+
|
|
89
|
+
## LANGUAGE-SPECIFIC IDIOMS & QUALITY GUIDELINES
|
|
90
|
+
{idioms_block}
|
|
91
|
+
|
|
92
|
+
## CORE CAPABILITIES & OPERATING PRINCIPLES
|
|
93
|
+
1. **Direct Action**: You already have the source code of the project in memory. Act immediately.
|
|
94
|
+
2. **Autonomous Tool Loop (ReAct)**:
|
|
95
|
+
- In BUILD mode, modify and upgrade code files directly using `write_file` or `edit_file_replace`.
|
|
96
|
+
- In PLAN mode, always save the comprehensive PRD with ASCII mockups to `plan.md`.
|
|
97
|
+
3. **Polyglot Code Quality**:
|
|
98
|
+
- Write clean, modular, production-ready code.
|
|
99
|
+
- Adhere strictly to the idioms of the target language.
|
|
100
|
+
{skill_prompt}
|
|
101
|
+
"""
|
|
102
|
+
return prompt.strip()
|