drydock-cli 3.0.60__tar.gz → 3.0.61__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.
Files changed (77) hide show
  1. {drydock_cli-3.0.60/drydock_cli.egg-info → drydock_cli-3.0.61}/PKG-INFO +47 -1
  2. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/README.md +46 -0
  3. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/skills.py +19 -0
  4. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/app.py +30 -7
  5. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tuning.py +25 -1
  6. {drydock_cli-3.0.60 → drydock_cli-3.0.61/drydock_cli.egg-info}/PKG-INFO +47 -1
  7. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/SOURCES.txt +1 -0
  8. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/pyproject.toml +1 -1
  9. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_skills.py +25 -0
  10. drydock_cli-3.0.61/tests/test_system_prompt_help.py +15 -0
  11. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/LICENSE +0 -0
  12. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/NOTICE +0 -0
  13. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/__init__.py +0 -0
  14. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/__main__.py +0 -0
  15. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/agent.py +0 -0
  16. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/bash_safety.py +0 -0
  17. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/cli.py +0 -0
  18. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/compaction.py +0 -0
  19. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/config.py +0 -0
  20. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/detect.py +0 -0
  21. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/gittools.py +0 -0
  22. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/graphrag.py +0 -0
  23. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/guards.py +0 -0
  24. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/loop_detect.py +0 -0
  25. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/mcp.py +0 -0
  26. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/providers.py +0 -0
  27. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tool_registry.py +0 -0
  28. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tools/__init__.py +0 -0
  29. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/__init__.py +0 -0
  30. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/approval.py +0 -0
  31. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/messages.py +0 -0
  32. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/tui/widgets.py +0 -0
  33. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock/web.py +0 -0
  34. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/dependency_links.txt +0 -0
  35. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/entry_points.txt +0 -0
  36. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/requires.txt +0 -0
  37. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/drydock_cli.egg-info/top_level.txt +0 -0
  38. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/setup.cfg +0 -0
  39. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_approval.py +0 -0
  40. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_back_command.py +0 -0
  41. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_output_bounding.py +0 -0
  42. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_process_group.py +0 -0
  43. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_safety.py +0 -0
  44. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_bash_timeout_network.py +0 -0
  45. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_cli_agents.py +0 -0
  46. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_compact_command.py +0 -0
  47. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_compaction.py +0 -0
  48. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_config.py +0 -0
  49. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_config_migration.py +0 -0
  50. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_context_limit_config.py +0 -0
  51. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_detect.py +0 -0
  52. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_dispatch.py +0 -0
  53. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_empty_response.py +0 -0
  54. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_failure_loop.py +0 -0
  55. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_first_run_setup.py +0 -0
  56. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_gittools.py +0 -0
  57. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_graphrag.py +0 -0
  58. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_guards_and_tools.py +0 -0
  59. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_hallucinated_tools.py +0 -0
  60. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_leaked_tool_call.py +0 -0
  61. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_loop_detect.py +0 -0
  62. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_mcp.py +0 -0
  63. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_oneshot_unreachable.py +0 -0
  64. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_plan_autocontinue.py +0 -0
  65. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_providers_unreachable.py +0 -0
  66. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_read_index.py +0 -0
  67. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_runaway_repetition.py +0 -0
  68. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_stop.py +0 -0
  69. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_streaming_newlines.py +0 -0
  70. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_subagent.py +0 -0
  71. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_todo.py +0 -0
  72. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tool_arg_parsing.py +0 -0
  73. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tools_undo.py +0 -0
  74. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tui.py +0 -0
  75. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_tuning.py +0 -0
  76. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_vision_input.py +0 -0
  77. {drydock_cli-3.0.60 → drydock_cli-3.0.61}/tests/test_web_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.60
3
+ Version: 3.0.61
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -71,6 +71,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
71
71
  `~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
72
72
  - **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
73
73
 
74
+ ## Slash commands
75
+
76
+ Typed into the prompt. The agent also knows these, so you can just **ask it**
77
+ ("how do I add my own docs?") and it'll point you to the right one.
78
+
79
+ | Command | What it does |
80
+ | --- | --- |
81
+ | `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
82
+ | `/graphrag add <path>` | Incrementally add more documents to the base |
83
+ | `/graphrag query <q>` | Test what the base returns (no model) |
84
+ | `/graphrag status` · `clear` | List indexed sources · wipe the base |
85
+ | `/skills` | List your skills |
86
+ | `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
87
+ | `/<name>` | Run a skill |
88
+ | `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
89
+ | `/mcp` | List connected MCP servers + their tools |
90
+ | `/model` · `/cwd` | Show/set model & endpoint · working directory |
91
+ | `/undo` · `/back` | Revert the last write · rewind the last turn |
92
+ | `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
93
+ | `/help` · `/quit` | Help · exit |
94
+
95
+ ### Knowledge base (GraphRAG) — ingesting your documents
96
+
97
+ ```
98
+ /graphrag build ./docs # index a file or a whole folder
99
+ /graphrag add ./more_docs # add more later, incrementally
100
+ /graphrag query "how are refunds handled?" # check retrieval
101
+ /graphrag status # what's indexed
102
+ ```
103
+
104
+ Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
105
+ tool) when a question touches your material. Ingests text formats
106
+ (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
107
+ (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
108
+ `<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
109
+
110
+ ### Custom skills
111
+
112
+ ```
113
+ /skills new commitmsg Write a concise conventional-commit message for: $ARGS
114
+ /commitmsg the staged auth changes # runs the skill with $ARGS substituted
115
+ ```
116
+
117
+ Skills are markdown files in `~/.drydock/skills/` (personal) or
118
+ `<project>/.drydock/skills/` (project); `/skills new` writes one for you.
119
+
74
120
  ## Install
75
121
 
76
122
  ```bash
@@ -49,6 +49,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
49
49
  `~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
50
50
  - **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
51
51
 
52
+ ## Slash commands
53
+
54
+ Typed into the prompt. The agent also knows these, so you can just **ask it**
55
+ ("how do I add my own docs?") and it'll point you to the right one.
56
+
57
+ | Command | What it does |
58
+ | --- | --- |
59
+ | `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
60
+ | `/graphrag add <path>` | Incrementally add more documents to the base |
61
+ | `/graphrag query <q>` | Test what the base returns (no model) |
62
+ | `/graphrag status` · `clear` | List indexed sources · wipe the base |
63
+ | `/skills` | List your skills |
64
+ | `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
65
+ | `/<name>` | Run a skill |
66
+ | `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
67
+ | `/mcp` | List connected MCP servers + their tools |
68
+ | `/model` · `/cwd` | Show/set model & endpoint · working directory |
69
+ | `/undo` · `/back` | Revert the last write · rewind the last turn |
70
+ | `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
71
+ | `/help` · `/quit` | Help · exit |
72
+
73
+ ### Knowledge base (GraphRAG) — ingesting your documents
74
+
75
+ ```
76
+ /graphrag build ./docs # index a file or a whole folder
77
+ /graphrag add ./more_docs # add more later, incrementally
78
+ /graphrag query "how are refunds handled?" # check retrieval
79
+ /graphrag status # what's indexed
80
+ ```
81
+
82
+ Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
83
+ tool) when a question touches your material. Ingests text formats
84
+ (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
85
+ (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
86
+ `<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
87
+
88
+ ### Custom skills
89
+
90
+ ```
91
+ /skills new commitmsg Write a concise conventional-commit message for: $ARGS
92
+ /commitmsg the staged auth changes # runs the skill with $ARGS substituted
93
+ ```
94
+
95
+ Skills are markdown files in `~/.drydock/skills/` (personal) or
96
+ `<project>/.drydock/skills/` (project); `/skills new` writes one for you.
97
+
52
98
  ## Install
53
99
 
54
100
  ```bash
@@ -77,6 +77,25 @@ def skills_dirs(cwd: str) -> list[Path]:
77
77
  return [Path.home() / ".drydock" / "skills", Path(cwd) / ".drydock" / "skills"]
78
78
 
79
79
 
80
+ def create_skill(name: str, body: str, *, description: str = "", scope: str = "user",
81
+ cwd: str = ".") -> Path:
82
+ """Author a new skill markdown file and return its path. scope='user' writes
83
+ to ~/.drydock/skills (available everywhere); scope='project' writes to
84
+ <cwd>/.drydock/skills. Raises ValueError on a bad name / empty body."""
85
+ name = (name or "").strip().lower()
86
+ body = (body or "").strip()
87
+ if not _NAME_RE.match(name):
88
+ raise ValueError("skill name must be letters/digits/-/_ (e.g. 'review')")
89
+ if not body:
90
+ raise ValueError("a skill needs a prompt body")
91
+ d = (Path(cwd) if scope == "project" else Path.home()) / ".drydock" / "skills"
92
+ d.mkdir(parents=True, exist_ok=True)
93
+ path = d / f"{name}.md"
94
+ front = f"---\nname: {name}\ndescription: {description or 'user skill'}\n---\n"
95
+ path.write_text(front + body + "\n", encoding="utf-8")
96
+ return path
97
+
98
+
80
99
  def load_skills(cwd: str) -> dict[str, Skill]:
81
100
  """Load all skills; later dirs (project) override earlier (user) by name."""
82
101
  skills: dict[str, Skill] = {}
@@ -492,7 +492,7 @@ class DrydockApp(App):
492
492
  " /compact shrink old context to free up the window\n"
493
493
  " /graphrag ingest docs into a knowledge base the agent can use\n"
494
494
  " build <path> · add <path> · query <q> · status · clear\n"
495
- " /skills list your reusable /<name> skills\n"
495
+ " /skills list skills · /skills new <name> <prompt> to create one\n"
496
496
  " /loop /loop <count> <prompt> — repeat a prompt (Esc stops)\n"
497
497
  " /mcp list connected MCP servers and their tools\n"
498
498
  " /clear reset the conversation\n"
@@ -504,7 +504,7 @@ class DrydockApp(App):
504
504
  elif cmd == "/loop":
505
505
  self._cmd_loop(arg)
506
506
  elif cmd == "/skills":
507
- self._cmd_skills()
507
+ self._cmd_skills(arg)
508
508
  elif cmd[1:] in self._skills:
509
509
  self._run_skill(self._skills[cmd[1:]], arg)
510
510
  else:
@@ -554,19 +554,42 @@ class DrydockApp(App):
554
554
  self._mount(UserMessage(prompt))
555
555
  self._begin(prompt)
556
556
 
557
- def _cmd_skills(self) -> None:
557
+ def _cmd_skills(self, arg: str = "") -> None:
558
+ """List skills, or create one: /skills new <name> <prompt…> (use $ARGS in
559
+ the prompt for trailing input). Created skills are usable as /<name>
560
+ immediately."""
561
+ from drydock import skills as skillsmod
562
+
563
+ parts = arg.split(maxsplit=2)
564
+ if parts and parts[0].lower() == "new":
565
+ if len(parts) < 3:
566
+ self._info(
567
+ "usage: /skills new <name> <prompt text>\n"
568
+ " e.g. /skills new review Run GitDiff, then review the changes "
569
+ "for bugs.\n (use $ARGS in the prompt for trailing input)"
570
+ )
571
+ return
572
+ name, body = parts[1], parts[2]
573
+ try:
574
+ path = skillsmod.create_skill(name, body, cwd=self.config.get("cwd") or ".")
575
+ except ValueError as e:
576
+ self._info(f"Couldn't create skill: {e}")
577
+ return
578
+ self._skills = skillsmod.load_skills(self.config.get("cwd") or ".") # reload
579
+ self._info(f"✓ Created skill /{name.lower()} ({path}). Invoke it as /{name.lower()}.")
580
+ return
558
581
  if not self._skills:
559
582
  self._info(
560
- "No skills yet. Create one as a markdown file in "
561
- "~/.drydock/skills/<name>.md (or <project>/.drydock/skills/), "
562
- "then invoke it as /<name>. The body is the prompt; use $ARGS for "
563
- "trailing input."
583
+ "No skills yet. Create one with: /skills new <name> <prompt text>\n"
584
+ "(or drop a markdown file in ~/.drydock/skills/<name>.md). Invoke as "
585
+ "/<name>; use $ARGS for trailing input."
564
586
  )
565
587
  return
566
588
  lines = ["Skills (invoke as /<name>):"]
567
589
  for name in sorted(self._skills):
568
590
  sk = self._skills[name]
569
591
  lines.append(f" /{name}" + (f" — {sk.description}" if sk.description else ""))
592
+ lines.append("Create one: /skills new <name> <prompt text>")
570
593
  self._info("\n".join(lines))
571
594
 
572
595
  def _run_skill(self, skill, arg: str) -> None:
@@ -206,9 +206,33 @@ def filter_tool_schemas(tool_schemas: list, model: str | None) -> list:
206
206
  return [t for t in tool_schemas if t.get("name") not in GEMMA_DISABLED_TOOLS]
207
207
 
208
208
 
209
+ # Reference for the model so it can answer "how do I …" questions about Drydock
210
+ # itself. The user TYPES these slash commands; you (the model) do NOT run them —
211
+ # you just explain the right one when asked. Appended to every system prompt.
212
+ _DRYDOCK_COMMANDS_HELP = (
213
+ "\n\nAbout Drydock (the tool you run inside). If the user asks how to do "
214
+ "something with Drydock, tell them the slash command — they type it, you do "
215
+ "NOT run it:\n"
216
+ "- Knowledge base from their docs: `/graphrag build <path>` (a file or "
217
+ "folder), `/graphrag add <path>` to add more, `/graphrag query <q>` to test "
218
+ "it, `/graphrag status`, `/graphrag clear`. Once built, you automatically use "
219
+ "the `Knowledge` tool to draw on it. It ingests text formats (md/txt/code/"
220
+ "json/yaml/…); PDFs/Word must be converted to text first.\n"
221
+ "- Custom skills (reusable `/<name>` prompts): `/skills new <name> <prompt "
222
+ "text>` creates one (use $ARGS in the prompt for trailing input); `/skills` "
223
+ "lists them; then they run it as `/<name>`.\n"
224
+ "- Other: `/model` (model/endpoint), `/cwd`, `/undo` (revert last write), "
225
+ "`/back` (rewind a turn), `/compact` (shrink context), `/loop <n> <prompt>` "
226
+ "(repeat a prompt), `/mcp` (connected MCP servers), `/status`, `/clear`, "
227
+ "`/help`, `/quit`. Internet + git are tools you call yourself "
228
+ "(WebSearch/WebFetch, GitStatus/GitDiff/GitLog/GitCommit)."
229
+ )
230
+
231
+
209
232
  def system_prompt_for_model(model: str | None) -> str:
210
233
  """Return the system prompt best suited to the model."""
211
- return _GEMMA_SYSTEM_PROMPT if is_gemma(model) else _DEFAULT_SYSTEM_PROMPT
234
+ base = _GEMMA_SYSTEM_PROMPT if is_gemma(model) else _DEFAULT_SYSTEM_PROMPT
235
+ return base + _DRYDOCK_COMMANDS_HELP
212
236
 
213
237
 
214
238
  def thinking_level_for_turn(turn_count: int, is_user_turn: bool) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.60
3
+ Version: 3.0.61
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -71,6 +71,52 @@ A full agentic CLI harness — every tool below is clean-room and dependency-fre
71
71
  `~/.drydock/skills/` (or `<project>/.drydock/skills/`); `$ARGS` substitution.
72
72
  - **Loops** — `/loop <count> <prompt>` runs a prompt iteratively (Esc stops).
73
73
 
74
+ ## Slash commands
75
+
76
+ Typed into the prompt. The agent also knows these, so you can just **ask it**
77
+ ("how do I add my own docs?") and it'll point you to the right one.
78
+
79
+ | Command | What it does |
80
+ | --- | --- |
81
+ | `/graphrag build <path>` | Build a knowledge base from a file or folder of docs/code |
82
+ | `/graphrag add <path>` | Incrementally add more documents to the base |
83
+ | `/graphrag query <q>` | Test what the base returns (no model) |
84
+ | `/graphrag status` · `clear` | List indexed sources · wipe the base |
85
+ | `/skills` | List your skills |
86
+ | `/skills new <name> <prompt>` | Create a reusable `/<name>` skill (use `$ARGS` for input) |
87
+ | `/<name>` | Run a skill |
88
+ | `/loop <count> <prompt>` | Repeat a prompt N times (Esc stops) |
89
+ | `/mcp` | List connected MCP servers + their tools |
90
+ | `/model` · `/cwd` | Show/set model & endpoint · working directory |
91
+ | `/undo` · `/back` | Revert the last write · rewind the last turn |
92
+ | `/compact` · `/status` · `/clear` | Shrink context · session stats · reset |
93
+ | `/help` · `/quit` | Help · exit |
94
+
95
+ ### Knowledge base (GraphRAG) — ingesting your documents
96
+
97
+ ```
98
+ /graphrag build ./docs # index a file or a whole folder
99
+ /graphrag add ./more_docs # add more later, incrementally
100
+ /graphrag query "how are refunds handled?" # check retrieval
101
+ /graphrag status # what's indexed
102
+ ```
103
+
104
+ Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
105
+ tool) when a question touches your material. Ingests text formats
106
+ (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
107
+ (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
108
+ `<project>/.drydock/graphrag.json` — clean-room, stdlib-only, no embeddings.
109
+
110
+ ### Custom skills
111
+
112
+ ```
113
+ /skills new commitmsg Write a concise conventional-commit message for: $ARGS
114
+ /commitmsg the staged auth changes # runs the skill with $ARGS substituted
115
+ ```
116
+
117
+ Skills are markdown files in `~/.drydock/skills/` (personal) or
118
+ `<project>/.drydock/skills/` (project); `/skills new` writes one for you.
119
+
74
120
  ## Install
75
121
 
76
122
  ```bash
@@ -65,6 +65,7 @@ tests/test_skills.py
65
65
  tests/test_stop.py
66
66
  tests/test_streaming_newlines.py
67
67
  tests/test_subagent.py
68
+ tests/test_system_prompt_help.py
68
69
  tests/test_todo.py
69
70
  tests/test_tool_arg_parsing.py
70
71
  tests/test_tools_undo.py
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
  # PyPI distribution name is drydock-cli (the established install name, continued
8
8
  # from the retired v2 fork); the import package + CLI command stay `drydock`.
9
9
  name = "drydock-cli"
10
- version = "3.0.60"
10
+ version = "3.0.61"
11
11
  description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -54,3 +54,28 @@ def test_empty_body_skipped(tmp_path):
54
54
 
55
55
  def test_no_skills_dir_is_empty(tmp_path):
56
56
  assert skills.load_skills(str(tmp_path)) == {}
57
+
58
+
59
+ def test_create_skill_user_scope(tmp_path, monkeypatch):
60
+ monkeypatch.setattr(skills.Path, "home", lambda: tmp_path)
61
+ path = skills.create_skill("review", "Run GitDiff then review for bugs.",
62
+ description="Review the diff")
63
+ assert path.exists()
64
+ loaded = skills.load_skills(str(tmp_path / "proj")) # user-scope visible anywhere
65
+ assert "review" in loaded and "review for bugs" in loaded["review"].body
66
+ assert loaded["review"].description == "Review the diff"
67
+
68
+
69
+ def test_create_skill_rejects_bad_name_and_empty_body(tmp_path, monkeypatch):
70
+ monkeypatch.setattr(skills.Path, "home", lambda: tmp_path)
71
+ import pytest
72
+ with pytest.raises(ValueError):
73
+ skills.create_skill("bad name!", "body")
74
+ with pytest.raises(ValueError):
75
+ skills.create_skill("ok", " ")
76
+
77
+
78
+ def test_create_skill_project_scope(tmp_path):
79
+ path = skills.create_skill("explain", "Explain $ARGS.", scope="project", cwd=str(tmp_path))
80
+ assert str(tmp_path) in str(path)
81
+ assert skills.load_skills(str(tmp_path))["explain"].render("auth.py") == "Explain auth.py."
@@ -0,0 +1,15 @@
1
+ """The system prompt must teach the model Drydock's own slash commands so it can
2
+ answer 'how do I add documents / make a skill' when a user asks."""
3
+ from __future__ import annotations
4
+
5
+ from drydock.tuning import system_prompt_for_model
6
+
7
+
8
+ def test_prompt_documents_graphrag_and_skills():
9
+ for model in ("gemma4", "some-other-model"):
10
+ p = system_prompt_for_model(model)
11
+ assert "/graphrag build" in p and "/graphrag add" in p
12
+ assert "/skills new" in p
13
+ assert "Knowledge" in p
14
+ # framed so the model explains, not runs, the command
15
+ assert "you do" in p.lower() and "not run" in p.lower()
File without changes
File without changes
File without changes