mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
b3a7b7ad64
* feat: add agent self-evolution * fix ci * delete unused doc * fix lint * fix evolution review issues
155 lines
3.6 KiB
Go
155 lines
3.6 KiB
Go
package evolution
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type DraftPreview struct {
|
|
CurrentBody string
|
|
RenderedBody string
|
|
DiffPreview string
|
|
}
|
|
|
|
func BuildDraftPreview(workspace string, draft SkillDraft) (DraftPreview, error) {
|
|
currentBody, hadOriginal, err := loadCurrentSkillBody(workspace, draft.TargetSkillName)
|
|
if err != nil {
|
|
return DraftPreview{}, err
|
|
}
|
|
|
|
renderedBody, err := renderAppliedBody(draft, currentBody, hadOriginal)
|
|
if err != nil {
|
|
return DraftPreview{}, err
|
|
}
|
|
|
|
return DraftPreview{
|
|
CurrentBody: currentBody,
|
|
RenderedBody: renderedBody,
|
|
DiffPreview: buildLineDiffPreview(currentBody, renderedBody),
|
|
}, nil
|
|
}
|
|
|
|
func loadCurrentSkillBody(workspace, skillName string) (string, bool, error) {
|
|
skillPath := filepath.Join(workspace, "skills", skillName, "SKILL.md")
|
|
data, err := os.ReadFile(skillPath)
|
|
if os.IsNotExist(err) {
|
|
return "", false, nil
|
|
}
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
return string(data), true, nil
|
|
}
|
|
|
|
func buildLineDiffPreview(currentBody, renderedBody string) string {
|
|
before := strings.Split(strings.TrimRight(currentBody, "\n"), "\n")
|
|
after := strings.Split(strings.TrimRight(renderedBody, "\n"), "\n")
|
|
|
|
if len(before) == 1 && before[0] == "" {
|
|
before = nil
|
|
}
|
|
if len(after) == 1 && after[0] == "" {
|
|
after = nil
|
|
}
|
|
|
|
prefixLen := sharedPrefixLen(before, after)
|
|
suffixLen := sharedSuffixLen(before[prefixLen:], after[prefixLen:])
|
|
const contextRadius = 2
|
|
|
|
beforeChangeStart := prefixLen
|
|
beforeChangeEnd := len(before) - suffixLen
|
|
afterChangeStart := prefixLen
|
|
afterChangeEnd := len(after) - suffixLen
|
|
|
|
hunkBeforeStart := previewMaxInt(0, beforeChangeStart-contextRadius)
|
|
hunkAfterStart := previewMaxInt(0, afterChangeStart-contextRadius)
|
|
hunkBeforeEnd := previewMinInt(len(before), beforeChangeEnd+contextRadius)
|
|
hunkAfterEnd := previewMinInt(len(after), afterChangeEnd+contextRadius)
|
|
|
|
removed := before[prefixLen : len(before)-suffixLen]
|
|
added := after[prefixLen : len(after)-suffixLen]
|
|
if len(removed) == 0 && len(added) == 0 {
|
|
return "(no content change)"
|
|
}
|
|
|
|
lines := make([]string, 0, (hunkBeforeEnd-hunkBeforeStart)+(hunkAfterEnd-hunkAfterStart))
|
|
header := make([]string, 0, 3+len(lines))
|
|
header = append(header,
|
|
"--- current",
|
|
"+++ rendered",
|
|
formatUnifiedHunkHeader(
|
|
hunkBeforeStart,
|
|
hunkBeforeEnd-hunkBeforeStart,
|
|
hunkAfterStart,
|
|
hunkAfterEnd-hunkAfterStart,
|
|
),
|
|
)
|
|
for _, line := range before[hunkBeforeStart:beforeChangeStart] {
|
|
lines = append(lines, " "+line)
|
|
}
|
|
for _, line := range removed {
|
|
lines = append(lines, "-"+line)
|
|
}
|
|
for _, line := range added {
|
|
lines = append(lines, "+"+line)
|
|
}
|
|
for _, line := range after[afterChangeEnd:hunkAfterEnd] {
|
|
lines = append(lines, " "+line)
|
|
}
|
|
return strings.Join(append(header, lines...), "\n")
|
|
}
|
|
|
|
func formatUnifiedHunkHeader(beforeStart, beforeCount, afterStart, afterCount int) string {
|
|
return "@@ -" + formatUnifiedRange(
|
|
beforeStart+1,
|
|
beforeCount,
|
|
) + " +" + formatUnifiedRange(
|
|
afterStart+1,
|
|
afterCount,
|
|
) + " @@"
|
|
}
|
|
|
|
func formatUnifiedRange(start, count int) string {
|
|
return strconv.Itoa(start) + "," + strconv.Itoa(count)
|
|
}
|
|
|
|
func sharedPrefixLen(left, right []string) int {
|
|
limit := len(left)
|
|
if len(right) < limit {
|
|
limit = len(right)
|
|
}
|
|
n := 0
|
|
for n < limit && left[n] == right[n] {
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
func sharedSuffixLen(left, right []string) int {
|
|
limit := len(left)
|
|
if len(right) < limit {
|
|
limit = len(right)
|
|
}
|
|
n := 0
|
|
for n < limit && left[len(left)-1-n] == right[len(right)-1-n] {
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
func previewMinInt(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func previewMaxInt(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|