refactor(go): rename module and package from 'contexa' to 'cortexa'

Swadhin Biswas committed 7 months ago
76bac60
1 parent ff86ca4
commit 76bac60
5 files changed
+669 -172
▼GO/README.md
+10-10
1
-# contexa
1
+# cortexa
2
2
3
-[![Go Reference](https://pkg.go.dev/badge/github.com/swadhinbiswas/contexa/GO.svg)](https://pkg.go.dev/github.com/swadhinbiswas/contexa/GO)
3
+[![Go Reference](https://pkg.go.dev/badge/github.com/swadhinbiswas/Cortexa/GO.svg)](https://pkg.go.dev/github.com/swadhinbiswas/Cortexa/GO)
4
4
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
-[![GitHub](https://img.shields.io/badge/GitHub-swadhinbiswas%2Fcontexa-black.svg?logo=github)](https://github.com/swadhinbiswas/contexa)
5
+[![GitHub](https://img.shields.io/badge/GitHub-swadhinbiswas%2FCortexa-black.svg?logo=github)](https://github.com/swadhinbiswas/Cortexa)
6
6
7
7
**Git-inspired context management for LLM agents.** COMMIT, BRANCH, MERGE, and CONTEXT operations over a persistent versioned memory workspace.
8
8
9
-Go implementation of the **contexa** framework.
9
+Go implementation of the **cortexa** framework.
10
10
11
11
Based on: [arXiv:2508.00031](https://arxiv.org/abs/2508.00031) -- *"Git Context Controller: Manage the Context of LLM-based Agents like Git"* (Junde Wu et al., 2025)
12
12
15
15
## Installation
16
16
17
17
```bash
18
-go get github.com/swadhinbiswas/contexa/GO/cortexa
18
+go get github.com/swadhinbiswas/Cortexa/GO/cortexa
19
19
```
20
20
21
21
---
27
27
28
28
import (
29
29
"fmt"
30
- "github.com/swadhinbiswas/contexa/GO/cortexa"
30
+ "github.com/swadhinbiswas/Cortexa/GO/cortexa"
31
31
)
32
32
33
33
func main() {
175
175
## Running Tests
176
176
177
177
```bash
178
-git clone https://github.com/swadhinbiswas/contexa.git
179
-cd contexa/GO
178
+git clone https://github.com/swadhinbiswas/Cortexa.git
179
+cd cortexa/GO
180
180
go test -v ./cortexa/
181
181
```
182
182
212
212
213
213
## Links
214
214
215
-- [GitHub Repository](https://github.com/swadhinbiswas/contexa)
216
-- [pkg.go.dev](https://pkg.go.dev/github.com/swadhinbiswas/contexa/GO)
215
+- [GitHub Repository](https://github.com/swadhinbiswas/Cortexa)
216
+- [pkg.go.dev](https://pkg.go.dev/github.com/swadhinbiswas/Cortexa/GO)
217
217
- [Original Paper](https://arxiv.org/abs/2508.00031)
218
218
- [Author: Swadhin Biswas](https://github.com/swadhinbiswas)
219
219
▼GO/cortexa/interop_test.go
+303-0
1
+package cortexa
2
+
3
+// interop_test.go — Cross-language interoperability tests.
4
+//
5
+// These tests verify that the on-disk .GCC/ format produced by the Go
6
+// implementation matches the specification that all 7 language
7
+// implementations (Python, JS/TS, Rust, Go, Zig, Lua, Elixir) agree on.
8
+//
9
+// They also verify that Go can read a workspace hand-crafted in the
10
+// canonical format (simulating what any other language would produce).
11
+
12
+import (
13
+ "os"
14
+ "path/filepath"
15
+ "strings"
16
+ "testing"
17
+)
18
+
19
+// ------------------------------------------------------------------ //
20
+// Format verification: Go writes → verify raw files //
21
+// ------------------------------------------------------------------ //
22
+
23
+func TestInterop_OTASeparatorFormat(t *testing.T) {
24
+ ws, _ := setupWorkspace(t, "interop test")
25
+ ws.LogOTA("obs1", "thought1", "act1")
26
+ ws.LogOTA("obs2", "thought2", "act2")
27
+
28
+ raw := ws.read(ws.logPath(mainBranch))
29
+
30
+ // OTA blocks MUST be separated by "---\n" (3 dashes), NOT "--------\n"
31
+ if strings.Contains(raw, "--------") {
32
+ t.Fatal("log.md must use '---' separator (3 dashes), not 8 dashes")
33
+ }
34
+ if !strings.Contains(raw, "\n---\n") {
35
+ t.Fatal("log.md must contain '---' separator between OTA blocks")
36
+ }
37
+
38
+ // Headers must use em-dash: "### Step N — timestamp"
39
+ if strings.Contains(raw, "Step 1-") || strings.Contains(raw, "Step 2-") {
40
+ t.Fatal("OTA headers must use em-dash ' — ', not hyphen '-'")
41
+ }
42
+ if !strings.Contains(raw, " — ") {
43
+ t.Fatal("OTA headers must contain em-dash ' — '")
44
+ }
45
+}
46
+
47
+func TestInterop_CommitSeparatorFormat(t *testing.T) {
48
+ ws, _ := setupWorkspace(t, "interop test")
49
+ ws.Commit("First milestone", nil, nil)
50
+ ws.Commit("Second milestone", nil, nil)
51
+
52
+ raw := ws.read(ws.commitPath(mainBranch))
53
+
54
+ // Commit blocks MUST be separated by "---\n"
55
+ parts := strings.Split(raw, "\n---\n")
56
+ if len(parts) < 2 {
57
+ t.Fatalf("commit.md must have at least 2 blocks separated by '---', got %d", len(parts))
58
+ }
59
+
60
+ // Each commit must start with "## Commit `<id>`" (first block may have a header)
61
+ for i, part := range parts {
62
+ trimmed := strings.TrimSpace(part)
63
+ if trimmed == "" {
64
+ continue
65
+ }
66
+ // The first block may contain a "# Commit History" header before the commit
67
+ if i == 0 && strings.HasPrefix(trimmed, "# Commit History") {
68
+ // Check that the commit still appears after the header
69
+ if !strings.Contains(trimmed, "## Commit `") {
70
+ t.Errorf("first block has header but no commit: %q", trimmed[:min(80, len(trimmed))])
71
+ }
72
+ continue
73
+ }
74
+ if !strings.HasPrefix(trimmed, "## Commit `") {
75
+ t.Errorf("commit block must start with '## Commit `<id>`', got: %q",
76
+ trimmed[:min(60, len(trimmed))])
77
+ }
78
+ }
79
+}
80
+
81
+func TestInterop_MetadataYAMLFormat(t *testing.T) {
82
+ ws, _ := setupWorkspace(t, "interop test")
83
+ ws.Branch("feature", "Test cross-lang metadata")
84
+
85
+ raw, err := os.ReadFile(ws.metaPath("feature"))
86
+ if err != nil {
87
+ t.Fatalf("read metadata.yaml: %v", err)
88
+ }
89
+
90
+ content := string(raw)
91
+
92
+ // Must contain required YAML fields
93
+ for _, field := range []string{"name:", "purpose:", "created_from:", "created_at:", "status:"} {
94
+ if !strings.Contains(content, field) {
95
+ t.Errorf("metadata.yaml missing required field: %s", field)
96
+ }
97
+ }
98
+
99
+ // Verify values
100
+ if !strings.Contains(content, "name: feature") {
101
+ t.Error("metadata.yaml name should be 'feature'")
102
+ }
103
+ if !strings.Contains(content, "status: active") {
104
+ t.Error("new branch metadata should have status: active")
105
+ }
106
+}
107
+
108
+func TestInterop_MergeUpdatesMetadataStatus(t *testing.T) {
109
+ ws, _ := setupWorkspace(t, "interop test")
110
+ ws.Branch("exp", "experiment")
111
+ ws.Commit("exp work", nil, nil)
112
+ ws.Merge("exp", nil, mainBranch)
113
+
114
+ raw, err := os.ReadFile(ws.metaPath("exp"))
115
+ if err != nil {
116
+ t.Fatalf("read metadata.yaml: %v", err)
117
+ }
118
+ content := string(raw)
119
+
120
+ if !strings.Contains(content, "status: merged") {
121
+ t.Error("merged branch metadata should have status: merged")
122
+ }
123
+ if !strings.Contains(content, "merged_into:") {
124
+ t.Error("merged branch metadata should have merged_into field")
125
+ }
126
+ if !strings.Contains(content, "merged_at:") {
127
+ t.Error("merged branch metadata should have merged_at field")
128
+ }
129
+}
130
+
131
+func TestInterop_DirectoryLayout(t *testing.T) {
132
+ ws, _ := setupWorkspace(t, "interop test")
133
+ ws.Branch("alt", "alternative")
134
+
135
+ // Verify exact directory layout
136
+ checks := []struct {
137
+ path string
138
+ isDir bool
139
+ }{
140
+ {filepath.Join(ws.root, gccDir, "main.md"), false},
141
+ {filepath.Join(ws.root, gccDir, "branches"), true},
142
+ {filepath.Join(ws.root, gccDir, "branches", "main"), true},
143
+ {filepath.Join(ws.root, gccDir, "branches", "main", "log.md"), false},
144
+ {filepath.Join(ws.root, gccDir, "branches", "main", "commit.md"), false},
145
+ {filepath.Join(ws.root, gccDir, "branches", "main", "metadata.yaml"), false},
146
+ {filepath.Join(ws.root, gccDir, "branches", "alt"), true},
147
+ {filepath.Join(ws.root, gccDir, "branches", "alt", "log.md"), false},
148
+ {filepath.Join(ws.root, gccDir, "branches", "alt", "commit.md"), false},
149
+ {filepath.Join(ws.root, gccDir, "branches", "alt", "metadata.yaml"), false},
150
+ }
151
+ for _, c := range checks {
152
+ info, err := os.Stat(c.path)
153
+ if err != nil {
154
+ t.Errorf("expected %s to exist", c.path)
155
+ continue
156
+ }
157
+ if c.isDir && !info.IsDir() {
158
+ t.Errorf("expected %s to be a directory", c.path)
159
+ }
160
+ if !c.isDir && info.IsDir() {
161
+ t.Errorf("expected %s to be a file", c.path)
162
+ }
163
+ }
164
+}
165
+
166
+// ------------------------------------------------------------------ //
167
+// Round-trip: hand-craft workspace → Go reads it //
168
+// ------------------------------------------------------------------ //
169
+
170
+func TestInterop_ReadHandCraftedWorkspace(t *testing.T) {
171
+ // Simulate what another language (e.g., Python, Zig, Rust) would write
172
+ dir := t.TempDir()
173
+ gccRoot := filepath.Join(dir, ".GCC")
174
+ branchDir := filepath.Join(gccRoot, "branches", "main")
175
+ os.MkdirAll(branchDir, 0o755)
176
+
177
+ // main.md — roadmap
178
+ os.WriteFile(filepath.Join(gccRoot, "main.md"),
179
+ []byte("# Roadmap\nBuild a cross-language test system\n"), 0o644)
180
+
181
+ // metadata.yaml — hand-crafted
182
+ os.WriteFile(filepath.Join(branchDir, "metadata.yaml"),
183
+ []byte("name: main\npurpose: primary development branch\ncreated_from: \"\"\ncreated_at: \"2025-06-01T00:00:00Z\"\nstatus: active\n"), 0o644)
184
+
185
+ // log.md — two OTA steps with canonical format
186
+ os.WriteFile(filepath.Join(branchDir, "log.md"),
187
+ []byte("# OTA Log — main\n\n"+
188
+ "### Step 1 — 2025-06-01T00:00:01Z\n"+
189
+ "**Observation:** Saw the input data\n\n"+
190
+ "**Thought:** Need to process it\n\n"+
191
+ "**Action:** Called the API\n\n"+
192
+ "---\n"+
193
+ "### Step 2 — 2025-06-01T00:00:02Z\n"+
194
+ "**Observation:** Got response\n\n"+
195
+ "**Thought:** Looks correct\n\n"+
196
+ "**Action:** Saved results\n\n"+
197
+ "---\n"),
198
+ 0o644)
199
+
200
+ // commit.md — two commits with canonical format
201
+ os.WriteFile(filepath.Join(branchDir, "commit.md"),
202
+ []byte("## Commit `abc12345`\n"+
203
+ "**Timestamp:** 2025-06-01T00:00:10Z\n\n"+
204
+ "**Branch Purpose:** primary development branch\n\n"+
205
+ "**Previous Progress Summary:** Initial state — no prior commits.\n\n"+
206
+ "**This Commit's Contribution:** Completed data ingestion\n\n"+
207
+ "---\n"+
208
+ "## Commit `def67890`\n"+
209
+ "**Timestamp:** 2025-06-01T00:00:20Z\n\n"+
210
+ "**Branch Purpose:** primary development branch\n\n"+
211
+ "**Previous Progress Summary:** Completed data ingestion\n\n"+
212
+ "**This Commit's Contribution:** Added validation layer\n\n"+
213
+ "---\n"),
214
+ 0o644)
215
+
216
+ // Now read it with Go's workspace
217
+ ws := New(dir)
218
+
219
+ // Context should parse everything
220
+ ctx, err := ws.Context(nil, 5)
221
+ if err != nil {
222
+ t.Fatalf("Context on hand-crafted workspace: %v", err)
223
+ }
224
+
225
+ if !strings.Contains(ctx.MainRoadmap, "cross-language test system") {
226
+ t.Error("should read roadmap from hand-crafted main.md")
227
+ }
228
+
229
+ if len(ctx.Commits) != 2 {
230
+ t.Fatalf("expected 2 commits, got %d", len(ctx.Commits))
231
+ }
232
+
233
+ if ctx.Commits[0].CommitID != "abc12345" {
234
+ t.Errorf("first commit ID should be abc12345, got %s", ctx.Commits[0].CommitID)
235
+ }
236
+ if ctx.Commits[1].ThisCommitContribution != "Added validation layer" {
237
+ t.Errorf("second commit contribution wrong: %s", ctx.Commits[1].ThisCommitContribution)
238
+ }
239
+
240
+ // K=1 should return only the last commit
241
+ ctx1, err := ws.Context(nil, 1)
242
+ if err != nil {
243
+ t.Fatalf("Context K=1: %v", err)
244
+ }
245
+ if len(ctx1.Commits) != 1 {
246
+ t.Fatalf("K=1 should return 1 commit, got %d", len(ctx1.Commits))
247
+ }
248
+ if ctx1.Commits[0].CommitID != "def67890" {
249
+ t.Error("K=1 should return the most recent commit")
250
+ }
251
+}
252
+
253
+func TestInterop_SanitizationRoundTrip(t *testing.T) {
254
+ ws, _ := setupWorkspace(t, "interop sanitization test")
255
+
256
+ // Content that contains the separator sequence
257
+ dangerous := "Here is a separator:\n---\nThis should not break parsing"
258
+ ws.LogOTA(dangerous, "safe thought", "safe action")
259
+ ws.Commit(dangerous, nil, nil)
260
+
261
+ // Read back via Context
262
+ ctx, err := ws.Context(nil, 1)
263
+ if err != nil {
264
+ t.Fatalf("Context after sanitized write: %v", err)
265
+ }
266
+
267
+ // The commit contribution should contain the original text (desanitized)
268
+ if ctx.Commits[0].ThisCommitContribution != dangerous {
269
+ t.Errorf("round-trip sanitization failed.\nExpected: %q\nGot: %q",
270
+ dangerous, ctx.Commits[0].ThisCommitContribution)
271
+ }
272
+
273
+ // Raw file should contain the escaped form, not the raw separator
274
+ rawLog := ws.read(ws.logPath(mainBranch))
275
+ if strings.Contains(rawLog, "\n---\nThis should not break") {
276
+ t.Error("raw log.md should contain escaped separator, not raw")
277
+ }
278
+ if !strings.Contains(rawLog, "\\---") {
279
+ t.Error("raw log.md should contain escaped '\\---'")
280
+ }
281
+}
282
+
283
+func TestInterop_BranchLogHasHeader(t *testing.T) {
284
+ ws, _ := setupWorkspace(t, "interop test")
285
+ ws.Branch("feat", "new feature")
286
+
287
+ raw := ws.read(ws.logPath("feat"))
288
+ // All languages write "# OTA Log — <branch>\n" as the first line
289
+ if !strings.HasPrefix(raw, "# OTA Log") {
290
+ t.Errorf("log.md should start with '# OTA Log', got: %q",
291
+ raw[:min(40, len(raw))])
292
+ }
293
+ if !strings.Contains(raw, "feat") {
294
+ t.Error("log.md header should contain branch name")
295
+ }
296
+}
297
+
298
+func min(a, b int) int {
299
+ if a < b {
300
+ return a
301
+ }
302
+ return b
303
+}
0
304
▼GO/cortexa/models.go
+34-4
1
-// Package cortexa implements contexa — Git-inspired context management
1
+// Package cortexa implements cortexa — Git-inspired context management
2
2
// for LLM agents. COMMIT, BRANCH, MERGE, and CONTEXT over versioned memory.
3
3
//
4
4
// Paper: "Git Context Controller: Manage the Context of LLM-based Agents like Git"
13
13
"strings"
14
14
)
15
15
16
+// Sanitize escapes separator sequences in user-provided content.
17
+func Sanitize(text string) string {
18
+ return strings.Replace(text, "\n---\n", "\n\\---\n", -1)
19
+}
20
+
21
+// Desanitize reverses the escaping applied by Sanitize.
22
+func Desanitize(text string) string {
23
+ return strings.Replace(text, "\n\\---\n", "\n---\n", -1)
24
+}
25
+
26
+// SplitBlocks splits markdown text on the "---\n" separator while
27
+// respecting escaped separators ("\---\n" produced by Sanitize).
28
+// After splitting, escaped separators are left in place; callers
29
+// should use Desanitize on individual field values.
30
+func SplitBlocks(text string) []string {
31
+ raw := strings.Split(text, "---\n")
32
+ var blocks []string
33
+ for i := 0; i < len(raw); i++ {
34
+ block := raw[i]
35
+ // If block ends with '\', the "---\n" was actually an escaped
36
+ // separator — rejoin with the next fragment.
37
+ for strings.HasSuffix(block, "\\") && i+1 < len(raw) {
38
+ i++
39
+ block = block + "---\n" + raw[i]
40
+ }
41
+ blocks = append(blocks, block)
42
+ }
43
+ return blocks
44
+}
45
+
16
46
// OTARecord represents a single Observation–Thought–Action cycle logged to log.md.
17
47
type OTARecord struct {
18
48
Step int `yaml:"step"`
26
56
func (r OTARecord) ToMarkdown() string {
27
57
return fmt.Sprintf(
28
58
"### Step %d — %s\n**Observation:** %s\n\n**Thought:** %s\n\n**Action:** %s\n\n---\n",
29
- r.Step, r.Timestamp, r.Observation, r.Thought, r.Action,
59
+ r.Step, r.Timestamp, Sanitize(r.Observation), Sanitize(r.Thought), Sanitize(r.Action),
30
60
)
31
61
}
32
62
47
77
"## Commit `%s`\n**Timestamp:** %s\n\n**Branch Purpose:** %s\n\n"+
48
78
"**Previous Progress Summary:** %s\n\n"+
49
79
"**This Commit's Contribution:** %s\n\n---\n",
50
- c.CommitID, c.Timestamp, c.BranchPurpose,
51
- c.PreviousProgressSummary, c.ThisCommitContribution,
80
+ c.CommitID, c.Timestamp, Sanitize(c.BranchPurpose),
81
+ Sanitize(c.PreviousProgressSummary), Sanitize(c.ThisCommitContribution),
52
82
)
53
83
}
54
84
55
85
▼GO/cortexa/workspace.go
+321-157
7
7
"os"
8
8
"path/filepath"
9
9
"strings"
10
+ "syscall"
10
11
"time"
11
12
12
13
"gopkg.in/yaml.v3"
27
28
return hex.EncodeToString(b)
28
29
}
29
30
31
+// validateNotEmpty returns an error if value is empty or whitespace-only.
32
+func validateNotEmpty(value, field string) error {
33
+ if strings.TrimSpace(value) == "" {
34
+ return fmt.Errorf("%s must not be empty", field)
35
+ }
36
+ return nil
37
+}
38
+
39
+// validateBranchName returns an error if name is not a valid branch identifier.
40
+func validateBranchName(name string) error {
41
+ if err := validateNotEmpty(name, "branch name"); err != nil {
42
+ return err
43
+ }
44
+ if strings.Contains(name, "/") || strings.Contains(name, "\\") {
45
+ return fmt.Errorf("branch name must not contain path separators: %q", name)
46
+ }
47
+ if name == "." || name == ".." {
48
+ return fmt.Errorf("branch name must not be '.' or '..': %q", name)
49
+ }
50
+ return nil
51
+}
52
+
30
53
// Workspace manages the .GCC/ directory for one agent project.
31
54
//
32
55
// Implements the four GCC commands (arXiv:2508.00031v2):
82
105
return filepath.Join(w.gccPath, "main.md")
83
106
}
84
107
108
+// ------------------------------------------------------------------ //
109
+// File locking //
110
+// ------------------------------------------------------------------ //
111
+
112
+// withLock acquires an exclusive file lock on .GCC/.lock, runs fn,
113
+// then releases the lock. This serialises concurrent access to the
114
+// workspace from multiple processes/goroutines.
115
+func (w *Workspace) withLock(fn func() error) error {
116
+ lockPath := filepath.Join(w.gccPath, ".lock")
117
+ if err := os.MkdirAll(filepath.Dir(lockPath), 0o755); err != nil {
118
+ return err
119
+ }
120
+ f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o644)
121
+ if err != nil {
122
+ return err
123
+ }
124
+ defer f.Close()
125
+
126
+ if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
127
+ return fmt.Errorf("failed to acquire lock: %w", err)
128
+ }
129
+ defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
130
+
131
+ return fn()
132
+}
133
+
134
+// withLockReturn is like withLock but returns a value along with an error.
135
+func withLockReturn[T any](w *Workspace, fn func() (T, error)) (T, error) {
136
+ var result T
137
+ err := w.withLock(func() error {
138
+ var innerErr error
139
+ result, innerErr = fn()
140
+ return innerErr
141
+ })
142
+ return result, err
143
+}
144
+
85
145
// ------------------------------------------------------------------ //
86
146
// I/O helpers //
87
147
// ------------------------------------------------------------------ //
114
174
func (w *Workspace) parseCommits(branch string) []CommitRecord {
115
175
text := w.read(w.commitPath(branch))
116
176
var records []CommitRecord
117
- for _, block := range strings.Split(text, "---\n") {
177
+ for _, block := range SplitBlocks(text) {
118
178
block = strings.TrimSpace(block)
119
179
if block == "" {
120
180
continue
121
181
}
122
182
var (
123
- commitID, branchPurpose, prevSummary, contribution, ts string
183
+ commitID string
184
+ ts string
124
185
)
186
+ // Accumulate multi-line field values keyed by field name.
187
+ fields := map[string]*strings.Builder{}
188
+ var current *strings.Builder
189
+
125
190
for _, line := range strings.Split(block, "\n") {
126
191
switch {
127
192
case strings.HasPrefix(line, "## Commit `"):
128
193
commitID = strings.TrimSuffix(strings.TrimPrefix(line, "## Commit `"), "`")
194
+ current = nil
129
195
case strings.HasPrefix(line, "**Timestamp:**"):
130
196
ts = strings.TrimSpace(strings.TrimPrefix(line, "**Timestamp:**"))
197
+ current = nil
131
198
case strings.HasPrefix(line, "**Branch Purpose:**"):
132
- branchPurpose = strings.TrimSpace(strings.TrimPrefix(line, "**Branch Purpose:**"))
199
+ b := &strings.Builder{}
200
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**Branch Purpose:**")))
201
+ fields["branch_purpose"] = b
202
+ current = b
133
203
case strings.HasPrefix(line, "**Previous Progress Summary:**"):
134
- prevSummary = strings.TrimSpace(strings.TrimPrefix(line, "**Previous Progress Summary:**"))
204
+ b := &strings.Builder{}
205
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**Previous Progress Summary:**")))
206
+ fields["prev_summary"] = b
207
+ current = b
135
208
case strings.HasPrefix(line, "**This Commit's Contribution:**"):
136
- contribution = strings.TrimSpace(strings.TrimPrefix(line, "**This Commit's Contribution:**"))
209
+ b := &strings.Builder{}
210
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**This Commit's Contribution:**")))
211
+ fields["contribution"] = b
212
+ current = b
213
+ default:
214
+ // Continuation line for the current field
215
+ if current != nil && line != "" {
216
+ current.WriteString("\n")
217
+ current.WriteString(line)
218
+ }
137
219
}
138
220
}
139
221
if commitID != "" {
222
+ getField := func(key string) string {
223
+ if b, ok := fields[key]; ok {
224
+ return Desanitize(strings.TrimSpace(b.String()))
225
+ }
226
+ return ""
227
+ }
140
228
records = append(records, CommitRecord{
141
229
CommitID: commitID,
142
230
BranchName: branch,
143
- BranchPurpose: branchPurpose,
144
- PreviousProgressSummary: prevSummary,
145
- ThisCommitContribution: contribution,
231
+ BranchPurpose: getField("branch_purpose"),
232
+ PreviousProgressSummary: getField("prev_summary"),
233
+ ThisCommitContribution: getField("contribution"),
146
234
Timestamp: ts,
147
235
})
148
236
}
153
241
func (w *Workspace) parseOTA(branch string) []OTARecord {
154
242
text := w.read(w.logPath(branch))
155
243
var records []OTARecord
156
- for _, block := range strings.Split(text, "---\n") {
244
+ for _, block := range SplitBlocks(text) {
157
245
block = strings.TrimSpace(block)
158
246
if block == "" {
159
247
continue
160
248
}
161
249
var step int
162
- var ts, obs, thought, action string
250
+ var ts string
251
+ fields := map[string]*strings.Builder{}
252
+ var current *strings.Builder
253
+
163
254
for _, line := range strings.Split(block, "\n") {
164
255
switch {
165
256
case strings.HasPrefix(line, "### Step "):
168
259
if len(parts) > 1 {
169
260
ts = strings.TrimSpace(parts[1])
170
261
}
262
+ current = nil
171
263
case strings.HasPrefix(line, "**Observation:**"):
172
- obs = strings.TrimSpace(strings.TrimPrefix(line, "**Observation:**"))
264
+ b := &strings.Builder{}
265
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**Observation:**")))
266
+ fields["obs"] = b
267
+ current = b
173
268
case strings.HasPrefix(line, "**Thought:**"):
174
- thought = strings.TrimSpace(strings.TrimPrefix(line, "**Thought:**"))
269
+ b := &strings.Builder{}
270
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**Thought:**")))
271
+ fields["thought"] = b
272
+ current = b
175
273
case strings.HasPrefix(line, "**Action:**"):
176
- action = strings.TrimSpace(strings.TrimPrefix(line, "**Action:**"))
274
+ b := &strings.Builder{}
275
+ b.WriteString(strings.TrimSpace(strings.TrimPrefix(line, "**Action:**")))
276
+ fields["action"] = b
277
+ current = b
278
+ default:
279
+ if current != nil && line != "" {
280
+ current.WriteString("\n")
281
+ current.WriteString(line)
282
+ }
283
+ }
284
+ }
285
+ getField := func(key string) string {
286
+ if b, ok := fields[key]; ok {
287
+ return Desanitize(strings.TrimSpace(b.String()))
177
288
}
289
+ return ""
178
290
}
291
+ obs := getField("obs")
292
+ thought := getField("thought")
179
293
if obs != "" || thought != "" {
180
294
records = append(records, OTARecord{
181
295
Step: step,
182
296
Timestamp: ts,
183
297
Observation: obs,
184
298
Thought: thought,
185
- Action: action,
299
+ Action: getField("action"),
186
300
})
187
301
}
188
302
}
214
328
return err
215
329
}
216
330
217
- roadmap := fmt.Sprintf("# Project Roadmap\n\n**Initialized:** %s\n\n%s\n", nowISO(), projectRoadmap)
218
- if err := w.write(w.mainMd(), roadmap); err != nil {
219
- return err
220
- }
221
- if err := w.write(w.logPath(mainBranch), fmt.Sprintf("# OTA Log — branch `%s`\n\n", mainBranch)); err != nil {
222
- return err
223
- }
224
- if err := w.write(w.commitPath(mainBranch), fmt.Sprintf("# Commit History — branch `%s`\n\n", mainBranch)); err != nil {
225
- return err
226
- }
331
+ return w.withLock(func() error {
332
+ roadmap := fmt.Sprintf("# Project Roadmap\n\n**Initialized:** %s\n\n%s\n", nowISO(), projectRoadmap)
333
+ if err := w.write(w.mainMd(), roadmap); err != nil {
334
+ return err
335
+ }
336
+ if err := w.write(w.logPath(mainBranch), fmt.Sprintf("# OTA Log — branch `%s`\n\n", mainBranch)); err != nil {
337
+ return err
338
+ }
339
+ if err := w.write(w.commitPath(mainBranch), fmt.Sprintf("# Commit History — branch `%s`\n\n", mainBranch)); err != nil {
340
+ return err
341
+ }
227
342
228
- meta := BranchMetadata{
229
- Name: mainBranch,
230
- Purpose: "Primary reasoning trajectory",
231
- CreatedFrom: "",
232
- CreatedAt: nowISO(),
233
- Status: "active",
234
- }
235
- data, err := yaml.Marshal(meta)
236
- if err != nil {
237
- return err
238
- }
239
- if err := w.write(w.metaPath(mainBranch), string(data)); err != nil {
240
- return err
241
- }
343
+ meta := BranchMetadata{
344
+ Name: mainBranch,
345
+ Purpose: "Primary reasoning trajectory",
346
+ CreatedFrom: "",
347
+ CreatedAt: nowISO(),
348
+ Status: "active",
349
+ }
350
+ data, err := yaml.Marshal(meta)
351
+ if err != nil {
352
+ return err
353
+ }
354
+ if err := w.write(w.metaPath(mainBranch), string(data)); err != nil {
355
+ return err
356
+ }
242
357
243
- w.currentBranch = mainBranch
244
- return nil
358
+ w.currentBranch = mainBranch
359
+ return nil
360
+ })
245
361
}
246
362
247
363
// Load opens an existing GCC workspace.
259
375
260
376
// LogOTA appends an Observation–Thought–Action step to the current branch's log.md.
261
377
func (w *Workspace) LogOTA(observation, thought, action string) (OTARecord, error) {
262
- existing := w.parseOTA(w.currentBranch)
263
- record := OTARecord{
264
- Step: len(existing) + 1,
265
- Timestamp: nowISO(),
266
- Observation: observation,
267
- Thought: thought,
268
- Action: action,
269
- }
270
- if err := w.appendFile(w.logPath(w.currentBranch), record.ToMarkdown()); err != nil {
271
- return OTARecord{}, err
272
- }
273
- return record, nil
378
+ if strings.TrimSpace(observation) == "" && strings.TrimSpace(thought) == "" && strings.TrimSpace(action) == "" {
379
+ return OTARecord{}, fmt.Errorf("at least one of observation, thought, or action must be non-empty")
380
+ }
381
+ return withLockReturn(w, func() (OTARecord, error) {
382
+ existing := w.parseOTA(w.currentBranch)
383
+ record := OTARecord{
384
+ Step: len(existing) + 1,
385
+ Timestamp: nowISO(),
386
+ Observation: observation,
387
+ Thought: thought,
388
+ Action: action,
389
+ }
390
+ if err := w.appendFile(w.logPath(w.currentBranch), record.ToMarkdown()); err != nil {
391
+ return OTARecord{}, err
392
+ }
393
+ return record, nil
394
+ })
274
395
}
275
396
276
397
// Commit is the COMMIT command (paper §3.2).
277
398
// Checkpoints milestone with: Branch Purpose, Previous Progress Summary,
278
399
// This Commit's Contribution.
279
400
func (w *Workspace) Commit(contribution string, previousSummary *string, updateRoadmap *string) (CommitRecord, error) {
280
- meta, _ := w.parseMeta(w.currentBranch)
281
- branchPurpose := ""
282
- if meta != nil {
283
- branchPurpose = meta.Purpose
401
+ if err := validateNotEmpty(contribution, "contribution"); err != nil {
402
+ return CommitRecord{}, err
284
403
}
404
+ return withLockReturn(w, func() (CommitRecord, error) {
405
+ meta, _ := w.parseMeta(w.currentBranch)
406
+ branchPurpose := ""
407
+ if meta != nil {
408
+ branchPurpose = meta.Purpose
409
+ }
285
410
286
- prevSummary := "Initial state — no prior commits."
287
- if previousSummary != nil {
288
- prevSummary = *previousSummary
289
- } else {
290
- commits := w.parseCommits(w.currentBranch)
291
- if len(commits) > 0 {
292
- prevSummary = commits[len(commits)-1].ThisCommitContribution
411
+ prevSummary := "Initial state — no prior commits."
412
+ if previousSummary != nil {
413
+ prevSummary = *previousSummary
414
+ } else {
415
+ commits := w.parseCommits(w.currentBranch)
416
+ if len(commits) > 0 {
417
+ prevSummary = commits[len(commits)-1].ThisCommitContribution
418
+ }
293
419
}
294
- }
295
420
296
- record := CommitRecord{
297
- CommitID: shortID(),
298
- BranchName: w.currentBranch,
299
- BranchPurpose: branchPurpose,
300
- PreviousProgressSummary: prevSummary,
301
- ThisCommitContribution: contribution,
302
- Timestamp: nowISO(),
303
- }
421
+ record := CommitRecord{
422
+ CommitID: shortID(),
423
+ BranchName: w.currentBranch,
424
+ BranchPurpose: branchPurpose,
425
+ PreviousProgressSummary: prevSummary,
426
+ ThisCommitContribution: contribution,
427
+ Timestamp: nowISO(),
428
+ }
304
429
305
- if err := w.appendFile(w.commitPath(w.currentBranch), record.ToMarkdown()); err != nil {
306
- return CommitRecord{}, err
307
- }
308
- if updateRoadmap != nil {
309
- update := fmt.Sprintf("\n## Update (%s)\n%s\n", record.Timestamp, *updateRoadmap)
310
- if err := w.appendFile(w.mainMd(), update); err != nil {
430
+ if err := w.appendFile(w.commitPath(w.currentBranch), record.ToMarkdown()); err != nil {
311
431
return CommitRecord{}, err
312
432
}
313
- }
314
- return record, nil
433
+ if updateRoadmap != nil {
434
+ update := fmt.Sprintf("\n## Update (%s)\n%s\n", record.Timestamp, *updateRoadmap)
435
+ if err := w.appendFile(w.mainMd(), update); err != nil {
436
+ return CommitRecord{}, err
437
+ }
438
+ }
439
+ return record, nil
440
+ })
315
441
}
316
442
317
443
// Branch is the BRANCH command (paper §3.3).
318
444
// Creates isolated workspace: B_t^(name) = BRANCH(M_{t-1}).
319
445
func (w *Workspace) Branch(name, purpose string) error {
320
- if _, err := os.Stat(w.branchDir(name)); err == nil {
321
- return fmt.Errorf("branch '%s' already exists", name)
322
- }
323
- if err := os.MkdirAll(w.branchDir(name), 0o755); err != nil {
324
- return err
325
- }
326
- if err := w.write(w.logPath(name), fmt.Sprintf("# OTA Log — branch `%s`\n\n", name)); err != nil {
446
+ if err := validateBranchName(name); err != nil {
327
447
return err
328
448
}
329
- if err := w.write(w.commitPath(name), fmt.Sprintf("# Commit History — branch `%s`\n\n", name)); err != nil {
449
+ if err := validateNotEmpty(purpose, "branch purpose"); err != nil {
330
450
return err
331
451
}
332
- meta := BranchMetadata{
333
- Name: name,
334
- Purpose: purpose,
335
- CreatedFrom: w.currentBranch,
336
- CreatedAt: nowISO(),
337
- Status: "active",
338
- }
339
- data, err := yaml.Marshal(meta)
340
- if err != nil {
341
- return err
452
+ if _, err := os.Stat(w.branchDir(name)); err == nil {
453
+ return fmt.Errorf("branch '%s' already exists", name)
342
454
}
343
- if err := w.write(w.metaPath(name), string(data)); err != nil {
455
+ if err := os.MkdirAll(w.branchDir(name), 0o755); err != nil {
344
456
return err
345
457
}
346
- w.currentBranch = name
347
- return nil
458
+ return w.withLock(func() error {
459
+ if err := w.write(w.logPath(name), fmt.Sprintf("# OTA Log — branch `%s`\n\n", name)); err != nil {
460
+ return err
461
+ }
462
+ if err := w.write(w.commitPath(name), fmt.Sprintf("# Commit History — branch `%s`\n\n", name)); err != nil {
463
+ return err
464
+ }
465
+ meta := BranchMetadata{
466
+ Name: name,
467
+ Purpose: purpose,
468
+ CreatedFrom: w.currentBranch,
469
+ CreatedAt: nowISO(),
470
+ Status: "active",
471
+ }
472
+ data, err := yaml.Marshal(meta)
473
+ if err != nil {
474
+ return err
475
+ }
476
+ if err := w.write(w.metaPath(name), string(data)); err != nil {
477
+ return err
478
+ }
479
+ w.currentBranch = name
480
+ return nil
481
+ })
348
482
}
349
483
350
484
// Merge is the MERGE command (paper §3.4).
351
485
// Integrates a completed branch into target, merging summaries and OTA traces.
352
486
func (w *Workspace) Merge(branchName string, summary *string, target string) (CommitRecord, error) {
487
+ if err := validateBranchName(branchName); err != nil {
488
+ return CommitRecord{}, err
489
+ }
490
+ if err := validateBranchName(target); err != nil {
491
+ return CommitRecord{}, err
492
+ }
353
493
if _, err := os.Stat(w.branchDir(branchName)); os.IsNotExist(err) {
354
494
return CommitRecord{}, fmt.Errorf("branch '%s' not found", branchName)
355
495
}
357
497
return CommitRecord{}, fmt.Errorf("target branch '%s' not found", target)
358
498
}
359
499
360
- branchCommits := w.parseCommits(branchName)
361
- branchOTA := w.parseOTA(branchName)
362
- meta, _ := w.parseMeta(branchName)
363
-
364
500
var mergeSummary string
365
- if summary != nil {
366
- mergeSummary = *summary
367
- } else {
368
- contribs := make([]string, len(branchCommits))
369
- for i, c := range branchCommits {
370
- contribs[i] = c.ThisCommitContribution
501
+ var meta *BranchMetadata
502
+
503
+ err := w.withLock(func() error {
504
+ branchCommits := w.parseCommits(branchName)
505
+ branchOTA := w.parseOTA(branchName)
506
+ var parseErr error
507
+ meta, parseErr = w.parseMeta(branchName)
508
+ _ = parseErr
509
+
510
+ if summary != nil {
511
+ mergeSummary = *summary
512
+ } else {
513
+ contribs := make([]string, len(branchCommits))
514
+ for i, c := range branchCommits {
515
+ contribs[i] = c.ThisCommitContribution
516
+ }
517
+ mergeSummary = fmt.Sprintf("Merged branch `%s` (%d commits). Contributions: %s",
518
+ branchName, len(branchCommits), strings.Join(contribs, " | "))
371
519
}
372
- mergeSummary = fmt.Sprintf("Merged branch `%s` (%d commits). Contributions: %s",
373
- branchName, len(branchCommits), strings.Join(contribs, " | "))
374
- }
375
520
376
- if len(branchOTA) > 0 {
377
- header := fmt.Sprintf("\n## Merged from `%s` (%s)\n\n", branchName, nowISO())
378
- if err := w.appendFile(w.logPath(target), header); err != nil {
379
- return CommitRecord{}, err
380
- }
381
- for _, rec := range branchOTA {
382
- if err := w.appendFile(w.logPath(target), rec.ToMarkdown()); err != nil {
383
- return CommitRecord{}, err
521
+ if len(branchOTA) > 0 {
522
+ header := fmt.Sprintf("\n## Merged from `%s` (%s)\n\n", branchName, nowISO())
523
+ if err := w.appendFile(w.logPath(target), header); err != nil {
524
+ return err
525
+ }
526
+ for _, rec := range branchOTA {
527
+ if err := w.appendFile(w.logPath(target), rec.ToMarkdown()); err != nil {
528
+ return err
529
+ }
384
530
}
385
531
}
532
+
533
+ w.currentBranch = target
534
+ return nil
535
+ })
536
+ if err != nil {
537
+ return CommitRecord{}, err
386
538
}
387
539
388
- w.currentBranch = target
540
+ // Commit acquires the lock internally
389
541
metaPurpose := ""
390
542
if meta != nil {
391
543
metaPurpose = meta.Purpose
396
548
return CommitRecord{}, err
397
549
}
398
550
551
+ // Mark branch as merged
399
552
if meta != nil {
400
- meta.Status = "merged"
401
- merged := target
402
- meta.MergedInto = &merged
403
- mergedAt := nowISO()
404
- meta.MergedAt = &mergedAt
405
- data, err := yaml.Marshal(meta)
553
+ err = w.withLock(func() error {
554
+ meta.Status = "merged"
555
+ merged := target
556
+ meta.MergedInto = &merged
557
+ mergedAt := nowISO()
558
+ meta.MergedAt = &mergedAt
559
+ data, err := yaml.Marshal(meta)
560
+ if err != nil {
561
+ return err
562
+ }
563
+ return w.write(w.metaPath(branchName), string(data))
564
+ })
406
565
if err != nil {
407
566
return CommitRecord{}, err
408
567
}
409
- if err := w.write(w.metaPath(branchName), string(data)); err != nil {
410
- return CommitRecord{}, err
411
- }
412
568
}
413
569
414
570
return mergeCommit, nil
417
573
// Context is the CONTEXT command (paper §3.5).
418
574
// Retrieves history at K-commit resolution. Paper default: K=1.
419
575
func (w *Workspace) Context(branch *string, k int) (ContextResult, error) {
420
- target := w.currentBranch
421
- if branch != nil {
422
- target = *branch
576
+ if k < 1 {
577
+ return ContextResult{}, fmt.Errorf("k must be >= 1, got %d", k)
423
578
}
424
- if _, err := os.Stat(w.branchDir(target)); os.IsNotExist(err) {
425
- return ContextResult{}, fmt.Errorf("branch '%s' not found", target)
426
- }
427
-
428
- allCommits := w.parseCommits(target)
429
- start := len(allCommits) - k
430
- if start < 0 {
431
- start = 0
432
- }
433
- commits := allCommits[start:]
434
- otaRecords := w.parseOTA(target)
435
- mainRoadmap := w.read(w.mainMd())
436
- meta, _ := w.parseMeta(target)
437
-
438
- return ContextResult{
439
- BranchName: target,
440
- K: k,
441
- Commits: commits,
442
- OTARecords: otaRecords,
443
- MainRoadmap: mainRoadmap,
444
- Metadata: meta,
445
- }, nil
579
+ return withLockReturn(w, func() (ContextResult, error) {
580
+ target := w.currentBranch
581
+ if branch != nil {
582
+ target = *branch
583
+ }
584
+ if _, err := os.Stat(w.branchDir(target)); os.IsNotExist(err) {
585
+ return ContextResult{}, fmt.Errorf("branch '%s' not found", target)
586
+ }
587
+
588
+ allCommits := w.parseCommits(target)
589
+ start := len(allCommits) - k
590
+ if start < 0 {
591
+ start = 0
592
+ }
593
+ commits := allCommits[start:]
594
+ otaRecords := w.parseOTA(target)
595
+ mainRoadmap := w.read(w.mainMd())
596
+ meta, _ := w.parseMeta(target)
597
+
598
+ return ContextResult{
599
+ BranchName: target,
600
+ K: k,
601
+ Commits: commits,
602
+ OTARecords: otaRecords,
603
+ MainRoadmap: mainRoadmap,
604
+ Metadata: meta,
605
+ }, nil
606
+ })
446
607
}
447
608
448
609
// ------------------------------------------------------------------ //
456
617
457
618
// SwitchBranch changes the active branch without creating a new one.
458
619
func (w *Workspace) SwitchBranch(name string) error {
620
+ if err := validateBranchName(name); err != nil {
621
+ return err
622
+ }
459
623
if _, err := os.Stat(w.branchDir(name)); os.IsNotExist(err) {
460
624
return fmt.Errorf("branch '%s' does not exist", name)
461
625
}
462
626
▼GO/go.mod
+1-1
1
-module github.com/swadhinbiswas/contexa/GO
1
+module github.com/swadhinbiswas/Cortexa/GO
2
2
3
3
go 1.21
4
4
5
5