Skip to content

Commit cfd45de

Browse files
committed
Support repo-map context
1 parent 41f9fd7 commit cfd45de

File tree

7 files changed

+225
-199
lines changed

7 files changed

+225
-199
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- Support tool call approval and configuration to manual approval.
6+
- Initial support for repo-map context.
67

78
## 0.7.0
89

dev/user.clj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(ns user
2+
(:require
3+
[eca.db :as db]))
4+
5+
(alter-var-root #'*warn-on-reflection* (constantly true))
6+
7+
(def ^:dynamic *db* @db/db*)
8+
9+
#_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]}
10+
(defmacro with-workspace-root [root-uri & body]
11+
`(binding [*db* (assoc *db* :workspace-folders [{:name "dev" :uri ~root-uri}])]
12+
~@body))

docs/features.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ Provides access to run shell commands, useful to run build tools, tests, and oth
3333

3434
- `eca_shell_command`: run shell command. Supports configs to exclude commands via `:nativeTools :shell :excludeCommands`.
3535

36+
### Contexts
37+
38+
User can include contexts to the chat, which can help LLM generate output with better quality.
39+
Here are the current supported contexts types:
40+
41+
- `file`: a file in the workspace, server will pass its content to LLM.
42+
- `directoryu`: a directory in the workspace, server will read all file contexts and pass to LLM.
43+
- `repoMap`: a summary view of workspaces files and folders, server will calculate this and pass to LLM. Currently, the repo-map includes only the file paths in git.
44+
3645
## Completion
3746

3847
Soon

docs/protocol.md

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ type ChatModel =
278278
*/
279279
type OllamaRunningModel = string
280280

281-
type ChatContext = FileContext | DirectoryContext | WebContext | CodeContext;
281+
type ChatContext = FileContext | DirectoryContext | WebContext | RepoMapContext;
282282

283283
/**
284284
* Context related to a file in the workspace
@@ -314,23 +314,13 @@ interface WebContext {
314314
}
315315

316316
/**
317-
* Context related to code snippets
317+
* Context about the workspaces repo-map, automatically calculated by server.
318+
* Clients should include this to chat by default but users may want exclude
319+
* this context to reduce context size if needed.
318320
*/
319-
interface CodeContext {
320-
type: 'code';
321-
/**
322-
* The code content
323-
*/
324-
content: string;
325-
/**
326-
* The programming language of the code
327-
*/
328-
language: string;
329-
/**
330-
* Name or description of what this code represents
331-
*/
332-
description?: string;
333-
}
321+
interface RepoMapContext {
322+
type: 'repoMap';
323+
}
334324
```
335325

336326
_Response:_

src/eca/features/chat.clj

Lines changed: 109 additions & 168 deletions
Large diffs are not rendered by default.

src/eca/features/index.clj

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
(ns eca.features.index
22
(:require
33
[babashka.fs :as fs]
4+
[clojure.core.memoize :as memoize]
45
[clojure.java.shell :as shell]
5-
[clojure.string :as string]))
6+
[clojure.string :as string]
7+
[eca.shared :as shared]))
68

79
(set! *warn-on-reflection* true)
810

11+
(def ^:private ttl-git-ls-files-ms 5000)
12+
13+
(defn ^:private git-ls-files* [root-path]
14+
(try
15+
(some-> (shell/sh "git" "ls-files" "--others" "--exclude-standard" "--cached"
16+
:dir root-path)
17+
:out
18+
(string/split #"\n"))
19+
20+
(catch Exception _ nil)))
21+
22+
(def ^:private git-ls-files (memoize/ttl git-ls-files* :ttl/threshold ttl-git-ls-files-ms))
23+
924
(defn filter-allowed [file-paths root-filename config]
1025
(reduce
1126
(fn [files {:keys [type]}]
1227
(case type
13-
:gitignore (let [git-files (try (some->> (some-> (shell/sh "git" "ls-files"
14-
:dir root-filename)
15-
:out
16-
(string/split #"\n"))
17-
(mapv (comp str fs/canonicalize #(fs/file root-filename %)))
18-
set)
19-
(catch Exception _ nil))]
20-
(println git-files (str (last files)))
28+
:gitignore (let [git-files (some->> (git-ls-files root-filename)
29+
(mapv (comp str fs/canonicalize #(fs/file root-filename %)))
30+
set)]
2131
(if (seq git-files)
2232
(filter (fn [file]
2333
(contains? git-files (str file)))
@@ -26,3 +36,45 @@
2636
files))
2737
file-paths
2838
(get-in config [:index :ignoreFiles])))
39+
40+
(defn insert-path [tree parts]
41+
(if (empty? parts)
42+
tree
43+
(let [head (first parts)
44+
tail (rest parts)]
45+
(update tree head #(insert-path (or % {}) tail)))))
46+
47+
(defn tree->str
48+
([tree] (tree->str tree 0))
49+
([tree indent]
50+
(let [indent-str (fn [level] (apply str (repeat (* 1 level) " ")))]
51+
(apply str
52+
(mapcat (fn [[k v]]
53+
(let [current-line (str (indent-str indent) k "\n")
54+
children-str (when (seq v)
55+
(tree->str v (inc indent)))]
56+
[current-line children-str]))
57+
(sort tree))))))
58+
59+
(defn repo-map [db & {:keys [as-string?]}]
60+
(let [tree (reduce
61+
(fn [t {:keys [uri]}]
62+
(let [root-filename (shared/uri->filename uri)
63+
files (git-ls-files root-filename)]
64+
(merge t
65+
{root-filename
66+
(reduce
67+
(fn [tree path]
68+
(insert-path tree (clojure.string/split path #"/")))
69+
{}
70+
files)})))
71+
{}
72+
(:workspace-folders db))]
73+
(if as-string?
74+
(tree->str tree)
75+
tree)))
76+
77+
(comment
78+
(require 'user)
79+
(user/with-workspace-root "file:///home/greg/dev/eca"
80+
(println (repo-map user/*db* {:as-string? true}))))

test/eca/features/index_test.clj

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
[babashka.fs :as fs]
44
[clojure.java.shell :as shell]
55
[clojure.test :refer [deftest is testing]]
6-
[eca.features.index :refer [filter-allowed]]
6+
[eca.features.index :as f.index]
7+
[eca.test-helper :as h]
78
[matcher-combinators.test :refer [match?]]))
89

910
(def gitignore-config
1011
{:index {:ignoreFiles [{:type :gitignore}]}})
1112

1213
(deftest ignore?-test
1314
(testing "gitignore type"
14-
(let [root "/fake/repo"
15+
(let [root "/fake/repo"
1516
file1 (fs/path root "ignored.txt")
1617
file2 (fs/path root "not-ignored.txt")]
1718
(testing "returns filtered files when `git ls-files` works"
@@ -20,11 +21,31 @@
2021
(is
2122
(match?
2223
[file2]
23-
(filter-allowed [file1 file2] root gitignore-config)))))
24+
(f.index/filter-allowed [file1 file2] root gitignore-config)))))
2425

2526
(testing "returns all files when `git ls-files` exits non-zero"
2627
(with-redefs [shell/sh (fn [& _args] {:exit 1})]
2728
(is
2829
(match?
29-
[file1 file2]
30-
(filter-allowed [file1 file2] root gitignore-config))))))))
30+
[file2]
31+
(f.index/filter-allowed [file1 file2] root gitignore-config))))))))
32+
33+
(deftest repo-map-test
34+
(testing "returns correct tree for a simple git repo"
35+
(with-redefs [f.index/git-ls-files (constantly ["README.md"
36+
"src/eca/core.clj"
37+
"test/eca/core_test.clj"])]
38+
(is (match?
39+
{"/fake/repo"
40+
{"README.md" {}
41+
"src" {"eca" {"core.clj" {}}}
42+
"test" {"eca" {"core_test.clj" {}}}}}
43+
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]})))))
44+
(testing "returns string tree with as-string? true"
45+
(with-redefs [f.index/git-ls-files (constantly ["foo.clj" "bar/baz.clj"])]
46+
(is (= (str "/fake/repo\n"
47+
" bar\n"
48+
" baz.clj\n"
49+
" foo.clj\n")
50+
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]}
51+
{:as-string? true}))))))

0 commit comments

Comments
 (0)