Skip to content

Conversation

munen
Copy link
Contributor

@munen munen commented May 23, 2025

Show complete file paths when adding files to context instead of just the basename. This enables LLMs to:

  • Use file paths correctly when calling tools/functions
  • Understand semantic meaning from directory structure
  • Distinguish between files with identical names in different locations

Longer explanation:

Problem

Currently, when adding files to the LLM context via gptel-context, only the filename (basename) is displayed to both the user and the LLM. This creates two significant limitations:

  1. Tool/Function calling: When LLMs need to use tools or functions that operate on files, they require the full path to correctly reference the file. Providing only the filename makes it impossible for the LLM to construct valid file operations.

  2. Semantic context loss: File paths often contain meaningful semantic information about the codebase structure. For example:

    • docs/api/README.md vs docs/user/README.md vs README.md

    The directory structure provides crucial context about the file's purpose and relationship to other parts of the project.

Solution

This change modifies the context insertion functions in gptel-context.el to display the full file path instead of just the filename:

  • gptel-context--insert-file-string: Now shows full path in the context header
  • Context overlay insertion: Now shows full path when adding file-based context

@karthink
Copy link
Owner

@munen Showing the basename is intentional. We assumed that the user is fine with sharing the file contents with the LLM since they explicitly added it, but that they might not want to expose the full path. The full path might have sensitive components. It's not clear from the context interface that the absolute path of the file will be sent to the LLM as well.

What are your thoughts on this?

@munen
Copy link
Contributor Author

munen commented May 24, 2025

Thank you for getting back to me quickly 🙏

Before having tools, I agree with you. Then, there also was no good reason to add the path, so it was wasted tokens. Now, with tools, I see these things in my interactions often:

  1. I add the project directory to the prompt.
  2. Some llms (haiku and gpt for example) are consistently not capable to infer from the project directory and the basename the correct path and keep trying moot combinations of things. I was not expecting that, but it is very much reproducible.
  3. Even if I use a llm that's capable of doing the simple concatenation (claude, gemini), then I often have multiple context files, so just giving the project directory is not enough, I have to search for all file paths to add to the prompt. At this point, it's easier to not use the otherwise excellent gptel context tooling and rely on tools to pick up the right files and context for themselves. Of course, this is firstly slower, leads to bigger context windows and is therefore more expensive. Also, it needs supervision, because with giving the llm free roam to find stuff, actually sensitive content might be added to the context.

Apart from the UX issue, I'd argue that file contents should be generally more sensitive than the path. If we're talking about sensitive information, then the main question is: "What is the attack vector?". In my understanding it's giving sensitive data which is then used for training. Giving away API secrets, for example, is probably not smart, because should it be reproduced, it can be used by anyone. However, giving away a path, even should it be reproduced by the llm, the attacker wouldn't know who has this path and how to access it. So I don't see the attack vector, personally.

If you do see security issues that I'm not seeing, do you see a different tactic of having tools that allow for file system modification? Could it be configuration to include the full path?

@munen
Copy link
Contributor Author

munen commented May 24, 2025

On a related note regarding a different kind of security ("How can I sandbox gptel tools?") I have found a solution that works great for me and have documented it on our blog: https://200ok.ch/posts/2025-05-23_sandboxing_ai_tools:_how_guix_containers_keep_your_host_safe_while_empowering_llms.html

There's also a bit of gptel praise sprinkled in. Thank you kindly for continuously putting effort into building this amazing tool. Using it makes my day on a regular basis 🙇

@munen
Copy link
Contributor Author

munen commented May 24, 2025

As a side note, I used Claude 4 to help me with the blog post. It was a great example of why I’ve created the above PR.

I added the blog post (Org mode) to the gptel-context and added the project root directory to the prompt. I even explicitly stated that it already had access to the file in the context. However, it consistently kept using the read_file tool. So in effect, the blog post has been twice in the context, plus I have a wasted tool call. Only when I gave it the full path as part of the prompt, it understood that it had already the file in the context.

In effect, the current context implementation was of no help, unfortunately. It would have been less work for both me and the llm had I only given it the file path in the prompt(; Of course, that’s not a solution since the gptel context generally gives me way more power over what context to provide and to constantly keep it updated.

In fact, I believe the gptel context to be a prime USP compared to other llm tools. Comparatively, even with the same llm, I get better results in gptel than in other tools. I believe that the main difference is that with gptel I can granularly select the relevant context. That focus in turn produces superior results.

@karthink
Copy link
Owner

Now, with tools, I see these things in my interactions often:

That makes sense. I'm assuming from your description that in your branch (where you include the full path instead of basenames), most LLMs you've tried recognize that the file is already available and avoid calling the read_file tool?

If you do see security issues that I'm not seeing, do you see a different tactic of having tools that allow for file system modification?

It's not a security issue, just a matter of UX and obeying user expectations. If I add /home/karthik/path/to/foo to the context, I would expect that contents of the file will be sent (for sure). Whether the path itself will also be included is not clear.

There are a couple of minor changes that might help reduce this expectation gap.

  1. We can use abbreviate-file-name and send ~/path/to/foo instead, which keeps the user name on the host out of the context. (I don't know if users would be okay sharing that. I wouldn't.)
  2. We can include the relative file name, so if the buffer's default-directory is ~/path, the file is named in the context as ./to/foo.

We can do both, but 2 will override 1 in most cases anyway.

Understanding whether this works well requires testing, so you'll have to let me know.

@karthink
Copy link
Owner

On a related note regarding a different kind of security ("How can I sandbox gptel tools?") I have found a solution that works great for me and have documented it on our blog:

The blog led me to your gptel setup -- you have many interesting LLM tools. Except for apply-diff-fenced, they appear to be quite straightforward. Do you find them to be satisfactory?

I've never managed the kind of free-form agentic use of gptel that you see in other coding assistants/agents. gptel lacks high-quality LLM tools and the right prompts for that kind of usage, both of which were probably derived from a lot of testing.

There's also a bit of gptel praise sprinkled in. Thank you kindly for continuously putting effort into building this amazing tool. Using it makes my day on a regular basis 🙇

I'm very glad to hear that!

@munen
Copy link
Contributor Author

munen commented May 25, 2025

That makes sense. I'm assuming from your description that in your branch (where you include the full path instead of basenames), most LLMs you've tried recognize that the file is already available and avoid calling the readfile tool?

Yes.

There are a couple of minor changes that might help reduce this expectation gap.

Based on initial tests, option 1 would be best. To be honest, when using upstream gptel (without the PR), I do exactly that. There's also no need for the username whatsoever. It's wasted tokens at best(;

With my sandboxing approach, and without the current PR, I'll usually start my prompts with something like:

project dir: /workspace
file path:
/workspace/src/components/OrgFile/components/Header/components/HeaderActionDrawer/index.js

(taken from my last vibecoded pr).

I'll refactor the PR to use option 1 and do some testing and will get back to you with results.

The blog led me to your gptel setup – you have many interesting LLM tools. Except for apply-diff-fenced, they appear to be quite straightforward.

Some of the tools are based off the gptel wiki. But I'm also continuously reading tool implementations in other languages and MCP/Integrations.

The apply_diff_fenced approach is taken from Aider (https://aider.chat/docs/more/edit-formats.html). For Gemini 2.5 pro, this works quite often whilst the 'standard' patch tool does not. The standard patch tool works better for Claude, though. Therefore I'll turn them on/off depending on which llm I use. This could be further improved by adding more docs to the tool itself.

All llms will regularly fail to apply a patch, but with small changes to the doc strings, it makes a huge difference in the llms ability to use them productively:

apply_diff_fenced: "Applies a diff (patch) to a specified file using fenced diff content. This is the PREFERRED method for modifying files as it is more token-efficient."

replace_file_contents: "Replaces the entire content of a specified file with new content. This tool should be used as a FALLBACK if 'apply_diff_fenced' fails repeatedly, as it requires more tokens to send the entire file content. IMPORTANT: This tool will completely overwrite the existing file content."

Do you find them to be satisfactory?

With the initial set of tools from the wiki, no. But, they made me interested to want to have more(; Initially, I did try other ai assistants (like aider) and I used to have better agentic results with them. With the current set of tools, I have pretty good results in gptel, as well! And since it's super easy to integrate new tools (like search), gptel agentic coding often works even better than the other ai assistants.

I'm still not 'satisfied' in the sense that I continue to see room for improvement and will continue to work on better tools. However, I am quite happy with the ability to do agentic coding in gptel. Here are some examples that were fully vibecoded. One was given only the GH issue (easy, because I use magit/forge and Emacs already sees the issues and gptel allows arbitrary buffers as context🙌) and was a one-shot. The other two were produced 100% by gptel, as well, but I did prompt multiple times and gptel used runcommand for tests, building docker images, finding relevant files, and iterated completely on it's own until the task was done.

A big game changer was to have filesystem tools, search and runcommand without supervision. That's only possible, because I now have an easy to use and safe sandboxing option.

I also have a default system prompt which has worked well for me for a long time: https://github.com/munen/emacs.d/blob/f78523a0e60cda0dea8ea4005aa8260c35b5f7c5/custom-settings.el#L20

I only have three other system prompts that are used to generate specific kinds of text (business text, zen temple emails, meeting minutes).

If you are interested, I could ping you once I have a set of tools that I'm 'fully' happy with. There's a few tweaks in my backlog that I planned to do anyway.

@munen
Copy link
Contributor Author

munen commented May 25, 2025

Documenting a intermediary solution that I'll keep refining and testing. For a mixed context with context like:

image

I'm getting this in *gptel-log*:

Request context:\n\nIn buffer `config.json (~/.docker/config.json)`:\n\n```web\n... (Line 2)\n\t\"auths\": {\n\n...\n```\n\nIn buffer `clippings.txt (~/clippings.txt)`:\n\n```text\n... (Line 6)\n\\357\\273\\277The record of master Rinzai (Rinzai Gigen)\r\n\n...\n```\n\nIn buffer `*scratch*`:\n\n```lisp-interaction\n... (Line 2)\n;; To create a file, visit it with \\342\\200\\230<open>\\342\\200\\231 and enter text in its buffer.\n\n...\n```

One issue is left at least: When a complete file is given, not via the buffer.

@munen
Copy link
Contributor Author

munen commented May 25, 2025

Now also working for shared files (not via buffer):

image

I'll keep testing this version and get back to the PR afterwards.

@drusso
Copy link

drusso commented May 31, 2025

From @munen:

Show complete file paths when adding files to context instead of just the basename. This enables LLMs to:

* Use file paths correctly when calling tools/functions

* Understand semantic meaning from directory structure

* Distinguish between files with identical names in different locations

From @karthink:

@munen Showing the basename is intentional. We assumed that the user is fine with sharing the file contents with the LLM since they explicitly added it, but that they might not want to expose the full path. The full path might have sensitive components. It's not clear from the context interface that the absolute path of the file will be sent to the LLM as well.

To add another idea to the mix, my solution has been to use the project-relative path (if possible), which is a reasonable middle ground for me. This approach does not work perfectly in all cases (ex. if the tool needs a full path, or if you have files from multiple projects in context), and perhaps the project relative path may have sensitive components. However, it is exactly what I need for my tool calls without using full paths.

The version of gptel-context--insert-file-string I use is:

(defun gptel-context--insert-file-string (path)
  "Insert at point the contents of the file at PATH as context."
  (insert (format "In file `%s`:"
                  (let ((project (project-current nil (file-name-directory path))))
                    (if project
                        (file-relative-name path (abbreviate-file-name (project-root project)))
                      (file-name-nondirectory path))))
          "\n\n```\n")
  (insert-file-contents path)
  (goto-char (point-max))
  (insert "\n```\n"))

We might benefit with adefcustom for a function that maps the full path to how it appears when added to context, with the default being current behaviour. I'm happy to do this if this is of interest.

@munen munen reopened this Jun 6, 2025
@munen
Copy link
Contributor Author

munen commented Jun 6, 2025

NB: My apologies. I did not intend to close this. I made a mistake fixing a merge conflict, so I started from the current master and then cherry-picked my changes on top of it. Github in the meantime thought that there's no changes, so it auto-closed the PR.

               This commit changes the gptel context format from a
               simple markdown block to a structured XML format.

               - Uses `<context_item>` tags for each context source.
               - Wraps all user content in `<![CDATA[...]]>` for
               safety.
               - Adds precise `start_line` and `end_line` numbers for
               all content.
@munen
Copy link
Contributor Author

munen commented Jun 6, 2025

I have extensively tested this in the last two weeks with agentic coding sessions every day. My proposal was an improvement on the status quo, but I learned some things.

I just pushed another refactor. In my the context to be used correctly by llms in combination with, it needs not just the actual file/buffer content, but it needs meta data as well as a specific prompt. Otherwise the context will be disregarded often. Here's an example output of the latest iteration:

CONTEXT USAGE AND RULES:

A "Request context" block is provided below. This context is the primary source of truth for the files or buffers mentioned.

- Each context item is wrapped in `<context_item>` tags.
- It contains metadata like `<file_path>` and `<file_type>`.
- The code/text is inside a `<content>` tag with `start_line` and `end_line` attributes. The content is wrapped in `<![CDATA[...]]>`.

**Mandatory Rules for Using Context:**
1.  **Source of Truth:** The context provided is the ground truth. YOU MUST use the content within the `<content>` tags for any actions related to that file and line range.
2.  **No Redundant Reading:** DO NOT use file-reading tools for any file path if its content is already provided in the context. Rely on the context.
3.  **Precise File Edits:** When using file modification tools, your edits MUST be based on the provided context. The line numbers and the original text you are replacing must align exactly with the information in the `<content>` tags.

---

Request context:

<context_item>
  <buffer_name>configuration.org</buffer_name>
  <file_path>~/.emacs.d/configuration.org</file_path>
  <file_type>org</file_type>
  <content start_line="2945" end_line="2948"><![CDATA[
  (add-to-list 'load-path "~/src/gptel-clone")
  (require 'gptel)
  (setq gptel-log-level 'info)
]]></content>
</context_item>

I'll continue testing, but I'm quite confident that this is a major improvement on the status quo.

@munen
Copy link
Contributor Author

munen commented Jun 6, 2025

To add another idea to the mix, my solution has been to use the project-relative path (if possible), which is a reasonable middle ground for me. This approach does not work perfectly in all cases (ex. if the tool needs a full path, or if you have files from multiple projects in context), and perhaps the project relative path may have sensitive components. However, it is exactly what I need for my tool calls without using full paths.

Thank you for picking an interest in this feature! The more people who can test, the merrier👍

As for the specific proposal, this is what I would regarding a project-relative path as a default: In this thread we already have multiple scenarios in which it won't work:

  • Many llms are curiously bad at concatenating paths. In fact, even with my solution from the last two weeks (the initial PR), it would often use the ${project_path}/${filename} instead of the path provided. We have to be very specific about which path to use. Otherwise lots of tool calls will fail. I have seen this behavior even with current Gemini Pro and Claude models.
  • It will not work when working with contexts from multiple repositories. I cannot talk about others, but this is something that I do just about every day. For work, but also for working on things like this PR. For example for this PR, I needed context from:
    1. My personal Emacs config
    2. My custom-settings.el file for gptel-directives
    3. The gptel code
    4. Other ai assistants (aider, zed) source

I'm not against having such a flag for people who want it. But I would strongly discourage a default which is known to fail in many cases. I'd argue that the default should lead to a UX of least surprise.

@munen
Copy link
Contributor Author

munen commented Jun 6, 2025

TL;DR: Even the strongest LLM currently is way bad when a file path is not as clearly specified as possible.

To add one more data point about how bad LLMs are with regards to using not 100% specific file paths and working dirs, this just happened in my work project:

  • I'm using the lastest Gemini 2.5 Pro 06-05 (which apart from the following mistake is so far doing a great job)
  • I'm using this PR
  • I gave gptel three whole files as context

Here's an excerpt of what the context looked like for one of those files:

<context_item>
  <file_path>~/src/200ok/alephdam/modules/ravenna/src/ravenna/some_file.clj</file_path>
  <file_type></file_type>
  <content start_line="1" end_line="236">
    <![CDATA[
             discarded for this comment
    ]]>
  </content>
</context_item>

Now, behold the function call that Gemini wanted to do when trying to edit this file:

"functionCall": {
  "name": "apply_diff",
  "args": {
    "working_dir": "/home/bastian/src/200ok/alephdam/modules/ravenna/",
    "file_path": "~/src/200ok/alephdam/modules/ravenna/src/ravenna/some_file.clj",
...

My name is not bastian, neither is my username. The string 'bastian' is neither part of the context, the whole codebase that gptel has access to, my Emacs config or the prompt used. The shortest Levensthein distance to 'bastian' is in the that one of the files included metadata for a picture. The photographer is called "Sebastian Redacted". I don't know if it picked it up from there. In any case, I'd say it is completely made up. Since I couldn't explain it, I confronted Gemini about it. Here's what it said:

*** Back up quickly from the task that we discussed before. Let's do something first. You tried to access the working dir /home/bastian/src/200ok/alephdam/modules/ravenna/. How come you think that "/home/bastian/" is my home folder?


#+begin_reasoning
**Reviewing Assumptions*

I'm currently dissecting the user's inquiry about my incorrect =/home/bastian/= assumption. It seems I made an unwarranted generalization based on a prior interaction. I'm trying to identify the specific trigger and refine my internal logic to avoid future missteps in path interpretation, perhaps by giving higher priority to input from the user.


*Deconstructing the Error*

My analysis delves deeper into the source of the incorrect =/home/bastian/= assumption. I'm focused on tracing how that path emerged. I've re-examined the failed =apply_diff= output and the user's input, searching for the logic that went awry. The key now is understanding how the tool environment and my interpretation of =~= led to the error. I'm focusing on the environmental context and the substitution process.


*Uncovering the Root*

I've traced the =/home/bastian/= assumption to the environment of the =apply_diff= execution. It seems I inferred the home directory path from output data, possibly a previous tool execution or metadata. I'm now actively seeking the source of the "bastian" username within that environment to fully understand the chain of reasoning. The goal is pinpointing how the path was formed.


*Clarifying the Tilde*

I'm now focusing on how I interpreted the tilde (=~=) in the provided file paths. I realize my assumption of =/home/bastian/= was premature. The key is now understanding the context of the user's input, specifically how the tool should have handled the tilde symbol, which is a common shortcut for the home directory. I'm investigating how the environment and training data contributed to this specific incorrect path.


*Identifying the Trigger*

I've traced the =/home/bastian/= inference to the =apply_diff= tool's execution and its internal handling of paths, specifically the =working_dir= parameter. Reviewing the prompt history confirms the user's input utilized the =~= notation. I'm pinpointing where in my reasoning the erroneous substitution occurred. The next step is correcting my internal path resolution logic.


*Identifying the Root Cause*

I'm now investigating the specific mechanism that led me to use =/home/bastian/=. The =/home/bastian/= path was likely present in an earlier tool output or environment variable. I'm focusing on the environmental context and the substitution process to identify the source of this assumption, specifically the "bastian" username. I need to understand why I interpreted the tilde (=~=) as =/home/bastian/= instead of simply passing it to the tool.



#+end_reasoning

You are absolutely right to call that out. My apologies. I made an incorrect and unwarranted assumption about your home directory path.

The file paths provided in the context, such as =~/src/200ok/alephdam/...=, use the tilde (=~=) character, which is a standard shortcut for a user's home directory. In my previous attempt, I incorrectly expanded this to =/home/bastian/=. This was a mistake on my part, likely a faulty inference drawn from patterns in my training data. I do not have access to your actual environment details, and I should not have hardcoded an absolute path like that.

I do get the security concerns about giving away the username, and I did think that using the abbreviate-file-name is good enough. However, after this incident, I would be inclined to make this an option. My proposal is:

  1. For data privacy reasons, the default for the given paths should be abbreviate-file-name.
  2. But when toggling the appropriate variable, it would become the full path.

@munen
Copy link
Contributor Author

munen commented Jun 6, 2025

For the time being, I'll add to the prompt that ~ should never be expanded. That should do the trick most of the time.

               The model was previously making an incorrect assumption
               by expanding the tilde (~) character in file paths to
               an absolute path (e.g., /home/someuser/). This caused
               tool commands to fail when the inferred username did
               not match the actual user's environment.
@karthink
Copy link
Owner

karthink commented Jul 9, 2025

@munen Let's merge the one-line change of using (abbreviate-file-name ...) instead of (file-name-base ...) first, as that's the original purpose of this PR. I can make this one line change myself, or I can merge a smaller PR from you. What would you prefer?

I have extensively tested this in the last two weeks with agentic coding sessions every day. My proposal was an improvement on the status quo, but I learned some things.

I just pushed another refactor. In my the context to be used correctly by llms in combination with, it needs not just the actual file/buffer content, but it needs meta data as well as a specific prompt. Otherwise the context will be disregarded often. Here's an example output of the latest iteration:

CONTEXT USAGE AND RULES:
...

I'm not sure about this prompt. Depending on the value of gptel-use-context it can end up anywhere in the payload -- such as in the last user message -- and not just as part of the system message.

A "Request context" block is provided below. This context is the primary source of truth for the files or buffers mentioned.

  • Each context item is wrapped in <context_item> tags.
  • It contains metadata like <file_path> and <file_type>.
  • The code/text is inside a <content> tag with start_line and end_line attributes. The content is wrapped in <![CDATA[...]]>.

Mandatory Rules for Using Context:

  1. Source of Truth: The context provided is the ground truth. YOU MUST use the content within the <content> tags for any actions related to that file and line range.
  2. No Redundant Reading: DO NOT use file-reading tools for any file path if its content is already provided in the context. Rely on the context.
  3. Precise File Edits: When using file modification tools, your edits MUST be based on the provided context. The line numbers and the original text you are replacing must align exactly with the information in the <content> tags.

Request context:

<context_item>
<buffer_name>configuration.org</buffer_name>
<file_path>~/.emacs.d/configuration.org</file_path>
<file_type>org</file_type>

</context_item>

Why the <![CDATA[...]] tag?

@munen
Copy link
Contributor Author

munen commented Jul 9, 2025

Hi @karthink

Let's merge the one-line change of using (abbreviate-file-name ...) instead of (file-name-base ...) first, as that's the original
purpose of this PR. I can make this one line change myself, or I can
merge a smaller PR from you. What would you prefer?

Thank you for offering to keep my contribution 🙏 I'm completely fine if you want to do the change yourself.

I'm not sure about this prompt. Depending on the value of
gptel-use-context it can end up anywhere in the payload -- such as
in the last user message -- and not just as part of the system
message.

I see. I haven't set the flag to user in the past. I just tried it and see that the whole context gets injected into the chat. Having another prompt there would not be optimal, of course. Having said so, without the prompt, the context will most likely not be used by agents.

Tbh, I don't understand the value proposition of setting the flag to user. I get system and nil, but user would always clutter the chat, no?

Do you have a suggestion how to proceed here?

Why the <![CDATA[...]]

The reason for using <![CDATA[...]]> is to mark a section in an XML document that should be interpreted as character data, not as markup. It is used to escape text that contains characters that would otherwise be recognized as markup. That's relevant when users send XML or content similar to XML like HTML. Having started out as a web dev and lecturer in web programming, and using gptel for a lot of XML related stuff, I wasn't going to forget about that(;

@munen
Copy link
Contributor Author

munen commented Jul 9, 2025

I have used this PR as is for over a month now and am quite happy with it. It's not perfect, but in my experience a big leg up to master. Of course, I'm happy to discuss/change anything that's up for debate!

karthink added a commit that referenced this pull request Jul 9, 2025
* gptel.el (gptel--insert-file-string): Include the full file path
in the context for included files.  The file name is passed
through `abbreviate-file-name'.

* NEWS (New features and UI changes): Mention change and reason.
@karthink
Copy link
Owner

karthink commented Jul 9, 2025

I made the change to use abbreviate-file-name. That satisfies the original purpose of this PR. I'll have to come back to the rest of this when I have more time -- the extra tags are fine but there are a couple more issues with including those accompanying instructions by default.

@munen
Copy link
Contributor Author

munen commented Jul 10, 2025

Sure, let's get back to this whenever is good for you. I'm also happy to address anything that you're bringing up.

Thank you for your continued efforts in gptel 🙏

jdormit pushed a commit to jdormit/gptel that referenced this pull request Jul 11, 2025
* gptel.el (gptel--insert-file-string): Include the full file path
in the context for included files.  The file name is passed
through `abbreviate-file-name'.

* NEWS (New features and UI changes): Mention change and reason.
@karthink karthink changed the title feat(context): display full file paths instead of filenames only feat(context): Use XML tags to delimit context chunks Jul 13, 2025
khinshankhan pushed a commit to khinshankhan/gptel that referenced this pull request Jul 28, 2025
* gptel.el (gptel--insert-file-string): Include the full file path
in the context for included files.  The file name is passed
through `abbreviate-file-name'.

* NEWS (New features and UI changes): Mention change and reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants