bareagent-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. bareagent/__init__.py +10 -0
  2. bareagent/concurrency/__init__.py +6 -0
  3. bareagent/concurrency/background.py +97 -0
  4. bareagent/concurrency/notification.py +61 -0
  5. bareagent/concurrency/scheduler.py +136 -0
  6. bareagent/config.toml +299 -0
  7. bareagent/core/__init__.py +1 -0
  8. bareagent/core/config_paths.py +49 -0
  9. bareagent/core/context.py +127 -0
  10. bareagent/core/fileutil.py +103 -0
  11. bareagent/core/goal.py +214 -0
  12. bareagent/core/handlers/__init__.py +1 -0
  13. bareagent/core/handlers/bash.py +79 -0
  14. bareagent/core/handlers/file_edit.py +47 -0
  15. bareagent/core/handlers/file_read.py +270 -0
  16. bareagent/core/handlers/file_write.py +34 -0
  17. bareagent/core/handlers/glob_search.py +30 -0
  18. bareagent/core/handlers/goal.py +60 -0
  19. bareagent/core/handlers/grep_search.py +52 -0
  20. bareagent/core/handlers/memory.py +71 -0
  21. bareagent/core/handlers/plan.py +106 -0
  22. bareagent/core/handlers/search_utils.py +77 -0
  23. bareagent/core/handlers/skill.py +87 -0
  24. bareagent/core/handlers/subagent_send.py +70 -0
  25. bareagent/core/handlers/web_fetch.py +126 -0
  26. bareagent/core/handlers/web_search.py +165 -0
  27. bareagent/core/handlers/workflow.py +190 -0
  28. bareagent/core/loop.py +535 -0
  29. bareagent/core/retry.py +131 -0
  30. bareagent/core/sandbox.py +27 -0
  31. bareagent/core/schema.py +21 -0
  32. bareagent/core/tools.py +779 -0
  33. bareagent/core/workflow.py +517 -0
  34. bareagent/core/workflow_registry.py +219 -0
  35. bareagent/debug/__init__.py +0 -0
  36. bareagent/debug/interaction_log.py +263 -0
  37. bareagent/debug/viewer.html +1750 -0
  38. bareagent/debug/web_viewer.py +157 -0
  39. bareagent/hooks/__init__.py +32 -0
  40. bareagent/hooks/config.py +118 -0
  41. bareagent/hooks/engine.py +197 -0
  42. bareagent/hooks/errors.py +14 -0
  43. bareagent/hooks/events.py +22 -0
  44. bareagent/lsp/__init__.py +63 -0
  45. bareagent/lsp/config.py +134 -0
  46. bareagent/lsp/coord.py +118 -0
  47. bareagent/lsp/diagnostics.py +240 -0
  48. bareagent/lsp/errors.py +24 -0
  49. bareagent/lsp/manager.py +866 -0
  50. bareagent/lsp/tools.py +629 -0
  51. bareagent/lsp/workspace_edit.py +305 -0
  52. bareagent/main.py +4205 -0
  53. bareagent/mcp/__init__.py +69 -0
  54. bareagent/mcp/_sse.py +69 -0
  55. bareagent/mcp/client.py +341 -0
  56. bareagent/mcp/config.py +169 -0
  57. bareagent/mcp/errors.py +32 -0
  58. bareagent/mcp/manager.py +318 -0
  59. bareagent/mcp/protocol.py +187 -0
  60. bareagent/mcp/registry.py +557 -0
  61. bareagent/mcp/transport/__init__.py +15 -0
  62. bareagent/mcp/transport/base.py +149 -0
  63. bareagent/mcp/transport/http_legacy.py +192 -0
  64. bareagent/mcp/transport/http_streamable.py +217 -0
  65. bareagent/mcp/transport/stdio.py +202 -0
  66. bareagent/memory/__init__.py +1 -0
  67. bareagent/memory/compact.py +203 -0
  68. bareagent/memory/conversation_io.py +226 -0
  69. bareagent/memory/embedding.py +194 -0
  70. bareagent/memory/persistent.py +515 -0
  71. bareagent/memory/token_counter.py +67 -0
  72. bareagent/memory/token_tracker.py +262 -0
  73. bareagent/memory/transcript.py +100 -0
  74. bareagent/permission/__init__.py +1 -0
  75. bareagent/permission/guard.py +329 -0
  76. bareagent/permission/rules.py +19 -0
  77. bareagent/planning/__init__.py +19 -0
  78. bareagent/planning/agent_types.py +169 -0
  79. bareagent/planning/skill_gen.py +141 -0
  80. bareagent/planning/skill_store.py +173 -0
  81. bareagent/planning/skills.py +146 -0
  82. bareagent/planning/subagent.py +355 -0
  83. bareagent/planning/subagent_registry.py +77 -0
  84. bareagent/planning/tasks.py +348 -0
  85. bareagent/planning/todo.py +153 -0
  86. bareagent/planning/worktree.py +122 -0
  87. bareagent/provider/__init__.py +1 -0
  88. bareagent/provider/anthropic.py +348 -0
  89. bareagent/provider/base.py +136 -0
  90. bareagent/provider/factory.py +130 -0
  91. bareagent/provider/openai.py +881 -0
  92. bareagent/provider/presets.py +72 -0
  93. bareagent/provider/setup.py +356 -0
  94. bareagent/skills/.gitkeep +1 -0
  95. bareagent/skills/code-review/SKILL.md +68 -0
  96. bareagent/skills/git/SKILL.md +68 -0
  97. bareagent/skills/test/SKILL.md +70 -0
  98. bareagent/team/__init__.py +17 -0
  99. bareagent/team/autonomous.py +193 -0
  100. bareagent/team/mailbox.py +239 -0
  101. bareagent/team/manager.py +155 -0
  102. bareagent/team/protocols.py +129 -0
  103. bareagent/tracing/__init__.py +12 -0
  104. bareagent/tracing/_api.py +92 -0
  105. bareagent/tracing/_proxy.py +60 -0
  106. bareagent/tracing/composite.py +115 -0
  107. bareagent/tracing/json_file.py +115 -0
  108. bareagent/tracing/langfuse.py +139 -0
  109. bareagent/tracing/otel.py +107 -0
  110. bareagent/tracing/setup.py +85 -0
  111. bareagent/ui/__init__.py +24 -0
  112. bareagent/ui/console.py +167 -0
  113. bareagent/ui/prompt.py +78 -0
  114. bareagent/ui/protocol.py +24 -0
  115. bareagent/ui/stream.py +66 -0
  116. bareagent/ui/theme.py +240 -0
  117. bareagent_cli-0.1.0.dist-info/METADATA +331 -0
  118. bareagent_cli-0.1.0.dist-info/RECORD +121 -0
  119. bareagent_cli-0.1.0.dist-info/WHEEL +4 -0
  120. bareagent_cli-0.1.0.dist-info/entry_points.txt +2 -0
  121. bareagent_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Literal
5
+
6
+ ProviderRoute = Literal["anthropic", "openai"]
7
+
8
+
9
+ @dataclass(slots=True, frozen=True)
10
+ class ProviderPreset:
11
+ """Static configuration for a known provider channel.
12
+
13
+ Drives both the factory (route + default base_url/key env) and, later, the
14
+ interactive setup wizard (display_name + candidate_models). Plain immutable
15
+ data, looked up by id via :func:`resolve_preset` -- no dynamic registry.
16
+ """
17
+
18
+ id: str
19
+ display_name: str
20
+ route: ProviderRoute
21
+ default_base_url: str | None
22
+ default_api_key_env: str
23
+ candidate_models: tuple[str, ...] = field(default_factory=tuple)
24
+
25
+
26
+ PROVIDER_PRESETS: dict[str, ProviderPreset] = {
27
+ "anthropic": ProviderPreset(
28
+ id="anthropic",
29
+ display_name="Claude (Anthropic)",
30
+ route="anthropic",
31
+ default_base_url=None,
32
+ default_api_key_env="ANTHROPIC_API_KEY",
33
+ candidate_models=("claude-sonnet-4-20250514", "claude-opus-4-20250514"),
34
+ ),
35
+ "openai": ProviderPreset(
36
+ id="openai",
37
+ display_name="ChatGPT (OpenAI)",
38
+ route="openai",
39
+ default_base_url=None,
40
+ default_api_key_env="OPENAI_API_KEY",
41
+ candidate_models=("gpt-4.1", "gpt-4o"),
42
+ ),
43
+ "deepseek": ProviderPreset(
44
+ id="deepseek",
45
+ display_name="DeepSeek",
46
+ route="openai",
47
+ default_base_url="https://api.deepseek.com",
48
+ default_api_key_env="DEEPSEEK_API_KEY",
49
+ candidate_models=("deepseek-chat", "deepseek-reasoner"),
50
+ ),
51
+ "qwen": ProviderPreset(
52
+ id="qwen",
53
+ display_name="Qwen (DashScope)",
54
+ route="openai",
55
+ default_base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
56
+ default_api_key_env="DASHSCOPE_API_KEY",
57
+ candidate_models=("qwen-plus", "qwen-max", "qwen-turbo"),
58
+ ),
59
+ "glm": ProviderPreset(
60
+ id="glm",
61
+ display_name="GLM (Zhipu/BigModel)",
62
+ route="openai",
63
+ default_base_url="https://open.bigmodel.cn/api/paas/v4",
64
+ default_api_key_env="ZHIPUAI_API_KEY",
65
+ candidate_models=("glm-4.6", "glm-4-plus"),
66
+ ),
67
+ }
68
+
69
+
70
+ def resolve_preset(preset_id: str) -> ProviderPreset | None:
71
+ """Look up a provider preset by id (case-insensitive), or None if unknown."""
72
+ return PROVIDER_PRESETS.get(preset_id.strip().lower())
@@ -0,0 +1,356 @@
1
+ """Interactive ``bareagent init`` setup wizard + stdlib-only TOML writer.
2
+
3
+ This module owns "choosing and configuring a provider", so per
4
+ ``directory-structure.md`` it lives next to ``presets.py`` / ``factory.py``
5
+ rather than in a new top-level package. The wizard is a numbered-menu + line
6
+ input flow (no full-screen prompt-toolkit dialog -- simpler, cross-platform,
7
+ testable). IO is injected via ``input_fn`` / ``output_fn`` so tests can script
8
+ answers without touching real stdin.
9
+
10
+ Writing is stdlib-only: read the existing ``config.local.toml`` text, replace
11
+ or insert just the ``[provider]`` section, validate the result with
12
+ ``tomllib``, then atomically write it back -- no ``tomlkit`` / ``tomli``
13
+ dependency (see ``quality-guidelines.md``).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import tomllib
20
+ from collections.abc import Callable
21
+ from pathlib import Path
22
+
23
+ from bareagent.core.config_paths import local_config_path
24
+ from bareagent.core.fileutil import atomic_write_text
25
+ from bareagent.provider.presets import PROVIDER_PRESETS, ProviderPreset
26
+
27
+ InputFn = Callable[[str], str]
28
+ OutputFn = Callable[[str], None]
29
+
30
+ # Ordered list of channels shown in the menu. The first five are presets; the
31
+ # sixth is the custom OpenAI-compatible branch.
32
+ _MENU_PRESET_IDS = ("deepseek", "openai", "anthropic", "qwen", "glm")
33
+ _CUSTOM_CHOICE_LABEL = "Third-party OpenAI-compatible (custom base_url)"
34
+
35
+ # Field order used when rendering the [provider] table so the written file is
36
+ # stable and readable.
37
+ _PROVIDER_FIELD_ORDER = (
38
+ "name",
39
+ "model",
40
+ "base_url",
41
+ "api_key",
42
+ "api_key_env",
43
+ "wire_api",
44
+ )
45
+
46
+
47
+ def run_setup_wizard(
48
+ *,
49
+ config_path: Path,
50
+ input_fn: InputFn | None = None,
51
+ output_fn: OutputFn | None = None,
52
+ ) -> bool:
53
+ """Run the interactive provider setup wizard.
54
+
55
+ Collects a channel choice, model, base_url and API key, then writes the
56
+ ``[provider]`` section into the ``.local`` sibling of *config_path*,
57
+ preserving every other section. Returns ``True`` when the config was
58
+ written, ``False`` when the user cancelled or gave unrecoverable input.
59
+
60
+ IO is injectable: *input_fn* defaults to :func:`input` and *output_fn* to
61
+ :func:`print`, so tests drive it with scripted answers and captured output.
62
+ """
63
+ ask = input_fn if input_fn is not None else input
64
+ say = output_fn if output_fn is not None else print
65
+
66
+ try:
67
+ choice = _select_channel(ask, say)
68
+ except _WizardCancelled:
69
+ say("Setup cancelled.")
70
+ return False
71
+ if choice is None:
72
+ return False
73
+
74
+ if choice == "custom":
75
+ table = _collect_custom(ask, say)
76
+ else:
77
+ table = _collect_preset(choice, ask, say)
78
+ if table is None:
79
+ return False
80
+
81
+ local_path = _local_config_path(config_path)
82
+ try:
83
+ _write_provider_section(local_path, table)
84
+ except OSError as exc:
85
+ say(f"Failed to write {local_path}: {exc}")
86
+ return False
87
+ except tomllib.TOMLDecodeError as exc:
88
+ # The in-memory validation in _write_provider_section rejected the
89
+ # spliced result (e.g. the existing file has an oddly-placed `[provider]`
90
+ # line inside a multi-line string). Abort cleanly -- the original file is
91
+ # never touched because validation runs before the atomic write.
92
+ say(f"Refusing to write {local_path}: result would not be valid TOML ({exc}).")
93
+ return False
94
+
95
+ say("")
96
+ say(f"Provider configuration written to {local_path}.")
97
+ say("Run `bareagent` to start a session with the configured channel.")
98
+ if "api_key_env" in table:
99
+ say(
100
+ "Remember to export the environment variable "
101
+ f"{table['api_key_env']} before running BareAgent."
102
+ )
103
+ return True
104
+
105
+
106
+ class _WizardCancelled(Exception):
107
+ """Internal signal that the user aborted the wizard (EOF / Ctrl+C)."""
108
+
109
+
110
+ def _ask(ask: InputFn, prompt: str) -> str:
111
+ try:
112
+ return ask(prompt).strip()
113
+ except (EOFError, KeyboardInterrupt) as exc:
114
+ raise _WizardCancelled from exc
115
+
116
+
117
+ def _select_channel(ask: InputFn, say: OutputFn) -> str | None:
118
+ """Prompt for a channel; return a preset id, ``"custom"``, or ``None``."""
119
+ say("BareAgent provider setup")
120
+ say("Select a provider channel:")
121
+ for index, preset_id in enumerate(_MENU_PRESET_IDS, start=1):
122
+ preset = PROVIDER_PRESETS[preset_id]
123
+ say(f" {index}) {preset.display_name}")
124
+ custom_index = len(_MENU_PRESET_IDS) + 1
125
+ say(f" {custom_index}) {_CUSTOM_CHOICE_LABEL}")
126
+
127
+ raw = _ask(ask, f"Channel [1-{custom_index}]: ")
128
+ if not raw:
129
+ say("No channel selected.")
130
+ return None
131
+ try:
132
+ selected = int(raw)
133
+ except ValueError:
134
+ say(f"Invalid choice: {raw!r}. Expected a number 1-{custom_index}.")
135
+ return None
136
+ if selected == custom_index:
137
+ return "custom"
138
+ if 1 <= selected <= len(_MENU_PRESET_IDS):
139
+ return _MENU_PRESET_IDS[selected - 1]
140
+ say(f"Choice out of range: {selected}. Expected 1-{custom_index}.")
141
+ return None
142
+
143
+
144
+ def _collect_preset(
145
+ preset_id: str,
146
+ ask: InputFn,
147
+ say: OutputFn,
148
+ ) -> dict[str, str] | None:
149
+ preset = PROVIDER_PRESETS[preset_id]
150
+ say("")
151
+ say(f"Configuring {preset.display_name}.")
152
+
153
+ model = _prompt_model(preset, ask, say)
154
+ if model is None:
155
+ return None
156
+
157
+ base_url = _prompt_base_url(preset.default_base_url, ask, say, required=False)
158
+
159
+ table: dict[str, str] = {"name": preset.id, "model": model}
160
+ if base_url:
161
+ table["base_url"] = base_url
162
+
163
+ if not _apply_key(table, preset.default_api_key_env, ask, say):
164
+ return None
165
+ return table
166
+
167
+
168
+ def _collect_custom(ask: InputFn, say: OutputFn) -> dict[str, str] | None:
169
+ say("")
170
+ say("Configuring a third-party OpenAI-compatible channel.")
171
+
172
+ name = _ask(ask, "Provider route name [openai]: ") or "openai"
173
+ base_url = _prompt_base_url(None, ask, say, required=True)
174
+ if base_url is None:
175
+ return None
176
+ model = _ask(ask, "Model: ")
177
+ if not model:
178
+ say("Model is required.")
179
+ return None
180
+
181
+ table: dict[str, str] = {"name": name, "model": model, "base_url": base_url}
182
+ if not _apply_key(table, "OPENAI_API_KEY", ask, say):
183
+ return None
184
+ return table
185
+
186
+
187
+ def _prompt_model(
188
+ preset: ProviderPreset,
189
+ ask: InputFn,
190
+ say: OutputFn,
191
+ ) -> str | None:
192
+ candidates = preset.candidate_models
193
+ if not candidates:
194
+ model = _ask(ask, "Model: ")
195
+ if not model:
196
+ say("Model is required.")
197
+ return None
198
+ return model
199
+
200
+ say("Candidate models:")
201
+ for index, name in enumerate(candidates, start=1):
202
+ say(f" {index}) {name}")
203
+ raw = _ask(
204
+ ask,
205
+ f"Model [number 1-{len(candidates)}, or type a custom name, default {candidates[0]}]: ",
206
+ )
207
+ if not raw:
208
+ return candidates[0]
209
+ try:
210
+ selected = int(raw)
211
+ except ValueError:
212
+ return raw
213
+ if 1 <= selected <= len(candidates):
214
+ return candidates[selected - 1]
215
+ say(f"Choice out of range: {selected}. Using {candidates[0]}.")
216
+ return candidates[0]
217
+
218
+
219
+ def _prompt_base_url(
220
+ default: str | None,
221
+ ask: InputFn,
222
+ say: OutputFn,
223
+ *,
224
+ required: bool,
225
+ ) -> str | None:
226
+ if default:
227
+ value = _ask(ask, f"Base URL [{default}]: ")
228
+ return value or default
229
+ value = _ask(ask, "Base URL: ")
230
+ if value:
231
+ return value
232
+ if required:
233
+ say("Base URL is required for a custom OpenAI-compatible channel.")
234
+ return None
235
+ return ""
236
+
237
+
238
+ def _apply_key(
239
+ table: dict[str, str],
240
+ default_api_key_env: str,
241
+ ask: InputFn,
242
+ say: OutputFn,
243
+ ) -> bool:
244
+ """Collect the key into *table* as ``api_key`` or ``api_key_env``.
245
+
246
+ Returns ``True`` on success, ``False`` when required input was missing.
247
+ """
248
+ say("API key storage:")
249
+ say(" 1) Write the key in plaintext to config.local.toml (default)")
250
+ say(" 2) Use an environment variable instead")
251
+ storage = _ask(ask, "Choice [1-2, default 1]: ")
252
+ if storage == "2":
253
+ env_name = _ask(ask, f"Environment variable name [{default_api_key_env}]: ")
254
+ table["api_key_env"] = env_name or default_api_key_env
255
+ return True
256
+
257
+ api_key = _ask(ask, "API key: ")
258
+ if not api_key:
259
+ say("API key is required.")
260
+ return False
261
+ table["api_key"] = api_key
262
+ return True
263
+
264
+
265
+ def _local_config_path(config_path: Path) -> Path:
266
+ """Return the writable ``config.local.toml`` path for *config_path*.
267
+
268
+ Delegates to :func:`bareagent.core.config_paths.local_config_path` so the
269
+ wizard writes exactly the file that ``load_config`` later merges as the local
270
+ override layer: the CWD for the bundled default, or the ``.local`` sibling for
271
+ an explicit ``--config`` path.
272
+ """
273
+ return local_config_path(config_path)
274
+
275
+
276
+ def _render_provider_section(provider_table: dict[str, str]) -> str:
277
+ """Render the ``[provider]`` table as a TOML text block (no trailing NL)."""
278
+ lines = ["[provider]"]
279
+ rendered_keys: set[str] = set()
280
+ for key in _PROVIDER_FIELD_ORDER:
281
+ value = provider_table.get(key)
282
+ if not value:
283
+ continue
284
+ lines.append(f"{key} = {json.dumps(value, ensure_ascii=False)}")
285
+ rendered_keys.add(key)
286
+ # Any extra simple keys (defensive: should not normally happen) keep a
287
+ # deterministic order so the file stays stable.
288
+ for key in sorted(provider_table):
289
+ if key in rendered_keys:
290
+ continue
291
+ value = provider_table[key]
292
+ if not value:
293
+ continue
294
+ lines.append(f"{key} = {json.dumps(value, ensure_ascii=False)}")
295
+ return "\n".join(lines)
296
+
297
+
298
+ def _replace_provider_block(original: str, new_block: str) -> str:
299
+ """Replace or append the top-level ``[provider]`` section in *original*.
300
+
301
+ Only the exact top-level table header ``[provider]`` is matched -- not
302
+ ``[provider.xxx]`` sub-tables and not ``[[provider]]`` arrays. The section
303
+ spans from its header line up to (but excluding) the next top-level
304
+ ``[``-prefixed header or EOF.
305
+ """
306
+ lines = original.splitlines()
307
+ start = _find_provider_header(lines)
308
+ new_lines = new_block.splitlines()
309
+
310
+ if start is None:
311
+ return _append_provider_block(original, new_block)
312
+
313
+ end = len(lines)
314
+ for index in range(start + 1, len(lines)):
315
+ if lines[index].lstrip().startswith("["):
316
+ end = index
317
+ break
318
+
319
+ rebuilt = lines[:start] + new_lines + lines[end:]
320
+ text = "\n".join(rebuilt)
321
+ if original.endswith("\n") or not original:
322
+ text += "\n"
323
+ return text
324
+
325
+
326
+ def _find_provider_header(lines: list[str]) -> int | None:
327
+ for index, line in enumerate(lines):
328
+ if line.strip() == "[provider]":
329
+ return index
330
+ return None
331
+
332
+
333
+ def _append_provider_block(original: str, new_block: str) -> str:
334
+ if not original.strip():
335
+ return new_block + "\n"
336
+ separator = "" if original.endswith("\n") else "\n"
337
+ # Blank line before the appended section keeps the file readable.
338
+ return f"{original}{separator}\n{new_block}\n"
339
+
340
+
341
+ def _write_provider_section(config_path: Path, provider_table: dict[str, str]) -> None:
342
+ """Replace/insert the ``[provider]`` section in *config_path*, atomically.
343
+
344
+ Preserves every other section verbatim. The resulting text is validated
345
+ with ``tomllib`` *before* it is written, so a malformed result raises
346
+ instead of corrupting the file.
347
+ """
348
+ original = config_path.read_text(encoding="utf-8") if config_path.is_file() else ""
349
+ new_block = _render_provider_section(provider_table)
350
+ updated = _replace_provider_block(original, new_block)
351
+
352
+ # Validate in memory before touching disk -- a parse failure must abort
353
+ # rather than write a broken file.
354
+ tomllib.loads(updated)
355
+
356
+ atomic_write_text(config_path, updated)
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,68 @@
1
+ # Code Review
2
+
3
+ Use this skill when you need a practical review checklist for correctness, safety, performance, and maintainability.
4
+
5
+ ## Review Order
6
+
7
+ - Start with behavior and risk.
8
+ - Then inspect security-sensitive edges.
9
+ - Then inspect performance hotspots.
10
+ - Finish with readability and tests.
11
+
12
+ ## Correctness
13
+
14
+ - Does the change satisfy the stated task?
15
+ - Are error cases handled deliberately?
16
+ - Are assumptions validated or documented?
17
+ - Are return values consistent across branches?
18
+ - Are default values safe?
19
+ - Is state updated in the right order?
20
+ - Can stale data survive after an update?
21
+ - Are identifiers stable and deterministic when tests need them?
22
+
23
+ ## Security
24
+
25
+ - Check command execution inputs for injection risk.
26
+ - Check file path handling for traversal or escape risk.
27
+ - Check network calls for accidental secret leakage.
28
+ - Check tool outputs for overexposure of sensitive data.
29
+ - Check whether permissions are bypassed unintentionally.
30
+ - Check whether untrusted input reaches shell commands directly.
31
+
32
+ ## Performance
33
+
34
+ - Look for repeated full-file scans in hot paths.
35
+ - Check whether expensive work can be cached safely.
36
+ - Avoid unnecessary large prompt injections.
37
+ - Prefer on-demand loading for heavy reference content.
38
+ - Keep loops linear unless a stronger structure is required.
39
+ - Watch for duplicate subprocess calls.
40
+
41
+ ## Readability
42
+
43
+ - Prefer explicit names over clever abstractions.
44
+ - Keep handlers thin and delegate logic to modules.
45
+ - Make formatting output easy for the agent to parse.
46
+ - Avoid hidden side effects in helper functions.
47
+ - Keep validation errors specific.
48
+
49
+ ## Testing
50
+
51
+ - Add tests for the happy path.
52
+ - Add tests for invalid input where behavior matters.
53
+ - Add tests for regressions around integration seams.
54
+ - Prefer deterministic outputs that are easy to assert.
55
+ - Verify new tools are reachable from the registry path.
56
+
57
+ ## Review Findings Format
58
+
59
+ - Report the most severe issue first.
60
+ - Include file and line when possible.
61
+ - Explain the concrete failure mode.
62
+ - Suggest the smallest correction that closes the gap.
63
+
64
+ ## When No Findings Exist
65
+
66
+ - Say so explicitly.
67
+ - Note any remaining test gaps or assumptions.
68
+ - Separate confirmed behavior from inferred behavior.
@@ -0,0 +1,68 @@
1
+ # Git Workflow
2
+
3
+ Use this skill when you need to prepare commits, choose a branch name, or check whether a change is ready to land.
4
+
5
+ ## Goals
6
+
7
+ - Keep history readable.
8
+ - Make branch purpose obvious.
9
+ - Prefer small, reviewable commits.
10
+ - Avoid mixing unrelated changes.
11
+
12
+ ## Branch Naming
13
+
14
+ - Use lowercase words and hyphens only.
15
+ - Prefer `feature/<area>-<summary>` for new work.
16
+ - Prefer `fix/<area>-<summary>` for bug fixes.
17
+ - Prefer `chore/<area>-<summary>` for maintenance.
18
+ - Prefer `docs/<area>-<summary>` for documentation-only work.
19
+ - Keep names short enough to read in `git branch`.
20
+ - Encode the user-facing intent, not the implementation detail.
21
+ - Good: `feature/planning-skills-loader`
22
+ - Good: `fix/repl-nag-reminder`
23
+ - Bad: `misc/stuff`
24
+ - Bad: `feature/final-version-v2`
25
+
26
+ ## Commit Structure
27
+
28
+ - Follow Conventional Commits.
29
+ - Format: `<type>(<scope>): <summary>`
30
+ - Omit the scope if it adds no value.
31
+ - Keep the summary in imperative mood.
32
+ - Keep the summary under 72 characters when practical.
33
+ - Types:
34
+ - `feat`: user-visible capability
35
+ - `fix`: behavior correction
36
+ - `refactor`: internal code change without behavior change
37
+ - `test`: test-only change
38
+ - `docs`: documentation-only change
39
+ - `chore`: maintenance or tooling
40
+
41
+ ## Commit Message Examples
42
+
43
+ - `feat(planning): add in-memory todo manager`
44
+ - `fix(main): inject nag reminder before latest user turn`
45
+ - `test(skills): cover skill scanning and loading`
46
+ - `docs(readme): explain planning layer`
47
+
48
+ ## Commit Hygiene
49
+
50
+ - Review `git diff --stat` before committing.
51
+ - Stage only files related to one logical change.
52
+ - Separate refactors from behavior changes when possible.
53
+ - Do not include generated files unless required by the repo.
54
+ - Mention follow-up work in the body, not the summary.
55
+
56
+ ## Before Commit
57
+
58
+ - Run the narrowest relevant test command first.
59
+ - Re-read changed error messages and user-facing text.
60
+ - Check for accidental debug prints.
61
+ - Check for unrelated formatting churn.
62
+ - Confirm new files are included.
63
+
64
+ ## When Unsure
65
+
66
+ - Prefer two small commits over one mixed commit.
67
+ - Prefer a boring branch name over a clever one.
68
+ - Ask whether the commit title would still make sense in six months.
@@ -0,0 +1,70 @@
1
+ # Testing
2
+
3
+ Use this skill when writing or reviewing tests for agent loops, tool handlers, prompt assembly, or filesystem behavior.
4
+
5
+ ## Core Principles
6
+
7
+ - Test behavior, not implementation trivia.
8
+ - Keep tests deterministic.
9
+ - Prefer local fixtures over shared global state.
10
+ - Make failure messages easy to understand.
11
+
12
+ ## AAA Structure
13
+
14
+ - Arrange the minimal state needed.
15
+ - Act with one clear trigger.
16
+ - Assert the smallest useful surface.
17
+ - Split cases instead of hiding multiple expectations in one test.
18
+
19
+ ## What To Cover
20
+
21
+ - Happy path behavior.
22
+ - Validation failures that users or the agent can trigger.
23
+ - Ordering-sensitive outputs when they affect prompt quality.
24
+ - Integration seams where objects are wired together.
25
+ - Empty-state behavior.
26
+ - State transitions across multiple calls.
27
+
28
+ ## Boundary Conditions
29
+
30
+ - Zero items.
31
+ - One item.
32
+ - Multiple items.
33
+ - Missing required fields.
34
+ - Unknown IDs or names.
35
+ - Invalid enum-like values.
36
+ - Whitespace-only inputs when relevant.
37
+
38
+ ## Mock Strategy
39
+
40
+ - Mock provider calls at the boundary, not deep inside data objects.
41
+ - Use simple fake classes when behavior is small and stateful.
42
+ - Prefer monkeypatch for subprocess or environment access.
43
+ - Avoid mocking code you can exercise cheaply for real.
44
+
45
+ ## Assertions
46
+
47
+ - Assert exact text when prompt wording matters.
48
+ - Assert substrings when the surrounding text is intentionally flexible.
49
+ - Assert ordering when the agent depends on ordered lists.
50
+ - Do not assert incidental whitespace unless required.
51
+
52
+ ## Filesystem Tests
53
+
54
+ - Use `tmp_path` for skill directories and generated files.
55
+ - Keep paths relative to the temp workspace.
56
+ - Write UTF-8 text explicitly.
57
+
58
+ ## Review Checklist
59
+
60
+ - Can this test fail for the right reason?
61
+ - Does it break if the intended behavior regresses?
62
+ - Is there a simpler fixture setup?
63
+ - Is the name specific about the scenario?
64
+
65
+ ## Anti-Patterns
66
+
67
+ - One test covering multiple unrelated behaviors.
68
+ - Mocking the method under test.
69
+ - Asserting internal counters unless they are part of behavior.
70
+ - Requiring network or real API keys for unit tests.
@@ -0,0 +1,17 @@
1
+ """Team collaboration modules for BareAgent."""
2
+
3
+ from bareagent.team.autonomous import AutonomousAgent
4
+ from bareagent.team.mailbox import Message, MessageBus
5
+ from bareagent.team.manager import AgentInstance, Teammate, TeammateManager
6
+ from bareagent.team.protocols import Protocol, ProtocolFSM
7
+
8
+ __all__ = [
9
+ "AgentInstance",
10
+ "AutonomousAgent",
11
+ "Message",
12
+ "MessageBus",
13
+ "Protocol",
14
+ "ProtocolFSM",
15
+ "Teammate",
16
+ "TeammateManager",
17
+ ]