aboutsummaryrefslogtreecommitdiff
path: root/docs/design/gptel-git-tools-magit-backend.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-16 05:58:14 -0500
committerCraig Jennings <c@cjennings.net>2026-05-16 05:58:14 -0500
commitd76efe15eb5718fb859df87ee12306bcbc56b07c (patch)
treeb9af43b4efed2116cb15f10100adb3a98a0682c4 /docs/design/gptel-git-tools-magit-backend.org
parent99d93203a867294addf4927ceec5644b9d3bf322 (diff)
downloaddotemacs-d76efe15eb5718fb859df87ee12306bcbc56b07c.tar.gz
dotemacs-d76efe15eb5718fb859df87ee12306bcbc56b07c.zip
docs(design): three new gptel / agentic design notes
- gptel-git-tools-magit-backend.org -- spec for reimplementing the three current git_* tools on top of magit, plus three new tools (blame, show, branches). - gptel-agentic-tool-ideas.org -- brainstorm seed for additional agentic gptel tools. - agentic-knowledgebase.org -- design sketch for using org-roam as the agent's durable project memory with org-agenda as the execution layer.
Diffstat (limited to 'docs/design/gptel-git-tools-magit-backend.org')
-rw-r--r--docs/design/gptel-git-tools-magit-backend.org192
1 files changed, 192 insertions, 0 deletions
diff --git a/docs/design/gptel-git-tools-magit-backend.org b/docs/design/gptel-git-tools-magit-backend.org
new file mode 100644
index 00000000..94fbb0ce
--- /dev/null
+++ b/docs/design/gptel-git-tools-magit-backend.org
@@ -0,0 +1,192 @@
+#+TITLE: Design: gptel git tools on a magit backend
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-05-16
+
+* Status
+
+Draft. Supersedes the three current git-tool implementations
+(=gptel-tools/git_status.el=, =gptel-tools/git_log.el=,
+=gptel-tools/git_diff.el=) shipped in commit =ceeae9b5=. Trigger:
+Craig flagged that magit already does much of this and could carry
+the backend for more git tools cheaply.
+
+* Problem
+
+The three current git_* tools shell out to git directly via
+=process-file= and parse stdout. Each carries:
+
+- Its own =--is-inside-work-tree= path-validation step.
+- Its own =-c color.ui=false= color suppression workaround (`git
+ status' doesn't accept =--no-color= the way `git log' / `git diff'
+ do).
+- Boilerplate to set up a temp buffer, run =process-file=, capture
+ output, return the string.
+
+There's also an opportunity cost: adding more git context tools
+(=git_blame=, =git_show=, =git_branches=, etc.) would mean
+duplicating the same boilerplate per tool.
+
+* Wins from a magit backend
+
+Three concrete things magit provides:
+
+1. *Path validation via =magit-toplevel=.* One call replaces the
+ two-step =process-file= + =rev-parse --is-inside-work-tree=
+ check. Returns the working-tree root or nil.
+
+2. *Process plumbing via =magit-git-insert= / =magit-git-string= /
+ =magit-git-lines=.* These wrap git invocation with magit's
+ environment, encoding handling, and the right color posture.
+ Drops the per-subcommand color-flag bikeshedding.
+
+3. *Typed helpers for higher-level concepts* -- =magit-get-current-branch=,
+ =magit-list-branches=, =magit-rev-ancestor-p=, etc. Most
+ relevant for the *new* tools (branches, show, blame), not the
+ three we already wrote.
+
+What magit doesn't give us: high-level "give me status as a string"
+helpers. =magit-status= / =magit-log-current= etc. populate
+interactive magit buffers, not strings. For tool output we'd still
+call =magit-git-insert "status" "--short" "--branch"= and grab the
+buffer string. Same shape, less boilerplate.
+
+* Costs
+
+- *Magit loads on first invocation* of any git_* tool. Magit pulls
+ in transient, with-editor, magit-section, magit-core -- heavyweight.
+ Mitigation: lazy =(require 'magit)= inside each tool's function
+ body so cold-start Emacs sessions don't pay the cost unless the
+ user actually calls a git tool.
+- *Tools no longer portable* to a no-magit Emacs. Acceptable here
+ because magit is a non-negotiable in this config; a future
+ drop-in distribution would need to publish a magit-free fallback.
+
+* Proposed shape
+
+** Single-file module: =gptel-tools/git_tools.el=
+
+The current "one file per tool" convention exists because the
+existing tools share little. These six tools share a lot
+(validate-path, run-git, truncate-output), so a single file with
+shared helpers is more honest.
+
+** Shared helpers
+
+- =cj/gptel-git--toplevel-or-error PATH=
+ - Wraps =magit-toplevel=. Signals =user-error= when PATH escapes
+ HOME, doesn't exist, or isn't inside a working tree.
+ - Returns the resolved working-tree root on success.
+
+- =cj/gptel-git--insert ARGS...=
+ - Wraps =magit-git-insert= in a =with-temp-buffer=, returns
+ =buffer-string=. Single chokepoint for color / encoding / error
+ handling.
+
+- =cj/gptel-git--truncate TEXT MAX-BYTES=
+ - Caps output, appends a one-line truncation marker when
+ triggered.
+
+ Open question: consolidate the matching helper from =web_fetch.el=
+ (=cj/gptel-web-fetch--truncate=) and the
+ =cj/update-text-file--*= analogue into a shared
+ =cj/gptel-tools--truncate-bytes= in =system-lib.el=, or keep
+ per-tool.
+
+** Six tools
+
+| Name | Magit-flavored shape |
+|------------------+--------------------------------------------------------------------|
+| =git_status= | =magit-git-insert "status" "--short" "--branch"= |
+| =git_log= | =magit-git-insert "log" "--oneline" (format "-n%d" N) ?--since= |
+| =git_diff= | =magit-git-insert "diff" REF1 REF2 "--" FILE= (each optional) |
+| =git_blame= | =magit-git-insert "blame" "--line-porcelain" FILE [-L S,E]= |
+| =git_show= | =magit-git-insert "show" REF= (message + full diff) |
+| =git_branches= | =magit-list-branches= (optionally filtered by =--list PATTERN=) |
+
+Each tool:
+- Validates =path= via =cj/gptel-git--toplevel-or-error=.
+- Calls =cj/gptel-git--insert= with the appropriate args.
+- Truncates via =cj/gptel-git--truncate=.
+- Registered as a separate tool with =gptel-make-tool= for
+ description / argv clarity at the model side.
+
+** Caps
+
+| Tool | Default cap | Hard cap |
+|---------------+----------------+--------------|
+| =git_status= | uncapped | uncapped |
+| =git_log= | 100 commits | 100 commits |
+| =git_diff= | 500 KB | 500 KB |
+| =git_blame= | 500 KB | 500 KB |
+| =git_show= | 500 KB | 500 KB |
+| =git_branches=| uncapped | uncapped |
+
+=git_log='s cap is on commit count; the rest cap output bytes.
+
+** :confirm posture
+
+All six tools are read-only. Same posture as the current
+implementation: =:confirm nil= (the model can call them
+autonomously, since they can't mutate state). The current
+git_status / git_log / git_diff already ship with =:confirm nil= --
+keeping it.
+
+** Tests
+
+Single file =tests/test-gptel-tools-git-tools.el=, replacing the
+three current per-tool test files. Real temp git repos via
+=process-file= (same pattern as current tests). Coverage per tool:
+Normal / Boundary / Error.
+
+Rough count: ~12 shared-helper tests (validator, insert wrapper,
+truncate) + ~7 per tool × 6 tools = ~54 tests total.
+
+* Migration
+
+1. Delete =gptel-tools/git_status.el=, =git_log.el=, =git_diff.el=.
+2. Delete =tests/test-gptel-tools-git-status.el=,
+ =test-gptel-tools-git-log.el=, =test-gptel-tools-git-diff.el=.
+3. Create =gptel-tools/git_tools.el= containing all six tools +
+ shared helpers.
+4. Create =tests/test-gptel-tools-git-tools.el=.
+5. Update =cj/gptel-local-tool-features= in =modules/ai-config.el=:
+ replace the three =git_*= symbols with one =git_tools= symbol
+ (or six if each tool wants its own feature file -- decide during
+ implementation).
+6. Make sure =modules/ai-config.el= can re-load without breaking the
+ live gptel session if the old tool symbols are still registered
+ from a prior Emacs.
+
+* Risks
+
+| Risk | Mitigation |
+|-------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------|
+| Magit load slows first git_* tool call | One-time hit per session, scoped to the tool's :function body. Acceptable for opt-in tools. |
+| Tool registration name collision with the old git_* symbols | Use distinct names (git_status / git_log / git_diff stay; new tools join them). Or remove + restart. |
+| =magit-toplevel= behavior on TRAMP / remote paths | Validator rejects paths outside HOME first, so TRAMP paths can't reach magit-toplevel. |
+| =git_blame= exposes code surfaces the model shouldn't read | =:confirm nil= is the wrong posture if blame is sensitive. Open question for review. |
+| =git_show= reveals past-self commit message wording | Same as blame -- low risk on personal repo, but worth flagging. |
+
+* Open questions
+
+1. Build all six tools in one push, or phase status/log/diff first
+ and add blame/show/branches in a follow-up? My read: one push.
+ The helpers are shared, marginal cost of three more tools is
+ small, and the model gets meaningfully more useful git context.
+2. Consolidate the output-truncation helper into =system-lib.el=,
+ touching =web_fetch.el= and =update_text_file.el= for a cleaner
+ API? Or defer that to a separate refactor commit?
+3. =git_blame= and =git_show= -- =:confirm nil= or =:confirm t=?
+ Personal repo lowers the stakes but the model could ask for
+ blame on /any/ file under HOME.
+4. Tool feature symbols: one =git_tools= entry in
+ =cj/gptel-local-tool-features=, or six (one per tool)?
+ Currently each tool lives in its own provide-symbol file. With
+ the single-file design we'd register one feature symbol that
+ loads all six.
+
+* Effort estimate
+
+M (1-3 hours). Helpers + six tool wrappers + ~50 tests + migration.
+Most of the time is test authoring; the production code is small
+because magit absorbs the boilerplate.