go: implement GCCWorkspace with full operation set

Swadhin Biswas committed 7 months ago
6011ef8
1 parent e26b07b
commit 6011ef8
1 file changed
+480 -0
Showing 1 changed file+480-0
▼GO/cortexa/workspace.go
+480-0
1
+package cortexa
2
+
3
+import (
4
+ "crypto/rand"
5
+ "encoding/hex"
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+ "time"
11
+
12
+ "gopkg.in/yaml.v3"
13
+)
14
+
15
+const (
16
+ mainBranch = "main"
17
+ gccDir = ".GCC"
18
+)
19
+
20
+func nowISO() string {
21
+ return time.Now().UTC().Format(time.RFC3339)
22
+}
23
+
24
+func shortID() string {
25
+ b := make([]byte, 4)
26
+ rand.Read(b)
27
+ return hex.EncodeToString(b)
28
+}
29
+
30
+// Workspace manages the .GCC/ directory for one agent project.
31
+//
32
+// Implements the four GCC commands (arXiv:2508.00031v2):
33
+//
34
+// COMMIT (§3.2) — milestone checkpointing to commit.md
35
+// BRANCH (§3.3) — isolated reasoning workspace
36
+// MERGE (§3.4) — synthesise divergent paths
37
+// CONTEXT (§3.5) — hierarchical memory retrieval (K-commit window)
38
+//
39
+// File system layout:
40
+//
41
+// .GCC/
42
+// ├── main.md # Global roadmap / planning artifact
43
+// └── branches/
44
+// ├── main/
45
+// │ ├── log.md # Continuous OTA trace
46
+// │ ├── commit.md # Milestone-level summaries
47
+// │ └── metadata.yaml # Branch intent & status
48
+// └── <branch>/
49
+// └── ...
50
+type Workspace struct {
51
+ root string
52
+ gccPath string
53
+ currentBranch string
54
+}
55
+
56
+// New returns a Workspace rooted at projectRoot.
57
+func New(projectRoot string) *Workspace {
58
+ return &Workspace{
59
+ root: projectRoot,
60
+ gccPath: filepath.Join(projectRoot, gccDir),
61
+ currentBranch: mainBranch,
62
+ }
63
+}
64
+
65
+// ------------------------------------------------------------------ //
66
+// Paths //
67
+// ------------------------------------------------------------------ //
68
+
69
+func (w *Workspace) branchDir(branch string) string {
70
+ return filepath.Join(w.gccPath, "branches", branch)
71
+}
72
+func (w *Workspace) logPath(branch string) string {
73
+ return filepath.Join(w.branchDir(branch), "log.md")
74
+}
75
+func (w *Workspace) commitPath(branch string) string {
76
+ return filepath.Join(w.branchDir(branch), "commit.md")
77
+}
78
+func (w *Workspace) metaPath(branch string) string {
79
+ return filepath.Join(w.branchDir(branch), "metadata.yaml")
80
+}
81
+func (w *Workspace) mainMd() string {
82
+ return filepath.Join(w.gccPath, "main.md")
83
+}
84
+
85
+// ------------------------------------------------------------------ //
86
+// I/O helpers //
87
+// ------------------------------------------------------------------ //
88
+
89
+func (w *Workspace) read(path string) string {
90
+ data, err := os.ReadFile(path)
91
+ if err != nil {
92
+ return ""
93
+ }
94
+ return string(data)
95
+}
96
+
97
+func (w *Workspace) write(path, content string) error {
98
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
99
+ return err
100
+ }
101
+ return os.WriteFile(path, []byte(content), 0o644)
102
+}
103
+
104
+func (w *Workspace) appendFile(path, content string) error {
105
+ f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
106
+ if err != nil {
107
+ return err
108
+ }
109
+ defer f.Close()
110
+ _, err = f.WriteString(content)
111
+ return err
112
+}
113
+
114
+func (w *Workspace) parseCommits(branch string) []CommitRecord {
115
+ text := w.read(w.commitPath(branch))
116
+ var records []CommitRecord
117
+ for _, block := range strings.Split(text, "---\n") {
118
+ block = strings.TrimSpace(block)
119
+ if block == "" {
120
+ continue
121
+ }
122
+ var (
123
+ commitID, branchPurpose, prevSummary, contribution, ts string
124
+ )
125
+ for _, line := range strings.Split(block, "\n") {
126
+ switch {
127
+ case strings.HasPrefix(line, "## Commit `"):
128
+ commitID = strings.TrimSuffix(strings.TrimPrefix(line, "## Commit `"), "`")
129
+ case strings.HasPrefix(line, "**Timestamp:**"):
130
+ ts = strings.TrimSpace(strings.TrimPrefix(line, "**Timestamp:**"))
131
+ case strings.HasPrefix(line, "**Branch Purpose:**"):
132
+ branchPurpose = strings.TrimSpace(strings.TrimPrefix(line, "**Branch Purpose:**"))
133
+ case strings.HasPrefix(line, "**Previous Progress Summary:**"):
134
+ prevSummary = strings.TrimSpace(strings.TrimPrefix(line, "**Previous Progress Summary:**"))
135
+ case strings.HasPrefix(line, "**This Commit's Contribution:**"):
136
+ contribution = strings.TrimSpace(strings.TrimPrefix(line, "**This Commit's Contribution:**"))
137
+ }
138
+ }
139
+ if commitID != "" {
140
+ records = append(records, CommitRecord{
141
+ CommitID: commitID,
142
+ BranchName: branch,
143
+ BranchPurpose: branchPurpose,
144
+ PreviousProgressSummary: prevSummary,
145
+ ThisCommitContribution: contribution,
146
+ Timestamp: ts,
147
+ })
148
+ }
149
+ }
150
+ return records
151
+}
152
+
153
+func (w *Workspace) parseOTA(branch string) []OTARecord {
154
+ text := w.read(w.logPath(branch))
155
+ var records []OTARecord
156
+ for _, block := range strings.Split(text, "---\n") {
157
+ block = strings.TrimSpace(block)
158
+ if block == "" {
159
+ continue
160
+ }
161
+ var step int
162
+ var ts, obs, thought, action string
163
+ for _, line := range strings.Split(block, "\n") {
164
+ switch {
165
+ case strings.HasPrefix(line, "### Step "):
166
+ parts := strings.SplitN(line, " — ", 2)
167
+ fmt.Sscanf(parts[0], "### Step %d", &step)
168
+ if len(parts) > 1 {
169
+ ts = strings.TrimSpace(parts[1])
170
+ }
171
+ case strings.HasPrefix(line, "**Observation:**"):
172
+ obs = strings.TrimSpace(strings.TrimPrefix(line, "**Observation:**"))
173
+ case strings.HasPrefix(line, "**Thought:**"):
174
+ thought = strings.TrimSpace(strings.TrimPrefix(line, "**Thought:**"))
175
+ case strings.HasPrefix(line, "**Action:**"):
176
+ action = strings.TrimSpace(strings.TrimPrefix(line, "**Action:**"))
177
+ }
178
+ }
179
+ if obs != "" || thought != "" {
180
+ records = append(records, OTARecord{
181
+ Step: step,
182
+ Timestamp: ts,
183
+ Observation: obs,
184
+ Thought: thought,
185
+ Action: action,
186
+ })
187
+ }
188
+ }
189
+ return records
190
+}
191
+
192
+func (w *Workspace) parseMeta(branch string) (*BranchMetadata, error) {
193
+ text := w.read(w.metaPath(branch))
194
+ if text == "" {
195
+ return nil, nil
196
+ }
197
+ var meta BranchMetadata
198
+ if err := yaml.Unmarshal([]byte(text), &meta); err != nil {
199
+ return nil, err
200
+ }
201
+ return &meta, nil
202
+}
203
+
204
+// ------------------------------------------------------------------ //
205
+// Initialisation //
206
+// ------------------------------------------------------------------ //
207
+
208
+// Init initialises a new GCC workspace.
209
+func (w *Workspace) Init(projectRoadmap string) error {
210
+ if _, err := os.Stat(w.gccPath); err == nil {
211
+ return fmt.Errorf("GCC workspace already exists at %s", w.gccPath)
212
+ }
213
+ if err := os.MkdirAll(w.branchDir(mainBranch), 0o755); err != nil {
214
+ return err
215
+ }
216
+
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
+ }
227
+
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
+ }
242
+
243
+ w.currentBranch = mainBranch
244
+ return nil
245
+}
246
+
247
+// Load opens an existing GCC workspace.
248
+func (w *Workspace) Load() error {
249
+ if _, err := os.Stat(w.gccPath); os.IsNotExist(err) {
250
+ return fmt.Errorf("no GCC workspace found at %s", w.gccPath)
251
+ }
252
+ w.currentBranch = mainBranch
253
+ return nil
254
+}
255
+
256
+// ------------------------------------------------------------------ //
257
+// GCC Commands //
258
+// ------------------------------------------------------------------ //
259
+
260
+// LogOTA appends an Observation–Thought–Action step to the current branch's log.md.
261
+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
274
+}
275
+
276
+// Commit is the COMMIT command (paper §3.2).
277
+// Checkpoints milestone with: Branch Purpose, Previous Progress Summary,
278
+// This Commit's Contribution.
279
+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
284
+ }
285
+
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
293
+ }
294
+ }
295
+
296
+ record := CommitRecord{
297
+ CommitID: shortID(),
298
+ BranchName: w.currentBranch,
299
+ BranchPurpose: branchPurpose,
300
+ PreviousProgressSummary: prevSummary,
301
+ ThisCommitContribution: contribution,
302
+ Timestamp: nowISO(),
303
+ }
304
+
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 {
311
+ return CommitRecord{}, err
312
+ }
313
+ }
314
+ return record, nil
315
+}
316
+
317
+// Branch is the BRANCH command (paper §3.3).
318
+// Creates isolated workspace: B_t^(name) = BRANCH(M_{t-1}).
319
+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 {
327
+ return err
328
+ }
329
+ if err := w.write(w.commitPath(name), fmt.Sprintf("# Commit History — branch `%s`\n\n", name)); err != nil {
330
+ return err
331
+ }
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
342
+ }
343
+ if err := w.write(w.metaPath(name), string(data)); err != nil {
344
+ return err
345
+ }
346
+ w.currentBranch = name
347
+ return nil
348
+}
349
+
350
+// Merge is the MERGE command (paper §3.4).
351
+// Integrates a completed branch into target, merging summaries and OTA traces.
352
+func (w *Workspace) Merge(branchName string, summary *string, target string) (CommitRecord, error) {
353
+ if _, err := os.Stat(w.branchDir(branchName)); os.IsNotExist(err) {
354
+ return CommitRecord{}, fmt.Errorf("branch '%s' not found", branchName)
355
+ }
356
+ if _, err := os.Stat(w.branchDir(target)); os.IsNotExist(err) {
357
+ return CommitRecord{}, fmt.Errorf("target branch '%s' not found", target)
358
+ }
359
+
360
+ branchCommits := w.parseCommits(branchName)
361
+ branchOTA := w.parseOTA(branchName)
362
+ meta, _ := w.parseMeta(branchName)
363
+
364
+ 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
371
+ }
372
+ mergeSummary = fmt.Sprintf("Merged branch `%s` (%d commits). Contributions: %s",
373
+ branchName, len(branchCommits), strings.Join(contribs, " | "))
374
+ }
375
+
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
384
+ }
385
+ }
386
+ }
387
+
388
+ w.currentBranch = target
389
+ metaPurpose := ""
390
+ if meta != nil {
391
+ metaPurpose = meta.Purpose
392
+ }
393
+ prevSummary := fmt.Sprintf("Merging branch `%s` with purpose: %s", branchName, metaPurpose)
394
+ mergeCommit, err := w.Commit(mergeSummary, &prevSummary, &mergeSummary)
395
+ if err != nil {
396
+ return CommitRecord{}, err
397
+ }
398
+
399
+ 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)
406
+ if err != nil {
407
+ return CommitRecord{}, err
408
+ }
409
+ if err := w.write(w.metaPath(branchName), string(data)); err != nil {
410
+ return CommitRecord{}, err
411
+ }
412
+ }
413
+
414
+ return mergeCommit, nil
415
+}
416
+
417
+// Context is the CONTEXT command (paper §3.5).
418
+// Retrieves history at K-commit resolution. Paper default: K=1.
419
+func (w *Workspace) Context(branch *string, k int) (ContextResult, error) {
420
+ target := w.currentBranch
421
+ if branch != nil {
422
+ target = *branch
423
+ }
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
446
+}
447
+
448
+// ------------------------------------------------------------------ //
449
+// Helpers //
450
+// ------------------------------------------------------------------ //
451
+
452
+// CurrentBranch returns the active branch name.
453
+func (w *Workspace) CurrentBranch() string {
454
+ return w.currentBranch
455
+}
456
+
457
+// SwitchBranch changes the active branch without creating a new one.
458
+func (w *Workspace) SwitchBranch(name string) error {
459
+ if _, err := os.Stat(w.branchDir(name)); os.IsNotExist(err) {
460
+ return fmt.Errorf("branch '%s' does not exist", name)
461
+ }
462
+ w.currentBranch = name
463
+ return nil
464
+}
465
+
466
+// ListBranches returns all branch names in the workspace.
467
+func (w *Workspace) ListBranches() ([]string, error) {
468
+ branchesRoot := filepath.Join(w.gccPath, "branches")
469
+ entries, err := os.ReadDir(branchesRoot)
470
+ if err != nil {
471
+ return nil, err
472
+ }
473
+ var branches []string
474
+ for _, e := range entries {
475
+ if e.IsDir() {
476
+ branches = append(branches, e.Name())
477
+ }
478
+ }
479
+ return branches, nil
480
+}
0
481