Skip to content

Commit 0e625c4

Browse files
committed
Add limit to repoMap with default of 800 total entries and 50 per dir.
Fixes #35
1 parent 6f34efc commit 0e625c4

File tree

6 files changed

+140
-45
lines changed

6 files changed

+140
-45
lines changed

docs/configuration.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ interface Config {
194194
ignoreFiles: [{
195195
type: string;
196196
}];
197+
repoMap?: {
198+
maxTotalEntries?: number;
199+
maxEntriesPerDir?: number;
200+
};
197201
};
198202
}
199203
```
@@ -231,7 +235,11 @@ interface Config {
231235
"index" : {
232236
"ignoreFiles" : [ {
233237
"type" : "gitignore"
234-
} ]
238+
} ],
239+
"repoMap": {
240+
"maxTotalEntries": 800,
241+
"maxEntriesPerDir": 50
242+
}
235243
}
236244
}
237245
```

src/eca/config.clj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
:chat {:welcomeMessage "Welcome to ECA!\n\nType '/' for commands\n\n"}
4747
:agentFileRelativePath "AGENT.md"
4848
:customProviders {}
49-
:index {:ignoreFiles [{:type :gitignore}]}})
49+
:index {:ignoreFiles [{:type :gitignore}]
50+
:repoMap {:maxTotalEntries 800
51+
:maxEntriesPerDir 50}}})
5052

5153
(defn get-env [env] (System/getenv env))
5254
(defn get-property [property] (System/getProperty property))

src/eca/features/chat.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@
348348
chosen-model (or model (default-model db config))
349349
rules (f.rules/all config (:workspace-folders db))
350350
refined-contexts (f.context/raw-contexts->refined contexts db config)
351-
repo-map* (delay (f.index/repo-map db {:as-string? true}))
351+
repo-map* (delay (f.index/repo-map db config {:as-string? true}))
352352
instructions (f.prompt/build-instructions refined-contexts rules repo-map* (or behavior (:chat-default-behavior db)) config)
353353
chat-ctx {:chat-id chat-id
354354
:request-id request-id

src/eca/features/commands.clj

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,19 @@
7272
(dissoc :server))))
7373
eca-commands [{:name "init"
7474
:type :native
75-
:description "Create/update the AGENT.md file teaching LLM about the project"}
75+
:description "Create/update the AGENT.md file teaching LLM about the project"
76+
:arguments []}
7677
{:name "costs"
7778
:type :native
7879
:description "Total costs of the current chat session."
79-
{:name "resume"
80-
:type :native
81-
:description "Resume the chats from this session workspaces."
82-
:arguments []}
83-
{:name "repo-map-show"
84-
:type :native
85-
:description "Actual repoMap of current session."
86-
:arguments []}
80+
:arguments []}
81+
{:name "resume"
82+
:type :native
83+
:description "Resume the chats from this session workspaces."
84+
:arguments []}
85+
{:name "repo-map-show"
86+
:type :native
87+
:description "Actual repoMap of current session."
8788
:arguments []}
8889
{:name "prompt-show"
8990
:type :native
@@ -136,7 +137,7 @@
136137
{:type :chat-messages
137138
:chats {chat-id [{:role "system" :content [{:type :text :text text}]}]}})
138139
"repo-map-show" {:type :chat-messages
139-
:chats {chat-id [{:role "system" :content [{:type :text :text (f.index/repo-map db {:as-string? true})}]}]}}
140+
:chats {chat-id [{:role "system" :content [{:type :text :text (f.index/repo-map db config {:as-string? true})}]}]}}
140141
"prompt-show" {:type :chat-messages
141142
:chats {chat-id [{:role "system" :content [{:type :text :text instructions}]}]}}
142143

src/eca/features/index.clj

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,44 +37,89 @@
3737
file-paths
3838
(get-in config [:index :ignoreFiles])))
3939

40-
(defn insert-path [tree parts]
40+
(defn ^:private insert-path [tree parts]
4141
(if (empty? parts)
4242
tree
4343
(let [head (first parts)
4444
tail (rest parts)]
4545
(update tree head #(insert-path (or % {}) tail)))))
4646

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))))))
47+
;; Count how many nodes (lines) a tree would render
48+
(defn ^:private tree-count [tree]
49+
(reduce (fn [cnt [_k v]]
50+
(let [self 1
51+
children (if (seq v) (tree-count v) 0)]
52+
(+ cnt self children)))
53+
0
54+
tree))
5855

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)))
56+
;; Render tree with limits: global max entries and per-directory max entries (non-root only)
57+
(defn ^:private tree->str-limited
58+
[tree max-total-entries max-entries-per-dir]
59+
(let [indent-str (fn [level] (apply str (repeat (* 1 level) " ")))
60+
remaining (atom max-total-entries)
61+
printed-lines (atom 0) ;; all printed lines (nodes + indicator lines)
62+
printed-actual (atom 0) ;; only actual tree nodes
63+
sb (StringBuilder.)
64+
;; emit a single line if we still have remaining budget
65+
emit-line (fn [^String s]
66+
(when (pos? @remaining)
67+
(.append sb s)
68+
(swap! remaining dec)
69+
(swap! printed-lines inc)))
70+
emit-node (fn [^String s]
71+
(when (pos? @remaining)
72+
(.append sb s)
73+
(swap! remaining dec)
74+
(swap! printed-lines inc)
75+
(swap! printed-actual inc)))
76+
;; recursive emit of a level (all entries in map m)
77+
emit-level (fn emit-level [m indent-level root?]
78+
(let [entries (sort m)
79+
;; Apply per-dir limit to the current level if not root
80+
entries-vec (vec entries)
81+
[to-show hidden] (if root?
82+
[entries-vec []]
83+
[(subvec entries-vec 0 (min (count entries-vec) max-entries-per-dir))
84+
(subvec entries-vec (min (count entries-vec) max-entries-per-dir))])]
85+
(doseq [[k v] to-show]
86+
(when (pos? @remaining)
87+
(emit-node (str (indent-str indent-level) k "\n"))
88+
(when (seq v)
89+
(emit-level v (inc indent-level) false))))
90+
(when (and (seq hidden) (pos? @remaining) (not root?))
91+
(emit-line (str (indent-str indent-level) "... truncated output ("
92+
(count hidden) " more entries)\n")))))]
93+
(emit-level tree 0 true)
94+
;; Compute total possible nodes and append global truncation line if needed
95+
(let [total (tree-count tree)
96+
omitted (- total @printed-actual)]
97+
(when (pos? omitted)
98+
(.append sb (str "... truncated output (" omitted " more entries)\n"))))
99+
(str sb)))
100+
101+
(defn repo-map
102+
([db config] (repo-map db config {}))
103+
([db config {:keys [as-string?]}]
104+
(let [tree (reduce
105+
(fn [t {:keys [uri]}]
106+
(let [root-filename (shared/uri->filename uri)
107+
files (git-ls-files root-filename)]
108+
(merge t
109+
{root-filename
110+
(reduce
111+
(fn [tree path]
112+
(insert-path tree (clojure.string/split path #"/")))
113+
{}
114+
files)})))
115+
{}
116+
(:workspace-folders db))]
117+
(if as-string?
118+
(let [{:keys [maxTotalEntries maxEntriesPerDir]} (get-in config [:index :repoMap])]
119+
(tree->str-limited tree maxTotalEntries maxEntriesPerDir))
120+
tree))))
76121

77122
(comment
78123
(require 'user)
79124
(user/with-workspace-root "file:///home/greg/dev/eca"
80-
(println (repo-map user/*db* {:as-string? true}))))
125+
(println (repo-map user/*db* {:max-total-entries 1000 :max-entries-per-dir 200} {:as-string? true}))))

test/eca/features/index_test.clj

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require
33
[babashka.fs :as fs]
44
[clojure.java.shell :as shell]
5+
[clojure.string :as string]
56
[clojure.test :refer [deftest is testing]]
67
[eca.features.index :as f.index]
78
[eca.shared :refer [multi-str]]
@@ -41,13 +42,51 @@
4142
{"README.md" {}
4243
"src" {"eca" {"core.clj" {}}}
4344
"test" {"eca" {"core_test.clj" {}}}}}
44-
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]})))))
45+
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]} {})))))
4546
(testing "returns string tree with as-string? true"
4647
(with-redefs [f.index/git-ls-files (constantly ["foo.clj" "bar/baz.clj"])]
4748
(is (= (multi-str (h/file-path "/fake/repo")
4849
" bar"
4950
" baz.clj"
5051
" foo.clj"
5152
"")
52-
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]}
53+
(eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]}
54+
{:index {:repoMap {:maxEntriesPerDir 50 :maxTotalEntries 800}}}
5355
{:as-string? true}))))))
56+
57+
(deftest repo-map-truncation-test
58+
(testing "per-directory truncation shows indicator and global truncated line"
59+
(with-redefs [f.index/git-ls-files (constantly ["AGENT.md"
60+
"src/a.clj"
61+
"src/b.clj"
62+
"src/c.clj"
63+
"src/d.clj"
64+
"src/e.clj"
65+
"src/f.clj"
66+
"src/g.clj"
67+
"src/h.clj"])]
68+
(let [out (eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]}
69+
{:index {:repoMap {:maxTotalEntries 800
70+
:maxEntriesPerDir 3}}}
71+
{:as-string? true})]
72+
(is (string/includes? out (str (h/file-path "/fake/repo") "\n")))
73+
;; Under src, only first 3 children (sorted) and a per-dir truncated line should appear
74+
(is (string/includes? out " src\n"))
75+
(is (string/includes? out " a.clj\n"))
76+
(is (string/includes? out " b.clj\n"))
77+
(is (string/includes? out " c.clj\n"))
78+
(is (string/includes? out " ... truncated output (5 more entries)\n"))
79+
;; A final global truncated line should also be present
80+
(is (string/includes? out "\n... truncated output (")))))
81+
(testing "global truncation appends final truncated line"
82+
(with-redefs [f.index/git-ls-files (constantly ["AGENT.md"
83+
"CHANGELOG.md"
84+
"LICENSE"
85+
"src/a.clj"
86+
"src/b.clj"])]
87+
(let [out (eca.features.index/repo-map {:workspace-folders [{:uri (h/file-uri "file:///fake/repo")}]}
88+
{:index {:repoMap {:maxTotalEntries 3
89+
:maxEntriesPerDir 800}}}
90+
{:as-string? true})]
91+
;; Contains a global truncated line
92+
(is (string/includes? out "\n... truncated output ("))))))

0 commit comments

Comments
 (0)