<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/gptel-tools, branch main</title>
<subtitle>My Emacs configuration
</subtitle>
<id>https://git.cjennings.net/dotemacs/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/dotemacs/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/'/>
<updated>2026-05-16T16:30:04+00:00</updated>
<entry>
<title>feat(gptel-tools): harden path validation with file-truename realpath</title>
<updated>2026-05-16T16:30:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T16:30:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=244d4c56768fcc60bd1b23fe45df7a57c7b293ec'/>
<id>urn:sha1:244d4c56768fcc60bd1b23fe45df7a57c7b293ec</id>
<content type='text'>
Resolves PATH through file-truename before applying home-directory and
read/write checks across the path-handling tools (git_status, git_log,
git_diff, move_to_trash, read_text_file, update_text_file,
write_text_file, list_directory_files, read_buffer, web_fetch).
Without the resolve step, a symlink under HOME pointing outside HOME
would pass the prefix check but the tool would act on the real target
-- a symlink-escape.

move_to_trash also tightens the trash-bin construction (treats empty
file extensions correctly) and switches the "critical directories"
list to truename-resolved canonical forms so a symlinked ~/.config
can't be trashed via an aliased path.

update_text_file fixes an off-by-one in the line-count derivation
when the source content is empty.

Each source change pairs with tests in tests/test-gptel-tools-*.el
and tests/test-update-text-file.el covering the realpath escape
paths, the empty-extension trash case, and the empty-content line-
count edge.  Combined coverage is now 100% across all ten gptel-tools
source files: 516 / 516 executable lines, 217 tests.
</content>
</entry>
<entry>
<title>feat(gptel-tools): wire web_fetch as a local tool</title>
<updated>2026-05-16T10:17:21+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T10:17:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=99d93203a867294addf4927ceec5644b9d3bf322'/>
<id>urn:sha1:99d93203a867294addf4927ceec5644b9d3bf322</id>
<content type='text'>
Fourth ADOPT entry from `docs/design/gptel-tools-shortlist.org'.
Lets gptel pull a URL into the conversation so the model can read
docs / current API shapes / etc. without me copy-pasting.

Shape:

- URL must be `http://' or `https://' (file://, ftp://, javascript:,
  scheme-less, etc. are rejected at the validator).
- HTML responses go through `pandoc -f html -t plain' so the model
  gets a reading shape that isn't full of markup; falls back to
  `w3m -dump -T text/html' if pandoc isn't on PATH; signals
  `user-error' if neither is.  Pass `raw=t' to skip stripping.
- Output capped at 200KB by default, hard cap 1MB; `max_bytes'
  argument lets the caller pick a lower cap.  Truncation reported
  inline.
- 4xx / 5xx response codes signal `error' with the code -- the
  alternative is returning an error page body, which the model
  would treat as content.

`:confirm t' on the tool because every call is a real outbound
network request.  The tool's description warns that URLs go
wherever the user-agent points, including internal networks if
that's what the URL names.

`tests/test-gptel-tools-web-fetch.el' -- 20 tests across Normal /
Boundary / Error.  URL validator covers http / https / non-string
/ empty / non-http schemes.  `--effective-max-bytes' covers default
/ low-clamp / hard-cap / passthrough.  Truncate helper covers
under-cap / at-cap / over-cap with the marker.  HTML stripper runs
against real pandoc / w3m (both installed in dev env, neither
should mangle simple markup).  Orchestrator stubs
`cj/gptel-web-fetch--retrieve' via `cl-letf' to cover normal /
raw / 4xx / 5xx / oversize / bad-scheme paths.

Wired into `cj/gptel-local-tool-features' so gptel exposes the
tool on next restart.

Note: `call-process-region' invocation flattened to a single
`with-temp-buffer' with DELETE=t -- the initial draft nested a
second temp buffer and routed output to the inner one, which got
killed before `buffer-string' on the outer ran.  Test caught it.
</content>
</entry>
<entry>
<title>feat(gptel-tools): wire git_status / git_log / git_diff as local tools</title>
<updated>2026-05-16T09:26:20+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T09:26:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=ceeae9b5e2625e23e6e3792d06a6c8122a36d18b'/>
<id>urn:sha1:ceeae9b5e2625e23e6e3792d06a6c8122a36d18b</id>
<content type='text'>
Three read-only git context tools so gptel can see what's changed
without me pasting `git status` / `git log` / `git diff` output into
every chat turn.  Builds the first batch from the ADOPT bucket in
`docs/design/gptel-tools-shortlist.org`.

Shape per tool:

- `gptel-tools/git_status.el` — `git status --short --branch` for a
  directory inside a git working tree under HOME.  Returns the
  porcelain output, or a "Clean working tree" marker when only the
  branch line is present.
- `gptel-tools/git_log.el` — `git log --oneline -nN` with an optional
  `--since` filter.  N defaults to 20, capped at 100; nil / non-
  integer / out-of-range N falls back to the default.
- `gptel-tools/git_diff.el` — `git diff [REF1 [REF2]] [-- FILE]`.
  Output capped at ~500KB so a runaway diff can't blow up context;
  truncation is reported inline.

Validation is uniform: path must resolve under HOME, must be a
directory, must be inside a git working tree (verified via
`git rev-parse --is-inside-work-tree`).  Color is disabled via
`-c color.ui=false` at the git level (`git status` doesn't accept
`--no-color` directly).

Tests run against real temp git repos created via `process-file`,
not mocked — there's nothing in gptel-tools/git_*.el that's
process-mockable in a meaningful way, and a real `git init` + a
couple of commits is cheaper than building a fake.  31 tests total:
7 for git_status, 11 for git_log, 13 for git_diff.

Wired into `cj/gptel-local-tool-features` so gptel exposes the
three tools on next restart.
</content>
</entry>
<entry>
<title>test(gptel-tools): cover the helpers across the five remaining tools</title>
<updated>2026-05-16T06:39:57+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T06:39:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=73e63b6c6850f8e14d8374c7bf6b127971cfbb08'/>
<id>urn:sha1:73e63b6c6850f8e14d8374c7bf6b127971cfbb08</id>
<content type='text'>
The gptel-tools files had zero direct coverage outside of
`update_text_file`, which landed with its rewrite earlier this
session.  This commit adds 52 tests across the five other tools.

For three of the tools the helpers were already top-level defuns
(`read_text_file`, `list_directory_files`, `move_to_trash`).  The
other two had their main bodies inlined into the `gptel-make-tool`
lambda -- I extracted them so the work is testable without mocking
gptel itself:

  read_buffer.el     -&gt; `cj/read-buffer--get-content`
  write_text_file.el -&gt; `cj/write-text-file--run` plus
                        `--validate-path`, `--backup-name`,
                        `--ensure-parent`

Test files, by tool:

- read_buffer.el          (5 tests):  normal, empty, buffer-object,
  text-property-stripping, missing buffer.
- write_text_file.el     (10 tests):  validate-path, backup-name
  shape, ensure-parent (creates missing / rejects unwritable), run
  with normal / overwrite / existing-no-overwrite / empty content /
  outside-home.
- read_text_file.el      (12 tests):  validate-file-path (normal +
  three error shapes), metadata plist shape, size limits (no-op /
  hard cap / warning bypass with no-confirm), binary detection
  (text vs null-byte), special-type EPUB and generic-binary paths.
- list_directory_files.el (15 tests):  mode-to-permissions (file /
  dir / executable), get-file-info (file / directory), extension
  filter (keep / drop / always-dir / nil-extension), format-file-
  entry, list-directory flat / recursive / error, format-output
  with and without files.
- move_to_trash.el       (10 tests):  unique-name (no conflict /
  conflict with timestamp / no-extension), validate-path (HOME / /tmp
  / outside / critical-dir / missing), perform on file and
  directory.

Each test file uses the same load-path / gptel-stub idiom
(`eval-and-compile` block, gptel stub when the real package isn't
available) so the byte-compile hook is happy.
</content>
</entry>
<entry>
<title>feat(gptel-tools): wire update_text_file as a local tool with tests</title>
<updated>2026-05-16T06:18:35+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-16T06:18:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=531f0f19f298e28b42dfb216f6008a1cbc6164d3'/>
<id>urn:sha1:531f0f19f298e28b42dfb216f6008a1cbc6164d3</id>
<content type='text'>
I rewrote `update_text_file.el` in pure Elisp.  The previous version
shelled out to sed for everything, had a stray quote terminator at EOF
(line 149) that broke loading, produced literal backslash-n where
actual newlines were expected, and prompted via `y-or-n-p` redundantly
with gptel's own `:confirm t` flag.

The five operations -- replace, append, prepend, insert-at-line,
delete-lines -- split into pure string transforms that test without
touching the disk.  The file-level wrapper validates the path, enforces
a 10MB size limit, takes a timestamped backup, and writes atomically.
No backup is created when the operation is a no-op.  Patterns are
literal substrings (not regex) so the model can't trip over
metacharacter quoting.

`tests/test-update-text-file.el` covers Normal / Boundary / Error per
operation plus the file-level wrapper.  48 tests green.  Added
`update_text_file` to `cj/gptel-local-tool-features` so gptel exposes
the tool after restart.
</content>
</entry>
<entry>
<title>perf: Merge performance branch - org-agenda cache, tests, and inbox zero</title>
<updated>2025-11-12T08:46:27+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2025-11-12T08:46:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=9e7787445878cfa719f335a9491e3a2a864385b7'/>
<id>urn:sha1:9e7787445878cfa719f335a9491e3a2a864385b7</id>
<content type='text'>
This squash merge combines 4 commits from the performance branch:

## Performance Improvements
- **org-agenda cache**: Cache org-agenda file list to reduce rebuild time
  - Eliminates redundant file system scans on each agenda view
  - Added tests for cache invalidation and updates

- **org-refile cache**: Optimize org-refile target building (15-20s → instant)
  - Cache eliminates bottleneck when capturing tasks

## Test Suite Improvements
- Fixed all 18 failing tests → 0 failures (107 test files passing)
- Deleted 9 orphaned test files (filesystem lib, dwim-shell-security, org-gcal-mock)
- Fixed missing dependencies (cj/custom-keymap, user-constants)
- Fixed duplicate test definitions and wrong variable names
- Adjusted benchmark timing thresholds for environment variance
- Added comprehensive tests for org-agenda cache functionality

## Documentation &amp; Organization
- **todo.org recovery**: Restored 1,176 lines lost in truncation
  - Recovered Methods 4, 5, 6 + Resolved + Inbox sections
  - Removed 3 duplicate TODO entries

- **Inbox zero**: Triaged 12 inbox items → 0 items
  - Completed: 3 tasks marked DONE (tests, transcription)
  - Relocated: 4 tasks to appropriate V2MOM Methods
  - Deleted: 4 duplicates/vague tasks
  - Merged: 1 task as subtask

## Files Changed
- 58 files changed, 29,316 insertions(+), 2,104 deletions(-)
- Tests: All 107 test files passing
- Codebase: Cleaner, better organized, fully tested
</content>
</entry>
<entry>
<title>maint: gptel: fetch_page: needs major work so disabling for now</title>
<updated>2025-10-20T16:34:00+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2025-10-20T16:34:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=2bfd71087d19d01bbc668216246ecfff0e758337'/>
<id>urn:sha1:2bfd71087d19d01bbc668216246ecfff0e758337</id>
<content type='text'>
</content>
</entry>
<entry>
<title>changing repositories</title>
<updated>2025-10-12T16:47:26+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2025-10-12T16:47:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=092304d9e0ccc37cc0ddaa9b136457e56a1cac20'/>
<id>urn:sha1:092304d9e0ccc37cc0ddaa9b136457e56a1cac20</id>
<content type='text'>
</content>
</entry>
</feed>
