zrb 1.21.29__py3-none-any.whl → 2.0.0a4__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.

Potentially problematic release.


This version of zrb might be problematic. Click here for more details.

Files changed (192) hide show
  1. zrb/__init__.py +118 -129
  2. zrb/builtin/__init__.py +54 -2
  3. zrb/builtin/llm/chat.py +147 -0
  4. zrb/callback/callback.py +8 -1
  5. zrb/cmd/cmd_result.py +2 -1
  6. zrb/config/config.py +491 -280
  7. zrb/config/helper.py +84 -0
  8. zrb/config/web_auth_config.py +50 -35
  9. zrb/context/any_shared_context.py +13 -2
  10. zrb/context/context.py +31 -3
  11. zrb/context/print_fn.py +13 -0
  12. zrb/context/shared_context.py +14 -1
  13. zrb/input/option_input.py +30 -2
  14. zrb/llm/agent/__init__.py +9 -0
  15. zrb/llm/agent/agent.py +215 -0
  16. zrb/llm/agent/summarizer.py +20 -0
  17. zrb/llm/app/__init__.py +10 -0
  18. zrb/llm/app/completion.py +281 -0
  19. zrb/llm/app/confirmation/allow_tool.py +66 -0
  20. zrb/llm/app/confirmation/handler.py +178 -0
  21. zrb/llm/app/confirmation/replace_confirmation.py +77 -0
  22. zrb/llm/app/keybinding.py +34 -0
  23. zrb/llm/app/layout.py +117 -0
  24. zrb/llm/app/lexer.py +155 -0
  25. zrb/llm/app/redirection.py +28 -0
  26. zrb/llm/app/style.py +16 -0
  27. zrb/llm/app/ui.py +733 -0
  28. zrb/llm/config/__init__.py +4 -0
  29. zrb/llm/config/config.py +122 -0
  30. zrb/llm/config/limiter.py +247 -0
  31. zrb/llm/history_manager/__init__.py +4 -0
  32. zrb/llm/history_manager/any_history_manager.py +23 -0
  33. zrb/llm/history_manager/file_history_manager.py +91 -0
  34. zrb/llm/history_processor/summarizer.py +108 -0
  35. zrb/llm/note/__init__.py +3 -0
  36. zrb/llm/note/manager.py +122 -0
  37. zrb/llm/prompt/__init__.py +29 -0
  38. zrb/llm/prompt/claude_compatibility.py +92 -0
  39. zrb/llm/prompt/compose.py +55 -0
  40. zrb/llm/prompt/default.py +51 -0
  41. zrb/llm/prompt/markdown/mandate.md +23 -0
  42. zrb/llm/prompt/markdown/persona.md +3 -0
  43. zrb/llm/prompt/markdown/summarizer.md +21 -0
  44. zrb/llm/prompt/note.py +41 -0
  45. zrb/llm/prompt/system_context.py +46 -0
  46. zrb/llm/prompt/zrb.py +41 -0
  47. zrb/llm/skill/__init__.py +3 -0
  48. zrb/llm/skill/manager.py +86 -0
  49. zrb/llm/task/__init__.py +4 -0
  50. zrb/llm/task/llm_chat_task.py +316 -0
  51. zrb/llm/task/llm_task.py +245 -0
  52. zrb/llm/tool/__init__.py +39 -0
  53. zrb/llm/tool/bash.py +75 -0
  54. zrb/llm/tool/code.py +266 -0
  55. zrb/llm/tool/file.py +419 -0
  56. zrb/llm/tool/note.py +70 -0
  57. zrb/{builtin/llm → llm}/tool/rag.py +8 -5
  58. zrb/llm/tool/search/brave.py +53 -0
  59. zrb/llm/tool/search/searxng.py +47 -0
  60. zrb/llm/tool/search/serpapi.py +47 -0
  61. zrb/llm/tool/skill.py +19 -0
  62. zrb/llm/tool/sub_agent.py +70 -0
  63. zrb/llm/tool/web.py +97 -0
  64. zrb/llm/tool/zrb_task.py +66 -0
  65. zrb/llm/util/attachment.py +101 -0
  66. zrb/llm/util/prompt.py +104 -0
  67. zrb/llm/util/stream_response.py +178 -0
  68. zrb/session/any_session.py +0 -3
  69. zrb/session/session.py +1 -1
  70. zrb/task/base/context.py +25 -13
  71. zrb/task/base/execution.py +52 -47
  72. zrb/task/base/lifecycle.py +7 -4
  73. zrb/task/base_task.py +48 -49
  74. zrb/task/base_trigger.py +4 -1
  75. zrb/task/cmd_task.py +6 -0
  76. zrb/task/http_check.py +11 -5
  77. zrb/task/make_task.py +3 -0
  78. zrb/task/rsync_task.py +5 -0
  79. zrb/task/scaffolder.py +7 -4
  80. zrb/task/scheduler.py +3 -0
  81. zrb/task/tcp_check.py +6 -4
  82. zrb/util/ascii_art/art/bee.txt +17 -0
  83. zrb/util/ascii_art/art/cat.txt +9 -0
  84. zrb/util/ascii_art/art/ghost.txt +16 -0
  85. zrb/util/ascii_art/art/panda.txt +17 -0
  86. zrb/util/ascii_art/art/rose.txt +14 -0
  87. zrb/util/ascii_art/art/unicorn.txt +15 -0
  88. zrb/util/ascii_art/banner.py +92 -0
  89. zrb/util/cli/markdown.py +22 -2
  90. zrb/util/cmd/command.py +33 -10
  91. zrb/util/file.py +51 -32
  92. zrb/util/match.py +78 -0
  93. zrb/util/run.py +3 -3
  94. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/METADATA +9 -15
  95. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/RECORD +100 -128
  96. zrb/attr/__init__.py +0 -0
  97. zrb/builtin/llm/attachment.py +0 -40
  98. zrb/builtin/llm/chat_completion.py +0 -274
  99. zrb/builtin/llm/chat_session.py +0 -270
  100. zrb/builtin/llm/chat_session_cmd.py +0 -288
  101. zrb/builtin/llm/chat_trigger.py +0 -79
  102. zrb/builtin/llm/history.py +0 -71
  103. zrb/builtin/llm/input.py +0 -27
  104. zrb/builtin/llm/llm_ask.py +0 -269
  105. zrb/builtin/llm/previous-session.js +0 -21
  106. zrb/builtin/llm/tool/__init__.py +0 -0
  107. zrb/builtin/llm/tool/api.py +0 -75
  108. zrb/builtin/llm/tool/cli.py +0 -52
  109. zrb/builtin/llm/tool/code.py +0 -236
  110. zrb/builtin/llm/tool/file.py +0 -560
  111. zrb/builtin/llm/tool/note.py +0 -84
  112. zrb/builtin/llm/tool/sub_agent.py +0 -150
  113. zrb/builtin/llm/tool/web.py +0 -171
  114. zrb/builtin/project/__init__.py +0 -0
  115. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
  116. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
  117. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
  118. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
  119. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
  120. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
  121. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
  122. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
  123. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
  124. zrb/builtin/project/create/__init__.py +0 -0
  125. zrb/builtin/shell/__init__.py +0 -0
  126. zrb/builtin/shell/autocomplete/__init__.py +0 -0
  127. zrb/callback/__init__.py +0 -0
  128. zrb/cmd/__init__.py +0 -0
  129. zrb/config/default_prompt/interactive_system_prompt.md +0 -29
  130. zrb/config/default_prompt/persona.md +0 -1
  131. zrb/config/default_prompt/summarization_prompt.md +0 -57
  132. zrb/config/default_prompt/system_prompt.md +0 -38
  133. zrb/config/llm_config.py +0 -339
  134. zrb/config/llm_context/config.py +0 -166
  135. zrb/config/llm_context/config_parser.py +0 -40
  136. zrb/config/llm_context/workflow.py +0 -81
  137. zrb/config/llm_rate_limitter.py +0 -190
  138. zrb/content_transformer/__init__.py +0 -0
  139. zrb/context/__init__.py +0 -0
  140. zrb/dot_dict/__init__.py +0 -0
  141. zrb/env/__init__.py +0 -0
  142. zrb/group/__init__.py +0 -0
  143. zrb/input/__init__.py +0 -0
  144. zrb/runner/__init__.py +0 -0
  145. zrb/runner/web_route/__init__.py +0 -0
  146. zrb/runner/web_route/home_page/__init__.py +0 -0
  147. zrb/session/__init__.py +0 -0
  148. zrb/session_state_log/__init__.py +0 -0
  149. zrb/session_state_logger/__init__.py +0 -0
  150. zrb/task/__init__.py +0 -0
  151. zrb/task/base/__init__.py +0 -0
  152. zrb/task/llm/__init__.py +0 -0
  153. zrb/task/llm/agent.py +0 -204
  154. zrb/task/llm/agent_runner.py +0 -152
  155. zrb/task/llm/config.py +0 -122
  156. zrb/task/llm/conversation_history.py +0 -209
  157. zrb/task/llm/conversation_history_model.py +0 -67
  158. zrb/task/llm/default_workflow/coding/workflow.md +0 -41
  159. zrb/task/llm/default_workflow/copywriting/workflow.md +0 -68
  160. zrb/task/llm/default_workflow/git/workflow.md +0 -118
  161. zrb/task/llm/default_workflow/golang/workflow.md +0 -128
  162. zrb/task/llm/default_workflow/html-css/workflow.md +0 -135
  163. zrb/task/llm/default_workflow/java/workflow.md +0 -146
  164. zrb/task/llm/default_workflow/javascript/workflow.md +0 -158
  165. zrb/task/llm/default_workflow/python/workflow.md +0 -160
  166. zrb/task/llm/default_workflow/researching/workflow.md +0 -153
  167. zrb/task/llm/default_workflow/rust/workflow.md +0 -162
  168. zrb/task/llm/default_workflow/shell/workflow.md +0 -299
  169. zrb/task/llm/error.py +0 -95
  170. zrb/task/llm/file_replacement.py +0 -206
  171. zrb/task/llm/file_tool_model.py +0 -57
  172. zrb/task/llm/history_processor.py +0 -206
  173. zrb/task/llm/history_summarization.py +0 -25
  174. zrb/task/llm/print_node.py +0 -221
  175. zrb/task/llm/prompt.py +0 -321
  176. zrb/task/llm/subagent_conversation_history.py +0 -41
  177. zrb/task/llm/tool_wrapper.py +0 -361
  178. zrb/task/llm/typing.py +0 -3
  179. zrb/task/llm/workflow.py +0 -76
  180. zrb/task/llm_task.py +0 -379
  181. zrb/task_status/__init__.py +0 -0
  182. zrb/util/__init__.py +0 -0
  183. zrb/util/cli/__init__.py +0 -0
  184. zrb/util/cmd/__init__.py +0 -0
  185. zrb/util/codemod/__init__.py +0 -0
  186. zrb/util/string/__init__.py +0 -0
  187. zrb/xcom/__init__.py +0 -0
  188. /zrb/{config/default_prompt/file_extractor_system_prompt.md → llm/prompt/markdown/file_extractor.md} +0 -0
  189. /zrb/{config/default_prompt/repo_extractor_system_prompt.md → llm/prompt/markdown/repo_extractor.md} +0 -0
  190. /zrb/{config/default_prompt/repo_summarizer_system_prompt.md → llm/prompt/markdown/repo_summarizer.md} +0 -0
  191. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +0 -0
  192. {zrb-1.21.29.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,122 @@
1
+ import json
2
+ import os
3
+ from typing import Dict
4
+
5
+ from zrb.config.config import CFG
6
+
7
+
8
+ class NoteManager:
9
+ def __init__(self, context_file: str = CFG.LLM_NOTE_FILE):
10
+ self.context_file = os.path.abspath(os.path.expanduser(context_file))
11
+
12
+ def _get_normalized_path(self, path: str) -> str:
13
+ """
14
+ Convert path to absolute path, then try to make it relative to home.
15
+ If it's inside home, return '~/rel/path'.
16
+ Otherwise return absolute path.
17
+ """
18
+ abs_path = os.path.abspath(os.path.expanduser(path))
19
+ home = os.path.expanduser("~")
20
+
21
+ if abs_path.startswith(home):
22
+ rel_path = os.path.relpath(abs_path, home)
23
+ if rel_path == ".":
24
+ return "~"
25
+ return os.path.join("~", rel_path)
26
+
27
+ return abs_path
28
+
29
+ def _load_data(self) -> Dict[str, str]:
30
+ if not os.path.exists(self.context_file):
31
+ return {}
32
+ try:
33
+ with open(self.context_file, "r", encoding="utf-8") as f:
34
+ return json.load(f)
35
+ except (json.JSONDecodeError, OSError):
36
+ return {}
37
+
38
+ def _save_data(self, data: Dict[str, str]):
39
+ os.makedirs(os.path.dirname(self.context_file), exist_ok=True)
40
+ with open(self.context_file, "w", encoding="utf-8") as f:
41
+ json.dump(data, f, indent=2, sort_keys=True)
42
+
43
+ def write(self, context_path: str, content: str):
44
+ """
45
+ Write content to context file for a specific path.
46
+ Turn context path to be relative to home directory (unless it is outside home directory).
47
+ """
48
+ key = self._get_normalized_path(context_path)
49
+ data = self._load_data()
50
+ data[key] = content
51
+ self._save_data(data)
52
+
53
+ def read(self, context_path: str) -> str:
54
+ """
55
+ Seek for any key in context file that match context path.
56
+ """
57
+ key = self._get_normalized_path(context_path)
58
+ data = self._load_data()
59
+ return data.get(key, "")
60
+
61
+ def read_all(self, context_path: str) -> Dict[str, str]:
62
+ """
63
+ Seek for all key in context file that match context path or any of its parent, return as key-value.
64
+ """
65
+ target_path = self._get_normalized_path(context_path)
66
+ data = self._load_data()
67
+
68
+ result = {}
69
+
70
+ # Check for root/home notes (~)
71
+ if "~" in data:
72
+ result["~"] = data["~"]
73
+
74
+ # If target_path is just "~", we are done
75
+ if target_path == "~":
76
+ return result
77
+
78
+ # We need to find all parents of target_path that are in data
79
+ # Example target: ~/zrb/src
80
+ # Parents: ~, ~/zrb
81
+
82
+ # Split the path parts
83
+ # If path starts with "~", we treat it specially
84
+
85
+ parts = []
86
+ if target_path.startswith("~" + os.sep) or target_path == "~":
87
+ # Remove ~
88
+ rel_part = target_path[2:] # "zrb/src"
89
+ parts = rel_part.split(os.sep)
90
+ current = "~"
91
+ else:
92
+ # Absolute path like /opt/project
93
+ parts = target_path.split(os.sep)
94
+ # parts for /opt/project -> ['', 'opt', 'project']
95
+ current = parts[0] # '' (root)
96
+ if current == "":
97
+ current = "/"
98
+ parts = parts[1:]
99
+
100
+ # Iterate and build up path
101
+ for part in parts:
102
+ if current == "~":
103
+ current = os.path.join("~", part)
104
+ elif current == "/":
105
+ current = os.path.join("/", part)
106
+ else:
107
+ current = os.path.join(current, part)
108
+
109
+ # Normalize just in case os.path.join changes things
110
+ # But wait, our keys in JSON are stored with separators.
111
+ # We should probably reconstruct the key carefully.
112
+
113
+ # Let's simplify:
114
+ # Check if 'current' exists in data
115
+ if current in data:
116
+ result[current] = data[current]
117
+
118
+ # Finally check the target_path itself if not covered
119
+ if target_path in data and target_path not in result:
120
+ result[target_path] = data[target_path]
121
+
122
+ return result
@@ -0,0 +1,29 @@
1
+ from zrb.llm.prompt.claude_compatibility import create_claude_compatibility_prompt
2
+ from zrb.llm.prompt.compose import PromptManager, PromptMiddleware, new_prompt
3
+ from zrb.llm.prompt.default import (
4
+ get_file_extractor_system_prompt,
5
+ get_mandate_prompt,
6
+ get_persona_prompt,
7
+ get_repo_extractor_system_prompt,
8
+ get_repo_summarizer_system_prompt,
9
+ get_summarizer_system_prompt,
10
+ )
11
+ from zrb.llm.prompt.note import create_note_prompt
12
+ from zrb.llm.prompt.system_context import system_context
13
+ from zrb.llm.prompt.zrb import create_zrb_prompt
14
+
15
+ __all__ = [
16
+ "create_claude_compatibility_prompt",
17
+ "PromptManager",
18
+ "PromptMiddleware",
19
+ "new_prompt",
20
+ "get_file_extractor_system_prompt",
21
+ "get_mandate_prompt",
22
+ "get_persona_prompt",
23
+ "get_repo_extractor_system_prompt",
24
+ "get_repo_summarizer_system_prompt",
25
+ "get_summarizer_system_prompt",
26
+ "create_note_prompt",
27
+ "system_context",
28
+ "create_zrb_prompt",
29
+ ]
@@ -0,0 +1,92 @@
1
+ from pathlib import Path
2
+ from typing import Callable, List, Optional
3
+
4
+ from zrb.context.any_context import AnyContext
5
+ from zrb.llm.skill.manager import SkillManager
6
+ from zrb.util.markdown import make_markdown_section
7
+
8
+
9
+ def create_claude_compatibility_prompt(skill_manager: SkillManager):
10
+ def claude_compatibility(
11
+ ctx: AnyContext,
12
+ current_prompt: str,
13
+ next_handler: Callable[[AnyContext, str], str],
14
+ ) -> str:
15
+ search_dirs = _get_search_directories()
16
+ additional_context = []
17
+
18
+ # 1. CLAUDE.md
19
+ claude_content = _get_combined_content("CLAUDE.md", search_dirs)
20
+ if claude_content:
21
+ additional_context.append(
22
+ make_markdown_section(
23
+ "Project Instructions (CLAUDE.md)", claude_content
24
+ )
25
+ )
26
+
27
+ # 2. AGENTS.md
28
+ agents_content = _get_combined_content("AGENTS.md", search_dirs)
29
+ if agents_content:
30
+ additional_context.append(
31
+ make_markdown_section("Agent Definitions (AGENTS.md)", agents_content)
32
+ )
33
+
34
+ # 3. Available Claude Skills
35
+ skills_section = _get_skills_section(skill_manager)
36
+ if skills_section:
37
+ additional_context.append(skills_section)
38
+
39
+ new_section = "\n\n".join(additional_context)
40
+ return next_handler(ctx, f"{current_prompt}\n\n{new_section}")
41
+
42
+ return claude_compatibility
43
+
44
+
45
+ def _get_search_directories() -> List[Path]:
46
+ search_dirs: List[Path] = []
47
+ # 1. User global config (~/.claude)
48
+ try:
49
+ home = Path.home()
50
+ search_dirs.append(home / ".claude")
51
+ except Exception:
52
+ pass
53
+
54
+ # 2. Project directories (Root -> ... -> CWD)
55
+ try:
56
+ cwd = Path.cwd()
57
+ # Parents returns [parent, grandparent...]. We want reversed (Root first)
58
+ # This allows specific configs (closer to CWD) to override general ones
59
+ project_dirs = list(cwd.parents)[::-1] + [cwd]
60
+ search_dirs.extend(project_dirs)
61
+ except Exception:
62
+ pass
63
+ return search_dirs
64
+
65
+
66
+ def _get_combined_content(filename: str, search_dirs: List[Path]) -> str:
67
+ contents = []
68
+ for directory in search_dirs:
69
+ file_path = directory / filename
70
+ if file_path.exists() and file_path.is_file():
71
+ try:
72
+ with open(file_path, "r", encoding="utf-8") as f:
73
+ content = f.read().strip()
74
+ if content:
75
+ contents.append(content)
76
+ except Exception:
77
+ pass
78
+ return "\n\n".join(contents)
79
+
80
+
81
+ def _get_skills_section(skill_manager: SkillManager) -> Optional[str]:
82
+ skills = skill_manager.scan()
83
+ if not skills:
84
+ return None
85
+
86
+ skills_context = ["Use 'activate_skill' to load instructions for a skill."]
87
+ for skill in skills:
88
+ skills_context.append(f"- {skill.name}")
89
+
90
+ return make_markdown_section(
91
+ "Available Skills (Claude Skills)", "\n".join(skills_context)
92
+ )
@@ -0,0 +1,55 @@
1
+ from typing import Callable
2
+
3
+ from zrb.context.any_context import AnyContext
4
+ from zrb.util.attr import get_str_attr
5
+
6
+ PromptMiddleware = Callable[[AnyContext, str, Callable[[AnyContext, str], str]], str]
7
+
8
+
9
+ class PromptManager:
10
+ def __init__(self, middlewares: list[PromptMiddleware] = []):
11
+ self._middlewares = middlewares
12
+
13
+ def add_middleware(self, *middleware: PromptMiddleware):
14
+ self.append_middlewear(*middleware)
15
+
16
+ def append_middlewear(self, *middleware: PromptMiddleware):
17
+ self._middlewares += middleware
18
+
19
+ def compose_prompt(self) -> Callable[[AnyContext], str]:
20
+ """
21
+ Composes a list of prompt middlewares into a single prompt factory function.
22
+
23
+ Each middleware should have the signature:
24
+ (ctx: AnyContext, prompt: str, next: Callable[[AnyContext, str], str]) -> str
25
+
26
+ The resulting function takes an AnyContext and returns the final prompt string.
27
+ """
28
+
29
+ def composed_prompt_factory(ctx: AnyContext) -> str:
30
+ def dispatch(index: int, current_prompt: str) -> str:
31
+ if index >= len(self._middlewares):
32
+ return current_prompt
33
+
34
+ middleware = self._middlewares[index]
35
+
36
+ def next_handler(c: AnyContext, p: str) -> str:
37
+ return dispatch(index + 1, p)
38
+
39
+ return middleware(ctx, current_prompt, next_handler)
40
+
41
+ return dispatch(0, "")
42
+
43
+ return composed_prompt_factory
44
+
45
+
46
+ def new_prompt(new_prompt: str, render: bool = False):
47
+ def new_prompt_middleware(
48
+ ctx: AnyContext, current_prompt: str, next: Callable[[AnyContext, str], str]
49
+ ):
50
+ effective_new_prompt = new_prompt
51
+ if render:
52
+ effective_new_prompt = get_str_attr(ctx, new_prompt, auto_render=True)
53
+ return next(ctx, f"{current_prompt}\n{effective_new_prompt}")
54
+
55
+ return new_prompt_middleware
@@ -0,0 +1,51 @@
1
+ import os
2
+
3
+ from zrb.config.config import CFG
4
+
5
+
6
+ def get_default_prompt(name: str) -> str:
7
+ # 1. Check for local project override (configured via LLM_PROMPT_DIR)
8
+ prompt_dir = getattr(CFG, "LLM_PROMPT_DIR", ".zrb/llm/prompt")
9
+ local_prompt_path = os.path.abspath(
10
+ os.path.join(os.getcwd(), prompt_dir, f"{name}.md")
11
+ )
12
+
13
+ if os.path.exists(local_prompt_path):
14
+ try:
15
+ with open(local_prompt_path, "r", encoding="utf-8") as f:
16
+ return f.read()
17
+ except Exception:
18
+ pass
19
+
20
+ # 2. Fallback to package default
21
+ cwd = os.path.dirname(__file__)
22
+ with open(os.path.join(cwd, "markdown", f"{name}.md"), "r", encoding="utf-8") as f:
23
+ return f.read()
24
+
25
+
26
+ def get_persona_prompt(assistant_name: str | None = None) -> str:
27
+ effective_assistant_name = (
28
+ assistant_name if assistant_name else CFG.LLM_ASSISTANT_NAME
29
+ )
30
+ prompt = get_default_prompt("persona")
31
+ return prompt.replace("{ASSISTANT_NAME}", effective_assistant_name)
32
+
33
+
34
+ def get_mandate_prompt() -> str:
35
+ return get_default_prompt("mandate")
36
+
37
+
38
+ def get_summarizer_system_prompt() -> str:
39
+ return get_default_prompt("summarizer")
40
+
41
+
42
+ def get_file_extractor_system_prompt() -> str:
43
+ return get_default_prompt("file_extractor")
44
+
45
+
46
+ def get_repo_extractor_system_prompt() -> str:
47
+ return get_default_prompt("repo_extractor")
48
+
49
+
50
+ def get_repo_summarizer_system_prompt() -> str:
51
+ return get_default_prompt("repo_summarizer")
@@ -0,0 +1,23 @@
1
+ # Mandate
2
+
3
+ This mandate represents your core operating principles. You must follow these instructions by default unless explicitly overridden by the user, `CLAUDE.md`, or `AGENTS.md`.
4
+
5
+ ## Core Philosophy
6
+
7
+ * **Safety & Precision**: You are an expert engineer. Do not guess. Verify assumptions. Read code before editing.
8
+ * **Conventions**: Respect the existing coding style, naming conventions, and architectural patterns of the project.
9
+ * **Minimalism**: Do not introduce unnecessary dependencies or complexities. Use existing tools and libraries whenever possible.
10
+
11
+ ## Operational Rules
12
+
13
+ 1. **Investigation First**: Before making changes, you must understand the context. Use `list_files`, `read_file`, or `search_files` to gather information.
14
+ 2. **Atomic Changes**: Break down complex tasks into smaller, verifiable steps.
15
+ 3. **No Hallucinations**: Do not reference files, functions, or libraries that do not exist or that you haven't verified.
16
+ 4. **Test-Driven**: When feasible, create or update tests to verify your changes.
17
+ 5. **Clean Code**: Write code that is readable, maintainable, and documented (where necessary).
18
+ 6. **Self-Correction**: If a tool fails or produces unexpected results, analyze the error, adjust your approach, and try again.
19
+
20
+ ## Communication
21
+
22
+ * **Direct & Professional**: Be concise. Avoid fluff.
23
+ * **Transparent**: Briefly explain *why* you are doing something if it involves significant changes or risks.
@@ -0,0 +1,3 @@
1
+ You are {ASSISTANT_NAME}, an intelligent, capable, and efficient AI Assistant.
2
+ Your goal is to assist the user in their tasks with precision and speed.
3
+
@@ -0,0 +1,21 @@
1
+ You are an expert conversation summarizer. Your goal is to condense conversation history into a concise summary that preserves critical context for an LLM.
2
+
3
+ # Guidelines:
4
+ 1. **Preserve User Intent:** Clearly state what the user wanted to achieve.
5
+ 2. **Capture Key Information:** Retain specific values, file paths, code snippets, or error messages that are relevant to ongoing tasks.
6
+ 3. **Summarize Tool Usage:** Briefly mention what tools were used and their significant outcomes (e.g., "User listed files and found 'main.py'").
7
+ 4. **Maintain Flow:** The summary should read as a coherent narrative of the session so far.
8
+ 5. **Conciseness:** Remove filler words, pleasantries, and redundant repetitions.
9
+
10
+ # Example:
11
+
12
+ *Original:*
13
+ User: "Hi, can you help me find the bug in my code?"
14
+ AI: "Sure, please share the code."
15
+ User: "Here it is: `def add(a, b): return a - b`"
16
+ AI: "I see, it subtracts instead of adds."
17
+ User: "Oh, right. Fix it please."
18
+ AI: "I'll fix it." (Tool Call: replace_in_file) (Tool Result: Success)
19
+
20
+ *Summary:*
21
+ The user asked for help finding a bug in a provided `add` function. The AI identified that it performed subtraction instead of addition. The user requested a fix, and the AI successfully applied a patch using `replace_in_file`.
zrb/llm/prompt/note.py ADDED
@@ -0,0 +1,41 @@
1
+ import os
2
+ from typing import Callable
3
+
4
+ from zrb.context.any_context import AnyContext
5
+ from zrb.llm.note.manager import NoteManager
6
+ from zrb.util.markdown import make_markdown_section
7
+
8
+
9
+ def create_note_prompt(note_manager: NoteManager):
10
+ def note_prompt(
11
+ ctx: AnyContext,
12
+ current_prompt: str,
13
+ next_handler: Callable[[AnyContext, str], str],
14
+ ) -> str:
15
+ cwd = os.getcwd()
16
+ notes = note_manager.read_all(cwd)
17
+
18
+ if not notes:
19
+ return next_handler(ctx, current_prompt)
20
+
21
+ # Format notes
22
+ note_lines = []
23
+
24
+ # Sort keys for deterministic output
25
+ sorted_keys = sorted(notes.keys())
26
+
27
+ for key in sorted_keys:
28
+ content = notes[key].strip()
29
+ if not content:
30
+ continue
31
+ note_lines.append(f"**{key}**:\n{content}\n")
32
+
33
+ if not note_lines:
34
+ return next_handler(ctx, current_prompt)
35
+
36
+ full_note_content = "\n".join(note_lines)
37
+ note_block = make_markdown_section("Notes & Context", full_note_content)
38
+
39
+ return next_handler(ctx, f"{current_prompt}\n\n{note_block}")
40
+
41
+ return note_prompt
@@ -0,0 +1,46 @@
1
+ import os
2
+ import platform
3
+ import subprocess
4
+ from datetime import datetime
5
+ from typing import Callable
6
+
7
+ from zrb.context.any_context import AnyContext
8
+ from zrb.util.markdown import make_markdown_section
9
+
10
+
11
+ def system_context(
12
+ ctx: AnyContext, current_prompt: str, next_handler: Callable[[AnyContext, str], str]
13
+ ) -> str:
14
+ # Time
15
+ now = datetime.now().strftime("%A, %B %d, %Y %H:%M:%S")
16
+
17
+ # System
18
+ os_info = f"{platform.system()} {platform.release()}"
19
+
20
+ # CWD
21
+ cwd = os.getcwd()
22
+
23
+ # Git
24
+ git_info = ""
25
+ try:
26
+ if os.path.isdir(os.path.join(cwd, ".git")):
27
+ # Get branch
28
+ res = subprocess.run(
29
+ ["git", "branch", "--show-current"], capture_output=True, text=True
30
+ )
31
+ if res.returncode == 0:
32
+ branch = res.stdout.strip()
33
+ if branch:
34
+ git_info = f"\n- Git Branch: {branch}"
35
+ except Exception:
36
+ pass
37
+
38
+ info_content = "\n".join(
39
+ [
40
+ f"- Date: {now}",
41
+ f"- OS: {os_info}",
42
+ f"- Directory: {cwd}{git_info}",
43
+ ]
44
+ )
45
+ info_block = make_markdown_section("System Context", info_content)
46
+ return next_handler(ctx, f"{current_prompt}\n\n{info_block}")
zrb/llm/prompt/zrb.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Callable
2
+
3
+ from zrb.config.config import CFG
4
+ from zrb.context.any_context import AnyContext
5
+ from zrb.runner.cli import cli
6
+ from zrb.util.markdown import make_markdown_section
7
+
8
+
9
+ def create_zrb_prompt():
10
+ def zrb_prompt(
11
+ ctx: AnyContext,
12
+ current_prompt: str,
13
+ next_handler: Callable[[AnyContext, str], str],
14
+ ) -> str:
15
+ additional_context = []
16
+ # Available Zrb Workflows (Skills)
17
+ zrb_cmd = CFG.ROOT_GROUP_NAME
18
+ zrb_context = []
19
+
20
+ # List top-level groups
21
+ if cli.subgroups:
22
+ zrb_context.append(f"Groups (Use 'list_{zrb_cmd}_tasks' to see details):")
23
+ for alias, grp in cli.subgroups.items():
24
+ zrb_context.append(f"- {alias}: {grp.description}")
25
+
26
+ # List top-level tasks
27
+ if cli.subtasks:
28
+ zrb_context.append("Tasks:")
29
+ for alias, task in cli.subtasks.items():
30
+ zrb_context.append(f"- {alias}: {task.description}")
31
+
32
+ additional_context.append(
33
+ make_markdown_section(
34
+ f"Available Skills ({zrb_cmd} Workflows)", "\n".join(zrb_context)
35
+ )
36
+ )
37
+
38
+ new_section = "\n\n".join(additional_context)
39
+ return next_handler(ctx, f"{current_prompt}\n\n{new_section}")
40
+
41
+ return zrb_prompt
@@ -0,0 +1,3 @@
1
+ from zrb.llm.skill.manager import Skill, SkillManager, skill_manager
2
+
3
+ __all__ = ["Skill", "SkillManager", "skill_manager"]
@@ -0,0 +1,86 @@
1
+ import os
2
+ from typing import Dict, List, Optional
3
+
4
+ IGNORE_DIRS = {
5
+ ".git",
6
+ "node_modules",
7
+ "__pycache__",
8
+ ".venv",
9
+ "venv",
10
+ "dist",
11
+ "build",
12
+ ".idea",
13
+ ".vscode",
14
+ }
15
+
16
+
17
+ class Skill:
18
+ def __init__(self, name: str, path: str, description: str):
19
+ self.name = name
20
+ self.path = path
21
+ self.description = description
22
+
23
+
24
+ class SkillManager:
25
+ def __init__(self, root_dir: str = "."):
26
+ self.root_dir = root_dir
27
+ self._skills: Dict[str, Skill] = {}
28
+
29
+ def scan(self) -> List[Skill]:
30
+ self._skills = {}
31
+ for root, dirs, files in os.walk(self.root_dir):
32
+ dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
33
+
34
+ for file in files:
35
+ if file == "SKILL.md" or file.endswith(".skill.md"):
36
+ full_path = os.path.join(root, file)
37
+ rel_path = os.path.relpath(full_path, self.root_dir)
38
+ self._load_skill(rel_path, full_path)
39
+
40
+ return list(self._skills.values())
41
+
42
+ def _load_skill(self, rel_path: str, full_path: str):
43
+ try:
44
+ with open(full_path, "r", encoding="utf-8") as f:
45
+ lines = f.readlines()
46
+
47
+ name = os.path.basename(os.path.dirname(full_path))
48
+ description = "No description"
49
+
50
+ # Parse Markdown for Header 1
51
+ for line in lines:
52
+ stripped = line.strip()
53
+ if stripped.startswith("# "):
54
+ name = stripped[2:].strip()
55
+ break
56
+
57
+ # Use name as key, handle duplicates
58
+ key = name
59
+ if key in self._skills:
60
+ key = f"{name} ({rel_path})"
61
+
62
+ self._skills[key] = Skill(name=key, path=rel_path, description=description)
63
+
64
+ except Exception:
65
+ pass
66
+
67
+ def get_skill_content(self, name: str) -> Optional[str]:
68
+ skill = self._skills.get(name)
69
+ if not skill:
70
+ # Try partial match or path match
71
+ for s in self._skills.values():
72
+ if s.name == name or s.path == name:
73
+ skill = s
74
+ break
75
+
76
+ if not skill:
77
+ return None
78
+
79
+ try:
80
+ with open(skill.path, "r", encoding="utf-8") as f:
81
+ return f.read()
82
+ except Exception as e:
83
+ return f"Error reading skill file: {e}"
84
+
85
+
86
+ skill_manager = SkillManager()
@@ -0,0 +1,4 @@
1
+ from zrb.llm.task.llm_chat_task import LLMChatTask
2
+ from zrb.llm.task.llm_task import LLMTask
3
+
4
+ __all__ = ["LLMChatTask", "LLMTask"]