From 917e61af20d81642e1432c98405f25a6c0517eaf Mon Sep 17 00:00:00 2001 From: lacvet Date: Tue, 7 Apr 2026 00:17:48 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EB=AA=A8=EB=A6=AC=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=20=EC=9A=B0=EC=84=A0=EC=88=9C=EC=9C=84=EC=99=80=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EB=B3=91=ED=95=A9=20=EC=A0=95=EC=B1=85=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 계층형 메모리 문서 로드 후 동일한 규칙 내용은 더 가까운 계층만 남기고 중복을 제거하도록 NormalizeInstructionDocuments 로직을 추가했습니다. 최종 메모리 문서는 managed, user, project, local 순서로 다시 정렬되고 우선순위 번호를 부여하며, MemoryTool의 list와 search 결과에서 그 우선순위를 함께 보여주도록 했습니다. README와 DEVELOPMENT 문서에 2026-04-07 00:39 (KST) 기준 이력을 반영했고 Release 빌드 경고 0 오류 0을 확인했습니다. --- README.md | 3 + docs/DEVELOPMENT.md | 10 +++ src/AxCopilot/Services/Agent/MemoryTool.cs | 6 +- src/AxCopilot/Services/AgentMemoryService.cs | 64 +++++++++++++++++++- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2c33e2..eb1d045 100644 --- a/README.md +++ b/README.md @@ -1405,3 +1405,6 @@ MIT License - 업데이트: 2026-04-07 00:31 (KST) - [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)에 `description:` frontmatter 메타를 추가해 계층형 메모리 규칙 파일이 “무엇을 위한 규칙인지” 설명을 가질 수 있게 했습니다. - [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) 는 `show_scope` 액션을 새로 지원합니다. 이제 `/memory` 계열 명령으로 `managed / user / project / local` 메모리 파일의 실제 내용을 직접 확인할 수 있고, `list/search` 결과에도 `description`과 `paths` 범위가 함께 표시됩니다. +- 업데이트: 2026-04-07 00:39 (KST) + - [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)에 계층형 메모리 우선순위/병합 정책을 추가했습니다. 같은 내용의 규칙이 여러 계층에 중복될 경우 더 가까운 규칙만 남기고, 최종 메모리 문서는 `managed → user → project → local` 순으로 다시 정렬됩니다. + - [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) 의 `list/search`는 이제 최종 우선순위 번호를 같이 보여줘, 어떤 규칙이 실제로 더 강하게 적용되는지 바로 확인할 수 있습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index af45007..92c393e 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5185,3 +5185,13 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) - `show_scope` 액션을 추가해 `managed / user / project / local` 메모리 파일의 실제 경로와 본문을 바로 확인할 수 있게 했다. - `list`, `search` 출력에는 계층형 메모리 문서의 `description`과 `paths` 메타를 함께 노출해, `claw-code`의 rule 파일처럼 어떤 규칙이 어느 범위에 적용되는지 빠르게 읽을 수 있도록 정리했다. + +## 2026-04-07 00:39 (KST) + +- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs) + - 계층형 메모리 로드 뒤 `NormalizeInstructionDocuments()`를 수행하도록 바꿨다. + - 동일한 규칙 내용이 여러 계층에 중복되면 더 나중에 로드된 문서, 즉 현재 작업 위치에 더 가까운 문서를 남기고 앞선 중복 문서는 제거한다. + - 최종 문서는 `managed → user → project → local` 순서로 다시 정렬하고, 각 문서에 최종 적용 우선순위 번호를 부여한다. +- [MemoryTool.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/MemoryTool.cs) + - `list`, `search` 출력에 계층형 메모리 문서의 우선순위를 함께 노출하도록 정리했다. + - 사용자는 이제 `/memory` 결과에서 어떤 규칙이 실제로 더 강하게 적용되는지 바로 읽을 수 있다. diff --git a/src/AxCopilot/Services/Agent/MemoryTool.cs b/src/AxCopilot/Services/Agent/MemoryTool.cs index 66d8d1b..8798ada 100644 --- a/src/AxCopilot/Services/Agent/MemoryTool.cs +++ b/src/AxCopilot/Services/Agent/MemoryTool.cs @@ -107,9 +107,10 @@ public class MemoryTool : IAgentTool sb.AppendLine($"계층형 메모리 {docs.Count}개:"); foreach (var doc in docs) { + 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}{suffix}{scopeHint}"); + sb.AppendLine($" [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}"); } sb.AppendLine(); } @@ -136,9 +137,10 @@ public class MemoryTool : IAgentTool sb.AppendLine($"계층형 메모리 파일 {docs.Count}개:"); foreach (var doc in docs) { + 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}{suffix}{scopeHint}"); + sb.AppendLine($" • [{doc.Label}] {doc.Path}{priority}{suffix}{scopeHint}"); } sb.AppendLine(); } diff --git a/src/AxCopilot/Services/AgentMemoryService.cs b/src/AxCopilot/Services/AgentMemoryService.cs index dcb8c2f..0ea6bec 100644 --- a/src/AxCopilot/Services/AgentMemoryService.cs +++ b/src/AxCopilot/Services/AgentMemoryService.cs @@ -146,6 +146,7 @@ public class AgentMemoryService } LoadInstructionDocuments(workFolder); + NormalizeInstructionDocuments(); } } @@ -416,7 +417,8 @@ public class AgentMemoryService Path = fullPath, Content = frontMatter.Content.Trim(), Paths = frontMatter.Paths, - Description = frontMatter.Description + Description = frontMatter.Description, + LoadOrder = _instructionDocuments.Count }); } catch (Exception ex) @@ -425,6 +427,60 @@ public class AgentMemoryService } } + private void NormalizeInstructionDocuments() + { + if (_instructionDocuments.Count <= 1) + return; + + var seenNormalized = new HashSet(StringComparer.OrdinalIgnoreCase); + var selected = new List(); + + foreach (var doc in _instructionDocuments.OrderByDescending(d => d.LoadOrder)) + { + var normalized = NormalizeInstructionContent(doc.Content); + if (!string.IsNullOrWhiteSpace(normalized) && !seenNormalized.Add(normalized)) + continue; + + selected.Add(doc); + } + + selected = selected + .OrderBy(d => GetLayerRank(d.Layer)) + .ThenBy(d => d.LoadOrder) + .ToList(); + + for (var i = 0; i < selected.Count; i++) + { + selected[i].Priority = i + 1; + } + + _instructionDocuments.Clear(); + _instructionDocuments.AddRange(selected); + } + + private static string NormalizeInstructionContent(string content) + { + if (string.IsNullOrWhiteSpace(content)) + return ""; + + var normalizedLines = content + .Replace("\r\n", "\n") + .Split('\n') + .Select(line => string.Join(" ", line.Split(' ', '\t').Where(part => part.Length > 0)).Trim()) + .Where(line => line.Length > 0); + + return string.Join("\n", normalizedLines).Trim(); + } + + private static int GetLayerRank(string layer) => layer switch + { + "managed" => 0, + "user" => 1, + "project" => 2, + "local" => 3, + _ => 9 + }; + private static IEnumerable EnumerateDirectoryChain(string projectRoot, string workFolder) { var current = Path.GetFullPath(workFolder); @@ -804,4 +860,10 @@ public class MemoryInstructionDocument [JsonPropertyName("description")] public string Description { get; set; } = ""; + + [JsonPropertyName("loadOrder")] + public int LoadOrder { get; set; } + + [JsonPropertyName("priority")] + public int Priority { get; set; } }