You've already forked agentic-coding-workflow
89 lines
2.5 KiB
Go
89 lines
2.5 KiB
Go
package projectmemory
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// kindHeadings maps each memory kind to its AGENTS.md section heading.
|
|
var kindHeadings = map[string]string{
|
|
KindDecision: "Decisions",
|
|
KindConvention: "Conventions",
|
|
KindFileMap: "File Map",
|
|
KindGotcha: "Gotchas",
|
|
}
|
|
|
|
// agentsMDHeader is the generated-file banner. AGENTS.md is written into the
|
|
// worktree before spawn; the banner marks it generated so it is not committed or
|
|
// picked up by auto-doc diffs.
|
|
const agentsMDHeader = "<!-- GENERATED by AgentCodingWorkflow project memory. Do not edit; changes are overwritten on spawn. -->"
|
|
|
|
// renderAgentsMD produces deterministic markdown grouped by kind. Within a kind,
|
|
// entries are sorted by title then id for stable output. When wsID is set,
|
|
// workspace-scoped entries plus project-wide (workspace_id NULL) entries are
|
|
// included; otherwise only project-wide entries.
|
|
func renderAgentsMD(entries []Entry, wsID *uuid.UUID) string {
|
|
byKind := map[string][]Entry{}
|
|
for _, e := range entries {
|
|
if !includeForWorkspace(e, wsID) {
|
|
continue
|
|
}
|
|
byKind[e.Kind] = append(byKind[e.Kind], e)
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString(agentsMDHeader)
|
|
b.WriteString("\n\n# AGENTS.md\n\n")
|
|
b.WriteString("Project knowledge curated by agents and maintainers. Treat as authoritative context.\n")
|
|
|
|
wrote := false
|
|
for _, kind := range validKinds {
|
|
items := byKind[kind]
|
|
if len(items) == 0 {
|
|
continue
|
|
}
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
if items[i].Title != items[j].Title {
|
|
return items[i].Title < items[j].Title
|
|
}
|
|
return items[i].ID.String() < items[j].ID.String()
|
|
})
|
|
b.WriteString("\n## ")
|
|
b.WriteString(kindHeadings[kind])
|
|
b.WriteString("\n\n")
|
|
for _, e := range items {
|
|
b.WriteString("### ")
|
|
b.WriteString(e.Title)
|
|
b.WriteString("\n")
|
|
if len(e.Tags) > 0 {
|
|
tags := append([]string(nil), e.Tags...)
|
|
sort.Strings(tags)
|
|
b.WriteString("Tags: ")
|
|
b.WriteString(strings.Join(tags, ", "))
|
|
b.WriteString("\n")
|
|
}
|
|
b.WriteString("\n")
|
|
b.WriteString(strings.TrimRight(e.Body, "\n"))
|
|
b.WriteString("\n")
|
|
}
|
|
wrote = true
|
|
}
|
|
if !wrote {
|
|
b.WriteString("\n(No project memory recorded yet.)\n")
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// includeForWorkspace decides whether an entry belongs in a worktree's AGENTS.md.
|
|
func includeForWorkspace(e Entry, wsID *uuid.UUID) bool {
|
|
if e.WorkspaceID == nil {
|
|
return true // project-wide
|
|
}
|
|
if wsID == nil {
|
|
return false
|
|
}
|
|
return *e.WorkspaceID == *wsID
|
|
}
|