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
175 lines
6.1 KiB
Go
175 lines
6.1 KiB
Go
package evolution
|
||
|
||
import "strings"
|
||
|
||
func skillDraftPromptInstructions() []string {
|
||
return []string{
|
||
"body_or_patch must contain the complete draft body or patch content as plain text.",
|
||
"body_or_patch is an internal draft and review artifact, so it may include concise learning provenance, source task evidence, and source skill summaries when useful for human review.",
|
||
"If change_kind is create, body_or_patch must be a complete SKILL.md file with exactly two parts: YAML frontmatter and a Markdown body.",
|
||
"The YAML frontmatter must contain only name and description fields.",
|
||
"The description field must and only describe what this skill can do and when to use it.",
|
||
"The deployable Markdown body should only contain what the skill is useful for and how to use it.",
|
||
"The Markdown body is loaded only after the skill triggers, so focus on concise usage guidance and the execution steps needed to complete the task.",
|
||
"When describing an operation process in the body, do not use vague summaries; provide detailed step-by-step instructions for the exact operation or execution process.",
|
||
"When creating a combined shortcut skill, summarize the functional purpose and result of the provided SKILL.md inputs; do not copy or directly include other skills' instructions.",
|
||
"Extract only the necessary operations from source skills and evidence, such as formulas, ordered transformations, commands, inputs, outputs, and boundary conditions.",
|
||
"The operational part of the generated skill must be directly usable by a future agent without reading the original task records or source skills.",
|
||
"Keep operational instructions separable from audit/provenance notes because the final deployed SKILL.md will be rendered without learning traces.",
|
||
}
|
||
}
|
||
|
||
func skillDraftPromptText() string {
|
||
return strings.Join(skillDraftPromptInstructions(), "\n")
|
||
}
|
||
|
||
func learningTraceReplacer() *strings.Replacer {
|
||
return strings.NewReplacer(
|
||
"## Learned Shortcut Update", "## Shortcut Update",
|
||
"## Learned Evolution", "## Usage Notes",
|
||
"## Learned Pattern", "## Usage Pattern",
|
||
"## Learned Context", "## Procedure Notes",
|
||
"## Source Evidence", "## Validation",
|
||
"## Source Skills", "## Procedure Details",
|
||
"### Source Skills", "### Procedure Details",
|
||
"## Learned Shortcut", "## Shortcut",
|
||
"### Learned Shortcut", "### Shortcut",
|
||
"Learned workflow for ", "Workflow for ",
|
||
"learned workflow for ", "workflow for ",
|
||
"from learned pattern: ", "for: ",
|
||
"Learned task:", "Task:",
|
||
"learned task:", "task:",
|
||
"Learned pattern:", "Pattern:",
|
||
"learned pattern:", "pattern:",
|
||
"Learned from", "Based on",
|
||
"learned from", "based on",
|
||
"Source evidence", "Validation",
|
||
"source evidence", "validation",
|
||
"task records", "validated examples",
|
||
"Task records", "Validated examples",
|
||
)
|
||
}
|
||
|
||
func renderDeployableSkillBody(body string) string {
|
||
body = strings.TrimSpace(body)
|
||
if body == "" {
|
||
return body
|
||
}
|
||
frontmatter, markdownBody := splitSkillFrontmatter(body)
|
||
if frontmatter != "" {
|
||
body = "---\n" + frontmatter + "\n---\n" + learningTraceReplacer().Replace(strings.TrimLeft(markdownBody, "\n"))
|
||
} else {
|
||
body = learningTraceReplacer().Replace(body)
|
||
}
|
||
body = normalizeDeployableDescription(body)
|
||
return removeDeployOnlyProvenanceLines(body)
|
||
}
|
||
|
||
func normalizeDeployableDescription(body string) string {
|
||
lines := strings.Split(body, "\n")
|
||
inFrontmatter := false
|
||
for i, line := range lines {
|
||
trimmed := strings.TrimSpace(line)
|
||
if i == 0 && trimmed == "---" {
|
||
inFrontmatter = true
|
||
continue
|
||
}
|
||
if inFrontmatter && trimmed == "---" {
|
||
break
|
||
}
|
||
if !inFrontmatter || !strings.HasPrefix(trimmed, "description:") {
|
||
continue
|
||
}
|
||
value := strings.TrimSpace(strings.TrimPrefix(trimmed, "description:"))
|
||
value = cleanDeployableDescription(value)
|
||
lines[i] = "description: " + value
|
||
break
|
||
}
|
||
return strings.Join(lines, "\n")
|
||
}
|
||
|
||
func cleanDeployableDescription(description string) string {
|
||
description = strings.TrimSpace(strings.Trim(description, `"'`))
|
||
for _, marker := range []string{
|
||
" for: ",
|
||
" from learned pattern: ",
|
||
" for learned pattern: ",
|
||
} {
|
||
if idx := strings.Index(strings.ToLower(description), marker); idx >= 0 {
|
||
description = strings.TrimSpace(description[idx+len(marker):])
|
||
break
|
||
}
|
||
}
|
||
description = strings.TrimPrefix(description, "Create combined shortcut ")
|
||
description = strings.TrimPrefix(description, "Refresh combined shortcut ")
|
||
description = strings.TrimPrefix(description, "Create shortcut ")
|
||
description = strings.TrimPrefix(description, "Refresh shortcut ")
|
||
description = strings.TrimSpace(description)
|
||
if description == "" {
|
||
return "Use this skill when the task matches its documented workflow."
|
||
}
|
||
return description
|
||
}
|
||
|
||
func sentenceFragment(text string) string {
|
||
text = strings.TrimSpace(text)
|
||
if text == "" {
|
||
return "complete the documented workflow"
|
||
}
|
||
runes := []rune(text)
|
||
if len(runes) > 0 && runes[0] >= 'A' && runes[0] <= 'Z' {
|
||
runes[0] = runes[0] + ('a' - 'A')
|
||
}
|
||
return string(runes)
|
||
}
|
||
|
||
func trimAtReadableBoundary(content string, maxLen int) string {
|
||
content = strings.TrimSpace(content)
|
||
runes := []rune(content)
|
||
if content == "" || maxLen <= 0 || len(runes) <= maxLen {
|
||
return content
|
||
}
|
||
|
||
cut := maxLen
|
||
searchStart := maxLen - minInt(maxLen/2, 240)
|
||
if searchStart < 0 {
|
||
searchStart = 0
|
||
}
|
||
for i := maxLen; i >= searchStart; i-- {
|
||
switch runes[i-1] {
|
||
case '\n', '.', '!', '?', ';', ':', '。', '!', '?', ';', ':':
|
||
cut = i
|
||
goto done
|
||
}
|
||
}
|
||
for i := maxLen; i >= searchStart; i-- {
|
||
if runes[i-1] == ' ' || runes[i-1] == '\t' {
|
||
cut = i
|
||
goto done
|
||
}
|
||
}
|
||
|
||
done:
|
||
return strings.TrimRight(strings.TrimSpace(string(runes[:cut])), ".,;:,。;:") + "..."
|
||
}
|
||
|
||
func removeDeployOnlyProvenanceLines(body string) string {
|
||
lines := strings.Split(body, "\n")
|
||
out := make([]string, 0, len(lines))
|
||
for _, line := range lines {
|
||
trimmed := strings.TrimSpace(line)
|
||
lower := strings.ToLower(trimmed)
|
||
if strings.HasPrefix(lower, "- evidence:") {
|
||
continue
|
||
}
|
||
if strings.HasPrefix(lower, "- validated examples:") {
|
||
continue
|
||
}
|
||
if strings.Contains(lower, "source_record_id") || strings.Contains(lower, "source record") {
|
||
continue
|
||
}
|
||
out = append(out, line)
|
||
}
|
||
return strings.TrimSpace(strings.Join(out, "\n"))
|
||
}
|