From 046589a6e0b2d77e9a8865382f466f81ef4832bc Mon Sep 17 00:00:00 2001 From: Dmitry <1319595+flowmitry@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:28:33 -0700 Subject: [PATCH 1/4] fix: support an unquotted string in the front matter --- .gitignore | 3 ++- internal/generator/copilot.go | 10 ++++------ internal/generator/cursor.go | 5 ++--- internal/generator/util.go | 18 +++++++++++++++++- internal/util/parser.go | 28 +++++++++++++++++++++++++++- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 9493657..37016d3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ bin .idea .DS_Store .aiignore -CLAUDE.md \ No newline at end of file +CLAUDE.md +syncai.exe \ No newline at end of file diff --git a/internal/generator/copilot.go b/internal/generator/copilot.go index 5904a2e..36acac3 100644 --- a/internal/generator/copilot.go +++ b/internal/generator/copilot.go @@ -2,7 +2,6 @@ package generator import ( "sort" - "strconv" "strings" "syncai/internal/model" ) @@ -12,15 +11,14 @@ type CopilotRulesGenerator struct{} func (g CopilotRulesGenerator) GenerateRules(metadata model.RulesMetadata, content []byte) []byte { var sb strings.Builder sb.WriteString("---\n") - // Copilot uses applyTo, but also include description for preservation sb.WriteString("description: ") - sb.WriteString(strconv.Quote(metadata.Description)) + sb.WriteString(quoteIfNeeded(metadata.Description)) sb.WriteString("\n") sb.WriteString("applyTo: ") if metadata.Globs == "" { - sb.WriteString(strconv.Quote("**")) + sb.WriteString(quoteIfNeeded("**")) } else { - sb.WriteString(strconv.Quote(metadata.Globs)) + sb.WriteString(quoteIfNeeded(metadata.Globs)) } sb.WriteString("\n") if len(metadata.ExtraFields) > 0 { @@ -35,7 +33,7 @@ func (g CopilotRulesGenerator) GenerateRules(metadata model.RulesMetadata, conte for _, k := range keys { sb.WriteString(k) sb.WriteString(": ") - sb.WriteString(strconv.Quote(metadata.ExtraFields[k])) + sb.WriteString(quoteIfNeeded(metadata.ExtraFields[k])) sb.WriteString("\n") } } diff --git a/internal/generator/cursor.go b/internal/generator/cursor.go index 59e4237..a113bd2 100644 --- a/internal/generator/cursor.go +++ b/internal/generator/cursor.go @@ -3,7 +3,6 @@ package generator import ( "fmt" "sort" - "strconv" "strings" "syncai/internal/model" ) @@ -14,7 +13,7 @@ func (g CursorRulesGenerator) GenerateRules(metadata model.RulesMetadata, conten var sb strings.Builder sb.WriteString("---\n") sb.WriteString("description: ") - sb.WriteString(strconv.Quote(metadata.Description)) + sb.WriteString(quoteIfNeeded(metadata.Description)) sb.WriteString("\n") sb.WriteString(fmt.Sprintf("alwaysApply: %t\n", metadata.IsAlwaysApply())) sb.WriteString("globs: ") @@ -33,7 +32,7 @@ func (g CursorRulesGenerator) GenerateRules(metadata model.RulesMetadata, conten for _, k := range keys { sb.WriteString(k) sb.WriteString(": ") - sb.WriteString(strconv.Quote(metadata.ExtraFields[k])) + sb.WriteString(quoteIfNeeded(metadata.ExtraFields[k])) sb.WriteString("\n") } } diff --git a/internal/generator/util.go b/internal/generator/util.go index b72c512..1f32afe 100644 --- a/internal/generator/util.go +++ b/internal/generator/util.go @@ -1,8 +1,24 @@ package generator -import "strings" +import ( + "strconv" + "strings" +) func isReservedField(field string) bool { fl := strings.ToLower(field) return fl == "description" || fl == "globs" || fl == "applyto" || fl == "alwaysapply" } + +func quoteIfNeeded(s string) string { + if s == "" { + return strconv.Quote(s) + } + if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") { + return s + } + if strings.ContainsAny(s, " \":{}[]#&*!|>'%@`") { + return strconv.Quote(s) + } + return s +} diff --git a/internal/util/parser.go b/internal/util/parser.go index 2bc1571..915a2a9 100644 --- a/internal/util/parser.go +++ b/internal/util/parser.go @@ -66,8 +66,9 @@ func ParseFile(path string) (model.Document, error) { } if foundEnd && yamlBuf.Len() > 0 { + raw := yamlBuf.Bytes() var m map[string]interface{} - if err := yaml.Unmarshal(yamlBuf.Bytes(), &m); err == nil { + if err := yaml.Unmarshal(raw, &m); err == nil { for k, v := range m { if v == nil { continue @@ -77,12 +78,37 @@ func ParseFile(path string) (model.Document, error) { if s, ok := cleanYAMLValue(vv); ok { metadata[k] = s } + case []interface{}: + // join sequence values into comma-separated metadata value + var parts []string + for _, item := range vv { + if s, ok := cleanYAMLValue(item); ok { + parts = append(parts, s) + } + } + if len(parts) > 0 { + metadata[k] = strings.Join(parts, ",") + } default: if s, ok := cleanYAMLValue(v); ok { metadata[k] = s } } } + } else { + for _, line := range strings.Split(string(raw), "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + metadata[key] = val + } } } else { // If we didn't find the end delimiter, reset body to full data From 8e103804444389061c20de7f11fd408a28523062 Mon Sep 17 00:00:00 2001 From: Dmitry <1319595+flowmitry@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:32:53 -0700 Subject: [PATCH 2/4] Update internal/generator/util.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/generator/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/generator/util.go b/internal/generator/util.go index 1f32afe..e62da8d 100644 --- a/internal/generator/util.go +++ b/internal/generator/util.go @@ -17,7 +17,7 @@ func quoteIfNeeded(s string) string { if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") { return s } - if strings.ContainsAny(s, " \":{}[]#&*!|>'%@`") { + if strings.ContainsAny(s, yamlSpecialChars) { return strconv.Quote(s) } return s From 028623458a3692b1b5d186d5d12e1d177df2353f Mon Sep 17 00:00:00 2001 From: Dmitry <1319595+flowmitry@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:33:19 -0700 Subject: [PATCH 3/4] Update internal/util/parser.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/util/parser.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/util/parser.go b/internal/util/parser.go index 915a2a9..1ed8335 100644 --- a/internal/util/parser.go +++ b/internal/util/parser.go @@ -106,7 +106,10 @@ func ParseFile(path string) (model.Document, error) { continue } key := strings.TrimSpace(parts[0]) - val := strings.TrimSpace(parts[1]) + key, val, ok := splitKeyValue(line) + if !ok { + continue + } metadata[key] = val } } From 02bf23a225e215dceaec25a5b798d7bd07d4f11d Mon Sep 17 00:00:00 2001 From: Dmitry <1319595+flowmitry@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:39:38 -0700 Subject: [PATCH 4/4] fix: streamline metadata parsing in ParseFile function --- internal/generator/util.go | 2 ++ internal/util/parser.go | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/generator/util.go b/internal/generator/util.go index e62da8d..d3d9495 100644 --- a/internal/generator/util.go +++ b/internal/generator/util.go @@ -5,6 +5,8 @@ import ( "strings" ) +const yamlSpecialChars = " \":{}[]#&*!|>'%@`" + func isReservedField(field string) bool { fl := strings.ToLower(field) return fl == "description" || fl == "globs" || fl == "applyto" || fl == "alwaysapply" diff --git a/internal/util/parser.go b/internal/util/parser.go index 1ed8335..915a2a9 100644 --- a/internal/util/parser.go +++ b/internal/util/parser.go @@ -106,10 +106,7 @@ func ParseFile(path string) (model.Document, error) { continue } key := strings.TrimSpace(parts[0]) - key, val, ok := splitKeyValue(line) - if !ok { - continue - } + val := strings.TrimSpace(parts[1]) metadata[key] = val } }