diff --git a/README.md b/README.md index 7ca1d69..39e02a0 100644 --- a/README.md +++ b/README.md @@ -1411,3 +1411,6 @@ MIT License - 업데이트: 2026-04-07 00:45 (KST) - AX Copilot 메인 설정의 에이전트 메모리 영역에서 `관리형 / 사용자 / 프로젝트 / 로컬` 메모리 파일을 직접 열어 수정할 수 있게 했습니다. [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml), [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs)에 계층형 메모리 편집 버튼과 전용 편집 다이얼로그를 추가했습니다. - 메모리 편집 다이얼로그는 현재 테마를 따르는 안내 패널과 멀티라인 편집기를 제공하고, `description`/`paths` frontmatter 예시를 바로 볼 수 있습니다. 저장 시 해당 scope 파일을 즉시 갱신하고, 빈 내용으로 저장하면 파일을 삭제한 뒤 메모리 계층을 다시 로드합니다. +- 업데이트: 2026-04-07 00:52 (KST) + - 계층형 메모리 frontmatter를 더 확장해 `enabled:`와 `tags:`를 지원하도록 했습니다. 이제 실험용 규칙을 파일에 남겨둔 채 비활성화할 수 있고, 규칙 묶음을 태그 단위로 구분해 관리할 수 있습니다. + - [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)는 `enabled: false`인 규칙 파일을 메모리 계층에서 제외하고, [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs)의 `list/search` 결과에는 `tags` 메타를 함께 보여줘 어떤 규칙군인지 더 빠르게 읽을 수 있게 했습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 8adf983..7bd56a5 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5205,3 +5205,15 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - scope별 메모리 파일 경로를 계산해 실제 내용을 읽고 수정할 수 있는 `OpenMemoryScopeEditor(...)`를 추가했다. - 편집 다이얼로그는 현재 테마 색을 사용하고, `description`/`paths` frontmatter 예시를 안내로 보여준다. - 저장 시 해당 scope 메모리 파일을 즉시 갱신하고, 빈 내용이면 파일을 삭제한 뒤 `AgentMemoryService.Load(...)`를 다시 호출해 메모리 계층이 곧바로 반영되도록 했다. + +## 2026-04-07 00:52 (KST) + +- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs) + - 계층형 메모리 frontmatter에 `enabled:`와 `tags:` 메타를 추가했다. + - `enabled: false`인 규칙 파일은 로드 단계에서 제외해 실험용/보관용 규칙을 파일에 남겨둔 채 비활성화할 수 있게 했다. + - `tags:`는 `- payments`, `- review` 같은 목록 형식으로 읽어 `MemoryInstructionDocument`에 함께 보관한다. +- [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) + - `list`, `search` 출력에 계층형 메모리 문서의 `tags` 메타를 함께 표시하도록 정리했다. + - 사용자는 이제 `/memory` 결과에서 규칙의 설명, 적용 경로, 우선순위뿐 아니라 태그 묶음까지 같이 확인할 수 있다. +- [SettingsWindow.xaml.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml.cs) + - 메모리 편집 다이얼로그의 frontmatter 예시를 `description / enabled / tags / paths` 기준으로 갱신해, 설정 UI에서도 현재 지원 메타 전체를 바로 참고할 수 있게 했다. diff --git a/src/AxCopilot/Services/Agent/MemoryTool.cs b/src/AxCopilot/Services/Agent/MemoryTool.cs index 8798ada..74556d4 100644 --- a/src/AxCopilot/Services/Agent/MemoryTool.cs +++ b/src/AxCopilot/Services/Agent/MemoryTool.cs @@ -110,7 +110,8 @@ public class MemoryTool : IAgentTool var priority = doc.Priority > 0 ? $" (우선순위 {doc.Priority})" : ""; var suffix = string.IsNullOrWhiteSpace(doc.Description) ? "" : $" — {doc.Description}"; var scopeHint = doc.Paths.Count > 0 ? $" (paths: {string.Join(", ", doc.Paths)})" : ""; - sb.AppendLine($" [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}"); + var tags = doc.Tags.Count > 0 ? $" (tags: {string.Join(", ", doc.Tags)})" : ""; + sb.AppendLine($" [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}{tags}"); } sb.AppendLine(); } @@ -140,7 +141,8 @@ public class MemoryTool : IAgentTool var priority = doc.Priority > 0 ? $" (우선순위 {doc.Priority})" : ""; var suffix = string.IsNullOrWhiteSpace(doc.Description) ? "" : $" — {doc.Description}"; var scopeHint = doc.Paths.Count > 0 ? $" (paths: {string.Join(", ", doc.Paths)})" : ""; - sb.AppendLine($" • [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}"); + var tags = doc.Tags.Count > 0 ? $" (tags: {string.Join(", ", doc.Tags)})" : ""; + sb.AppendLine($" • [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}{tags}"); } sb.AppendLine(); } diff --git a/src/AxCopilot/Services/AgentMemoryService.cs b/src/AxCopilot/Services/AgentMemoryService.cs index 0ea6bec..38af009 100644 --- a/src/AxCopilot/Services/AgentMemoryService.cs +++ b/src/AxCopilot/Services/AgentMemoryService.cs @@ -407,6 +407,9 @@ public class AgentMemoryService return; var frontMatter = ParseFrontMatter(content); + if (!frontMatter.Enabled) + return; + if (frontMatter.Paths.Count > 0 && !ShouldApplyToCurrentWorkFolder(frontMatter.Paths, projectRoot, _currentWorkFolder)) return; @@ -418,6 +421,8 @@ public class AgentMemoryService Content = frontMatter.Content.Trim(), Paths = frontMatter.Paths, Description = frontMatter.Description, + Tags = frontMatter.Tags, + Enabled = frontMatter.Enabled, LoadOrder = _instructionDocuments.Count }); } @@ -695,11 +700,11 @@ public class AgentMemoryService } } - private static (string Content, List Paths, string Description) ParseFrontMatter(string content) + private static (string Content, List Paths, string Description, List Tags, bool Enabled) ParseFrontMatter(string content) { var lines = content.Replace("\r\n", "\n").Split('\n').ToList(); if (lines.Count < 3 || !string.Equals(lines[0].Trim(), "---", StringComparison.Ordinal)) - return (content, new List(), ""); + return (content, new List(), "", new List(), true); var endIndex = -1; for (var i = 1; i < lines.Count; i++) @@ -712,11 +717,14 @@ public class AgentMemoryService } if (endIndex < 1) - return (content, new List(), ""); + return (content, new List(), "", new List(), true); var paths = new List(); + var tags = new List(); var description = ""; + var enabled = true; var inPaths = false; + var inTags = false; for (var i = 1; i < endIndex; i++) { var line = lines[i].Trim(); @@ -724,11 +732,29 @@ public class AgentMemoryService { description = line["description:".Length..].Trim().Trim('"'); inPaths = false; + inTags = false; + continue; + } + if (line.StartsWith("enabled:", StringComparison.OrdinalIgnoreCase)) + { + var raw = line["enabled:".Length..].Trim().Trim('"'); + enabled = !string.Equals(raw, "false", StringComparison.OrdinalIgnoreCase) + && !string.Equals(raw, "0", StringComparison.OrdinalIgnoreCase) + && !string.Equals(raw, "off", StringComparison.OrdinalIgnoreCase); + inPaths = false; + inTags = false; continue; } if (line.StartsWith("paths:", StringComparison.OrdinalIgnoreCase)) { inPaths = true; + inTags = false; + continue; + } + if (line.StartsWith("tags:", StringComparison.OrdinalIgnoreCase)) + { + inTags = true; + inPaths = false; continue; } @@ -745,10 +771,24 @@ public class AgentMemoryService if (line.Length > 0) inPaths = false; } + + if (inTags) + { + if (line.StartsWith("-", StringComparison.Ordinal)) + { + var tag = line[1..].Trim().Trim('"'); + if (!string.IsNullOrWhiteSpace(tag)) + tags.Add(tag); + continue; + } + + if (line.Length > 0) + inTags = false; + } } var stripped = string.Join("\n", lines.Skip(endIndex + 1)).Trim(); - return (stripped, paths, description); + return (stripped, paths, description, tags, enabled); } private static bool ShouldApplyToCurrentWorkFolder(IReadOnlyList patterns, string? projectRoot, string? currentWorkFolder) @@ -861,6 +901,12 @@ public class MemoryInstructionDocument [JsonPropertyName("description")] public string Description { get; set; } = ""; + [JsonPropertyName("tags")] + public List Tags { get; set; } = new(); + + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } = true; + [JsonPropertyName("loadOrder")] public int LoadOrder { get; set; } diff --git a/src/AxCopilot/Views/SettingsWindow.xaml.cs b/src/AxCopilot/Views/SettingsWindow.xaml.cs index 3c0f254..d0461fe 100644 --- a/src/AxCopilot/Views/SettingsWindow.xaml.cs +++ b/src/AxCopilot/Views/SettingsWindow.xaml.cs @@ -3063,7 +3063,7 @@ public partial class SettingsWindow : Window Margin = new Thickness(0, 0, 0, 12), Child = new TextBlock { - Text = "frontmatter 예시:\n---\ndescription: 결제 모듈 규칙\npaths:\n - src/Payments/**\n---\n\n메모리 내용은 /memory 명령과 함께 사용되며, 빈 내용으로 저장하면 파일이 삭제됩니다.", + Text = "frontmatter 예시:\n---\ndescription: 결제 모듈 규칙\nenabled: true\ntags:\n - payments\n - review\npaths:\n - src/Payments/**\n---\n\n메모리 내용은 /memory 명령과 함께 사용되며, 빈 내용으로 저장하면 파일이 삭제됩니다.", FontSize = 12, Foreground = subFgBrush, TextWrapping = TextWrapping.Wrap,