refactor(elixir): rename module from 'contexa' to 'cortexa'

Swadhin Biswas committed 7 months ago
6dd3d24
1 parent d6fab51
commit 6dd3d24
8 files changed
+269 -149
▼ELIXIR/README.md
+4-4
1
-# contexa — Elixir
1
+# cortexa — Elixir
2
2
3
3
Git-inspired context management for LLM agents — **COMMIT**, **BRANCH**, **MERGE**, and **CONTEXT** operations over a persistent versioned memory workspace.
4
4
11
11
```elixir
12
12
def deps do
13
13
[
14
- {:contexa, "~> 0.1.1"}
14
+ {:cortexa, "~> 0.1.1"}
15
15
]
16
16
end
17
17
```
19
19
## Quick Start
20
20
21
21
```elixir
22
-alias Contexa.{Workspace, Models}
22
+alias Cortexa.{Workspace, Models}
23
23
24
24
# Create workspace
25
25
ws = Workspace.new("/tmp/my-project")
46
46
47
47
## API
48
48
49
-### `Contexa.Workspace`
49
+### `Cortexa.Workspace`
50
50
51
51
| Function | Description |
52
52
|----------|-------------|
53
53
▼ELIXIR/lib/contexa.ex
+0-22
1
-defmodule Contexa do
2
- @moduledoc """
3
- contexa — Git-inspired context management for LLM agents.
4
-
5
- COMMIT, BRANCH, MERGE, and CONTEXT operations over a persistent
6
- versioned memory workspace. Based on arXiv:2508.00031.
7
-
8
- ## Quick Start
9
-
10
- ws = Contexa.Workspace.new("/tmp/my-project")
11
- ws = Contexa.Workspace.init(ws, "Build an AI agent")
12
-
13
- {ws, _ota} = Contexa.Workspace.log_ota(ws, "Observed X", "Thinking Y", "Did Z")
14
- {ws, _commit} = Contexa.Workspace.commit(ws, "Implemented feature")
15
-
16
- ws = Contexa.Workspace.branch(ws, "explore", "Try alternative approach")
17
- {ws, _commit} = Contexa.Workspace.merge(ws, "explore")
18
-
19
- ctx = Contexa.Workspace.context(ws, "main", 3)
20
- IO.puts(Contexa.Models.context_summary(ctx))
21
- """
22
-end
23
0
▼ELIXIR/lib/cortexa.ex
+22-0
1
+defmodule Cortexa do
2
+ @moduledoc """
3
+ cortexa — Git-inspired context management for LLM agents.
4
+
5
+ COMMIT, BRANCH, MERGE, and CONTEXT operations over a persistent
6
+ versioned memory workspace. Based on arXiv:2508.00031.
7
+
8
+ ## Quick Start
9
+
10
+ ws = Cortexa.Workspace.new("/tmp/my-project")
11
+ ws = Cortexa.Workspace.init(ws, "Build an AI agent")
12
+
13
+ {ws, _ota} = Cortexa.Workspace.log_ota(ws, "Observed X", "Thinking Y", "Did Z")
14
+ {ws, _commit} = Cortexa.Workspace.commit(ws, "Implemented feature")
15
+
16
+ ws = Cortexa.Workspace.branch(ws, "explore", "Try alternative approach")
17
+ {ws, _commit} = Cortexa.Workspace.merge(ws, "explore")
18
+
19
+ ctx = Cortexa.Workspace.context(ws, "main", 3)
20
+ IO.puts(Cortexa.Models.context_summary(ctx))
21
+ """
22
+end
0
23
▼ELIXIR/lib/contexa/models.ex → ELIXIR/lib/cortexa/models.ex
+59-17
1
-defmodule Contexa.Models do
1
+defmodule Cortexa.Models do
2
2
@moduledoc """
3
- Data structures for the contexa GCC workspace.
3
+ Data structures for the cortexa GCC workspace.
4
4
5
- contexa — Git-inspired context management for LLM agents.
5
+ cortexa — Git-inspired context management for LLM agents.
6
6
Based on arXiv:2508.00031.
7
7
"""
8
8
139
139
}
140
140
end
141
141
142
+ # ── Input sanitization ─────────────────────────────────────────────
143
+
144
+ @doc "Escape separator sequences in user-provided content."
145
+ @spec sanitize(String.t()) :: String.t()
146
+ def sanitize(text) when is_binary(text) do
147
+ String.replace(text, "\n---\n", "\n\\---\n")
148
+ end
149
+ def sanitize(nil), do: ""
150
+
151
+ @doc "Reverse the escaping applied by sanitize."
152
+ @spec desanitize(String.t()) :: String.t()
153
+ def desanitize(text) when is_binary(text) do
154
+ String.replace(text, "\n\\---\n", "\n---\n")
155
+ end
156
+ def desanitize(nil), do: ""
157
+
158
+ @doc """
159
+ Split markdown text on separator while respecting escaped separators.
160
+
161
+ After naive splitting on the delimiter, blocks whose predecessor ends
162
+ with `\\` are rejoined (the backslash means `\\---\\n` was an escaped
163
+ separator produced by `sanitize/1`).
164
+ """
165
+ @spec split_blocks(String.t(), String.t()) :: [String.t()]
166
+ def split_blocks(text, delim \\ "\n---\n") do
167
+ text
168
+ |> String.split(delim)
169
+ |> rejoin_escaped(delim, [])
170
+ end
171
+
172
+ defp rejoin_escaped([], _delim, acc), do: Enum.reverse(acc)
173
+ defp rejoin_escaped([block | rest], delim, []) do
174
+ rejoin_escaped(rest, delim, [block])
175
+ end
176
+ defp rejoin_escaped([block | rest], delim, [prev | acc]) do
177
+ if String.ends_with?(prev, "\\") do
178
+ rejoin_escaped(rest, delim, [prev <> delim <> block | acc])
179
+ else
180
+ rejoin_escaped(rest, delim, [block, prev | acc])
181
+ end
182
+ end
183
+
142
184
# ── Markdown serialization ────────────────────────────────────────
143
185
144
186
@doc "Render an OTARecord as markdown."
146
188
def ota_to_markdown(%OTARecord{} = rec) do
147
189
"""
148
190
### Step #{rec.step} — #{rec.timestamp}
149
- **Observation:** #{rec.observation}
191
+ **Observation:** #{sanitize(rec.observation)}
150
192
151
- **Thought:** #{rec.thought}
193
+ **Thought:** #{sanitize(rec.thought)}
152
194
153
- **Action:** #{rec.action}
195
+ **Action:** #{sanitize(rec.action)}
154
196
155
197
---
156
198
"""
163
205
## Commit `#{rec.commit_id}`
164
206
**Timestamp:** #{rec.timestamp}
165
207
166
- **Branch Purpose:** #{rec.branch_purpose}
208
+ **Branch Purpose:** #{sanitize(rec.branch_purpose)}
167
209
168
- **Previous Progress Summary:** #{rec.previous_progress_summary}
210
+ **Previous Progress Summary:** #{sanitize(rec.previous_progress_summary)}
169
211
170
- **This Commit's Contribution:** #{rec.this_commit_contribution}
212
+ **This Commit's Contribution:** #{sanitize(rec.this_commit_contribution)}
171
213
172
214
---
173
215
"""
242
284
@spec parse_commits(String.t()) :: [CommitRecord.t()]
243
285
def parse_commits(text) do
244
286
text
245
- |> String.split("\n---\n")
287
+ |> split_blocks("\n---\n")
246
288
|> Enum.map(&String.trim/1)
247
289
|> Enum.filter(&(&1 != ""))
248
290
|> Enum.reduce([], fn block, acc ->
263
305
commit_id: commit_id,
264
306
branch_name: "",
265
307
timestamp: extract.(~r/\*\*Timestamp:\*\*\s*(.+)/),
266
- branch_purpose: extract.(~r/\*\*Branch Purpose:\*\*\s*(.+)/),
267
- previous_progress_summary: extract.(~r/\*\*Previous Progress Summary:\*\*\s*(.+)/),
268
- this_commit_contribution: extract.(~r/\*\*This Commit's Contribution:\*\*\s*(.+)/)
308
+ branch_purpose: desanitize(extract.(~r/\*\*Branch Purpose:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\z)/)),
309
+ previous_progress_summary: desanitize(extract.(~r/\*\*Previous Progress Summary:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\z)/)),
310
+ this_commit_contribution: desanitize(extract.(~r/\*\*This Commit's Contribution:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\n\n---|\z)/))
269
311
}
270
312
acc ++ [rec]
271
313
else
278
320
@spec parse_ota(String.t()) :: [OTARecord.t()]
279
321
def parse_ota(text) do
280
322
text
281
- |> String.split("\n---\n")
323
+ |> split_blocks("\n---\n")
282
324
|> Enum.map(&String.trim/1)
283
325
|> Enum.filter(&(&1 != ""))
284
326
|> Enum.reduce([], fn block, acc ->
294
336
end
295
337
end
296
338
297
- obs = extract.(~r/\*\*Observation:\*\*\s*(.+)/)
298
- thought = extract.(~r/\*\*Thought:\*\*\s*(.+)/)
299
- action = extract.(~r/\*\*Action:\*\*\s*(.+)/)
339
+ obs = desanitize(extract.(~r/\*\*Observation:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\z)/))
340
+ thought = desanitize(extract.(~r/\*\*Thought:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\z)/))
341
+ action = desanitize(extract.(~r/\*\*Action:\*\*\s*([\s\S]+?)(?=\n\n\*\*|\n\n---|\z)/))
300
342
301
343
if obs != "" or thought != "" or action != "" do
302
344
rec = %OTARecord{
303
345
▼ELIXIR/lib/contexa/workspace.ex → ELIXIR/lib/cortexa/workspace.ex
+173-95
1
-defmodule Contexa.Workspace do
1
+defmodule Cortexa.Workspace do
2
2
@moduledoc """
3
3
GCCWorkspace — persistent versioned memory workspace for LLM agents.
4
4
5
- contexa — Git-inspired context management.
5
+ cortexa — Git-inspired context management.
6
6
Based on arXiv:2508.00031.
7
7
"""
8
8
9
- alias Contexa.Models
10
- alias Contexa.Models.{OTARecord, CommitRecord, ContextResult}
9
+ alias Cortexa.Models
10
+ alias Cortexa.Models.{OTARecord, CommitRecord, ContextResult}
11
11
12
12
@main_branch "main"
13
13
@gcc_dir ".GCC"
14
+ @lock_timeout 10_000 # milliseconds
14
15
15
16
defstruct [:root, :gcc_dir, current_branch: "main"]
16
17
20
21
current_branch: String.t()
21
22
}
22
23
24
+ # ── Input validation ─────────────────────────────────────────────
25
+
26
+ defp validate_not_empty!(value, field_name) do
27
+ if is_nil(value) or String.trim(to_string(value)) == "" do
28
+ raise ArgumentError, "#{field_name} must not be empty"
29
+ end
30
+ end
31
+
32
+ defp validate_branch_name!(name) do
33
+ validate_not_empty!(name, "Branch name")
34
+ if String.contains?(name, "/") or String.contains?(name, "\\") do
35
+ raise ArgumentError, "Branch name must not contain '/' or '\\'"
36
+ end
37
+ if name == "." or name == ".." do
38
+ raise ArgumentError, "Branch name must not be '.' or '..'"
39
+ end
40
+ end
41
+
42
+ # ── File locking ─────────────────────────────────────────────────
43
+
44
+ @doc false
45
+ defp with_lock(ws, fun) do
46
+ lock_path = Path.join(ws.gcc_dir, ".lock")
47
+ File.mkdir_p!(Path.dirname(lock_path))
48
+
49
+ case :file.open(String.to_charlist(lock_path), [:write, :raw]) do
50
+ {:ok, fd} ->
51
+ try do
52
+ case :file.lock(fd, :exclusive) do
53
+ :ok ->
54
+ fun.()
55
+ {:error, reason} ->
56
+ raise "Failed to acquire lock: #{inspect(reason)}"
57
+ end
58
+ after
59
+ :file.unlock(fd)
60
+ :file.close(fd)
61
+ end
62
+ {:error, reason} ->
63
+ raise "Failed to open lock file: #{inspect(reason)}"
64
+ end
65
+ end
66
+
23
67
# ── Constructor ───────────────────────────────────────────────────
24
68
25
69
@doc "Create a new workspace handle."
44
88
branch_dir = branch_dir(ws, @main_branch)
45
89
File.mkdir_p!(branch_dir)
46
90
47
- ts = Models.timestamp()
48
-
49
- # main.md
50
- File.write!(
51
- Path.join(ws.gcc_dir, "main.md"),
52
- "# Project Roadmap\n\n**Initialized:** #{ts}\n\n#{project_roadmap}\n"
53
- )
54
-
55
- # log.md
56
- File.write!(
57
- Path.join(branch_dir, "log.md"),
58
- "# OTA Log — branch `main`\n\n"
59
- )
60
-
61
- # commit.md
62
- File.write!(
63
- Path.join(branch_dir, "commit.md"),
64
- "# Commit History — branch `main`\n\n"
65
- )
66
-
67
- # metadata.yaml
68
- meta = Models.new_branch_metadata(@main_branch, "Primary reasoning trajectory", "")
69
- File.write!(
70
- Path.join(branch_dir, "metadata.yaml"),
71
- Models.metadata_to_yaml(meta)
72
- )
91
+ with_lock(ws, fn ->
92
+ ts = Models.timestamp()
93
+
94
+ # main.md
95
+ File.write!(
96
+ Path.join(ws.gcc_dir, "main.md"),
97
+ "# Project Roadmap\n\n**Initialized:** #{ts}\n\n#{project_roadmap}\n"
98
+ )
99
+
100
+ # log.md
101
+ File.write!(
102
+ Path.join(branch_dir, "log.md"),
103
+ "# OTA Log — branch `main`\n\n"
104
+ )
105
+
106
+ # commit.md
107
+ File.write!(
108
+ Path.join(branch_dir, "commit.md"),
109
+ "# Commit History — branch `main`\n\n"
110
+ )
111
+
112
+ # metadata.yaml
113
+ meta = Models.new_branch_metadata(@main_branch, "Primary reasoning trajectory", "")
114
+ File.write!(
115
+ Path.join(branch_dir, "metadata.yaml"),
116
+ Models.metadata_to_yaml(meta)
117
+ )
118
+ end)
73
119
74
120
%{ws | current_branch: @main_branch}
75
121
end
102
148
@doc "Append an OTA cycle to the current branch."
103
149
@spec log_ota(t(), String.t(), String.t(), String.t()) :: {t(), OTARecord.t()}
104
150
def log_ota(ws, observation, thought, action) do
105
- dir = branch_dir(ws, ws.current_branch)
106
- log_path = Path.join(dir, "log.md")
107
- existing = read_file_safe(log_path) |> Models.parse_ota()
108
- step = length(existing) + 1
109
- rec = Models.new_ota(step, observation, thought, action)
110
- File.write!(log_path, read_file_safe(log_path) <> Models.ota_to_markdown(rec))
111
- {ws, rec}
151
+ obs_blank = is_nil(observation) or String.trim(to_string(observation)) == ""
152
+ tht_blank = is_nil(thought) or String.trim(to_string(thought)) == ""
153
+ act_blank = is_nil(action) or String.trim(to_string(action)) == ""
154
+ if obs_blank and tht_blank and act_blank do
155
+ raise ArgumentError, "At least one of observation, thought, or action must be non-empty"
156
+ end
157
+
158
+ with_lock(ws, fn ->
159
+ dir = branch_dir(ws, ws.current_branch)
160
+ log_path = Path.join(dir, "log.md")
161
+ existing = read_file_safe(log_path) |> Models.parse_ota()
162
+ step = length(existing) + 1
163
+ rec = Models.new_ota(step, observation, thought, action)
164
+ File.write!(log_path, read_file_safe(log_path) <> Models.ota_to_markdown(rec))
165
+ {ws, rec}
166
+ end)
112
167
end
113
168
114
169
# ── Commit ───────────────────────────────────────────────────────
116
171
@doc "COMMIT — checkpoint a milestone on the current branch."
117
172
@spec commit(t(), String.t(), String.t() | nil, String.t() | nil) :: {t(), CommitRecord.t()}
118
173
def commit(ws, contribution, previous_summary \\ nil, update_roadmap \\ nil) do
174
+ validate_not_empty!(contribution, "Contribution")
175
+
176
+ with_lock(ws, fn ->
177
+ commit_inner(ws, contribution, previous_summary, update_roadmap)
178
+ end)
179
+ end
180
+
181
+ @doc false
182
+ defp commit_inner(ws, contribution, previous_summary, update_roadmap) do
119
183
dir = branch_dir(ws, ws.current_branch)
120
184
121
185
meta = Path.join(dir, "metadata.yaml")
157
221
@doc "BRANCH — create an isolated reasoning workspace."
158
222
@spec branch(t(), String.t(), String.t()) :: t()
159
223
def branch(ws, name, purpose) do
224
+ validate_branch_name!(name)
225
+ validate_not_empty!(purpose, "Branch purpose")
226
+
160
227
dir = branch_dir(ws, name)
161
228
if File.dir?(dir) do
162
229
raise "Branch already exists: #{name}"
164
231
165
232
File.mkdir_p!(dir)
166
233
167
- File.write!(
168
- Path.join(dir, "log.md"),
169
- "# OTA Log — branch `#{name}`\n\n"
170
- )
171
- File.write!(
172
- Path.join(dir, "commit.md"),
173
- "# Commit History — branch `#{name}`\n\n"
174
- )
175
-
176
- meta = Models.new_branch_metadata(name, purpose, ws.current_branch)
177
- File.write!(
178
- Path.join(dir, "metadata.yaml"),
179
- Models.metadata_to_yaml(meta)
180
- )
234
+ with_lock(ws, fn ->
235
+ File.write!(
236
+ Path.join(dir, "log.md"),
237
+ "# OTA Log — branch `#{name}`\n\n"
238
+ )
239
+ File.write!(
240
+ Path.join(dir, "commit.md"),
241
+ "# Commit History — branch `#{name}`\n\n"
242
+ )
243
+
244
+ meta = Models.new_branch_metadata(name, purpose, ws.current_branch)
245
+ File.write!(
246
+ Path.join(dir, "metadata.yaml"),
247
+ Models.metadata_to_yaml(meta)
248
+ )
249
+ end)
181
250
182
251
%{ws | current_branch: name}
183
252
end
187
256
@doc "Switch to an existing branch."
188
257
@spec switch_branch(t(), String.t()) :: t()
189
258
def switch_branch(ws, name) do
259
+ validate_branch_name!(name)
190
260
dir = branch_dir(ws, name)
191
261
unless File.dir?(dir) do
192
262
raise "Branch does not exist: #{name}"
216
286
@doc "MERGE — integrate a branch back into a target."
217
287
@spec merge(t(), String.t(), String.t() | nil, String.t()) :: {t(), CommitRecord.t()}
218
288
def merge(ws, branch_name, summary \\ nil, target \\ "main") do
289
+ validate_branch_name!(branch_name)
290
+ validate_branch_name!(target)
291
+
219
292
src_dir = branch_dir(ws, branch_name)
220
293
tgt_dir = branch_dir(ws, target)
221
294
222
295
unless File.dir?(src_dir), do: raise("Source branch does not exist: #{branch_name}")
223
296
unless File.dir?(tgt_dir), do: raise("Target branch does not exist: #{target}")
224
297
225
- src_commits = Path.join(src_dir, "commit.md") |> read_file_safe() |> Models.parse_commits()
226
- src_ota = Path.join(src_dir, "log.md") |> read_file_safe() |> Models.parse_ota()
227
- src_meta = Path.join(src_dir, "metadata.yaml") |> read_file_safe() |> Models.metadata_from_yaml()
298
+ with_lock(ws, fn ->
299
+ src_commits = Path.join(src_dir, "commit.md") |> read_file_safe() |> Models.parse_commits()
300
+ src_ota = Path.join(src_dir, "log.md") |> read_file_safe() |> Models.parse_ota()
301
+ src_meta = Path.join(src_dir, "metadata.yaml") |> read_file_safe() |> Models.metadata_from_yaml()
228
302
229
- summary = if is_nil(summary) or summary == "" do
230
- contributions = Enum.map(src_commits, & &1.this_commit_contribution) |> Enum.join(" | ")
231
- "Merged branch `#{branch_name}` (#{length(src_commits)} commits). Contributions: #{contributions}"
232
- else
233
- summary
234
- end
303
+ summary = if is_nil(summary) or summary == "" do
304
+ contributions = Enum.map(src_commits, & &1.this_commit_contribution) |> Enum.join(" | ")
305
+ "Merged branch `#{branch_name}` (#{length(src_commits)} commits). Contributions: #{contributions}"
306
+ else
307
+ summary
308
+ end
235
309
236
- # Append OTA records from source to target
237
- if length(src_ota) > 0 do
238
- ts = Models.timestamp()
239
- tgt_log = Path.join(tgt_dir, "log.md")
240
- header = "\n## Merged from `#{branch_name}` (#{ts})\n\n"
241
- ota_text = Enum.map(src_ota, &Models.ota_to_markdown/1) |> Enum.join("")
242
- File.write!(tgt_log, read_file_safe(tgt_log) <> header <> ota_text)
243
- end
310
+ # Append OTA records from source to target
311
+ if length(src_ota) > 0 do
312
+ ts = Models.timestamp()
313
+ tgt_log = Path.join(tgt_dir, "log.md")
314
+ header = "\n## Merged from `#{branch_name}` (#{ts})\n\n"
315
+ ota_text = Enum.map(src_ota, &Models.ota_to_markdown/1) |> Enum.join("")
316
+ File.write!(tgt_log, read_file_safe(tgt_log) <> header <> ota_text)
317
+ end
244
318
245
- # Switch to target and create merge commit
246
- ws = %{ws | current_branch: target}
247
- prev = "Merging branch `#{branch_name}` with purpose: #{src_meta.purpose || ""}"
248
- roadmap_update = "Merged `#{branch_name}`: #{summary}"
249
- {ws, commit_rec} = commit(ws, summary, prev, roadmap_update)
319
+ # Switch to target and create merge commit (using inner to avoid double-lock)
320
+ ws = %{ws | current_branch: target}
321
+ prev = "Merging branch `#{branch_name}` with purpose: #{src_meta.purpose || ""}"
322
+ roadmap_update = "Merged `#{branch_name}`: #{summary}"
323
+ {ws, commit_rec} = commit_inner(ws, summary, prev, roadmap_update)
250
324
251
- # Update source metadata
252
- ts = Models.timestamp()
253
- updated_meta = %{src_meta | status: "merged", merged_into: target, merged_at: ts}
254
- File.write!(Path.join(src_dir, "metadata.yaml"), Models.metadata_to_yaml(updated_meta))
325
+ # Update source metadata
326
+ ts = Models.timestamp()
327
+ updated_meta = %{src_meta | status: "merged", merged_into: target, merged_at: ts}
328
+ File.write!(Path.join(src_dir, "metadata.yaml"), Models.metadata_to_yaml(updated_meta))
255
329
256
- {ws, commit_rec}
330
+ {ws, commit_rec}
331
+ end)
257
332
end
258
333
259
334
# ── Context ──────────────────────────────────────────────────────
261
336
@doc "CONTEXT — hierarchical memory retrieval."
262
337
@spec context(t(), String.t() | nil, non_neg_integer()) :: ContextResult.t()
263
338
def context(ws, branch_name \\ nil, k \\ 1) do
339
+ if k < 1, do: raise(ArgumentError, "k must be >= 1")
264
340
branch_name = branch_name || ws.current_branch
265
341
dir = branch_dir(ws, branch_name)
266
342
267
343
unless File.dir?(dir), do: raise("Branch does not exist: #{branch_name}")
268
344
269
- all_commits = Path.join(dir, "commit.md") |> read_file_safe() |> Models.parse_commits()
270
- all_ota = Path.join(dir, "log.md") |> read_file_safe() |> Models.parse_ota()
271
- roadmap = Path.join(ws.gcc_dir, "main.md") |> read_file_safe()
272
-
273
- meta_text = Path.join(dir, "metadata.yaml") |> read_file_safe()
274
- meta = if meta_text != "", do: Models.metadata_from_yaml(meta_text), else: nil
275
-
276
- commits = Enum.take(all_commits, -k)
277
-
278
- %ContextResult{
279
- branch_name: branch_name,
280
- k: k,
281
- commits: commits,
282
- ota_records: all_ota,
283
- main_roadmap: roadmap,
284
- metadata: meta
285
- }
345
+ with_lock(ws, fn ->
346
+ all_commits = Path.join(dir, "commit.md") |> read_file_safe() |> Models.parse_commits()
347
+ all_ota = Path.join(dir, "log.md") |> read_file_safe() |> Models.parse_ota()
348
+ roadmap = Path.join(ws.gcc_dir, "main.md") |> read_file_safe()
349
+
350
+ meta_text = Path.join(dir, "metadata.yaml") |> read_file_safe()
351
+ meta = if meta_text != "", do: Models.metadata_from_yaml(meta_text), else: nil
352
+
353
+ commits = Enum.take(all_commits, -k)
354
+
355
+ %ContextResult{
356
+ branch_name: branch_name,
357
+ k: k,
358
+ commits: commits,
359
+ ota_records: all_ota,
360
+ main_roadmap: roadmap,
361
+ metadata: meta
362
+ }
363
+ end)
286
364
end
287
365
end
288
366
▼ELIXIR/mix.exs
+4-4
1
-defmodule Contexa.MixProject do
1
+defmodule Cortexa.MixProject do
2
2
use Mix.Project
3
3
4
4
@version "0.1.1"
5
- @source_url "https://github.com/swadhinbiswas/contexa"
5
+ @source_url "https://github.com/swadhinbiswas/Cortexa"
6
6
7
7
def project do
8
8
[
9
- app: :contexa,
9
+ app: :cortexa,
10
10
version: @version,
11
11
elixir: "~> 1.14",
12
12
start_permanent: Mix.env() == :prod,
45
45
46
46
defp package do
47
47
[
48
- name: "contexa",
48
+ name: "cortexa",
49
49
licenses: ["MIT"],
50
50
links: %{
51
51
"GitHub" => @source_url,
52
52
▼ELIXIR/test/models_test.exs
+3-3
1
-defmodule Contexa.ModelsTest do
1
+defmodule Cortexa.ModelsTest do
2
2
use ExUnit.Case, async: true
3
3
4
- alias Contexa.Models
5
- alias Contexa.Models.{OTARecord, CommitRecord, BranchMetadata}
4
+ alias Cortexa.Models
5
+ alias Cortexa.Models.{OTARecord, CommitRecord, BranchMetadata}
6
6
7
7
test "generate_id returns 8 hex chars" do
8
8
id = Models.generate_id()
9
9
▼ELIXIR/test/workspace_test.exs
+4-4
1
-defmodule Contexa.WorkspaceTest do
1
+defmodule Cortexa.WorkspaceTest do
2
2
use ExUnit.Case, async: true
3
3
4
- alias Contexa.Workspace
5
- alias Contexa.Models
4
+ alias Cortexa.Workspace
5
+ alias Cortexa.Models
6
6
7
7
defp make_tmp do
8
- dir = Path.join(System.tmp_dir!(), "contexa_ex_test_#{System.unique_integer([:positive])}")
8
+ dir = Path.join(System.tmp_dir!(), "cortexa_ex_test_#{System.unique_integer([:positive])}")
9
9
File.rm_rf!(dir)
10
10
File.mkdir_p!(dir)
11
11
dir
12
12