메모리 계층 우선순위와 중복 병합 정책 정리
Some checks failed
Release Gate / gate (push) Has been cancelled

계층형 메모리 문서 로드 후 동일한 규칙 내용은 더 가까운 계층만 남기고 중복을 제거하도록 NormalizeInstructionDocuments 로직을 추가했습니다.

최종 메모리 문서는 managed, user, project, local 순서로 다시 정렬되고 우선순위 번호를 부여하며, MemoryTool의 list와 search 결과에서 그 우선순위를 함께 보여주도록 했습니다.

README와 DEVELOPMENT 문서에 2026-04-07 00:39 (KST) 기준 이력을 반영했고 Release 빌드 경고 0 오류 0을 확인했습니다.
This commit is contained in:
2026-04-07 00:17:48 +09:00
parent 7093c77849
commit 917e61af20
4 changed files with 80 additions and 3 deletions

View File

@@ -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`는 이제 최종 우선순위 번호를 같이 보여줘, 어떤 규칙이 실제로 더 강하게 적용되는지 바로 확인할 수 있습니다.

View File

@@ -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` 결과에서 어떤 규칙이 실제로 더 강하게 적용되는지 바로 읽을 수 있다.

View File

@@ -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();
}

View File

@@ -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<string>(StringComparer.OrdinalIgnoreCase);
var selected = new List<MemoryInstructionDocument>();
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<string> 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; }
}