AX Agent 계층형 메모리 규칙 메타 확장 및 표시 보강
Some checks failed
Release Gate / gate (push) Has been cancelled

- AgentMemoryService frontmatter에 enabled와 tags 메타를 추가해 규칙 파일을 비활성 상태로 보관하거나 태그 단위로 묶어 관리할 수 있도록 확장
- enabled:false 규칙은 로드 단계에서 제외하도록 처리해 실험용 메모리 규칙을 안전하게 보관 가능하게 정리
- MemoryTool list/search 출력에 tags 메타를 함께 노출해 규칙 설명·적용 경로·우선순위와 함께 묶음 성격까지 바로 읽을 수 있도록 개선
- 설정의 메모리 편집 다이얼로그 frontmatter 예시를 description/enabled/tags/paths 기준으로 갱신해 현재 지원 메타를 UI에서도 바로 참고 가능하게 정리
- README 및 DEVELOPMENT 문서에 2026-04-07 00:52 (KST) 기준 작업 이력 반영

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\
- 경고 0 / 오류 0
This commit is contained in:
2026-04-07 00:34:32 +09:00
parent 4e1dcf082c
commit 0bfec6fb78
5 changed files with 70 additions and 7 deletions

View File

@@ -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` 메타를 함께 보여줘 어떤 규칙군인지 더 빠르게 읽을 수 있게 했습니다.

View File

@@ -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에서도 현재 지원 메타 전체를 바로 참고할 수 있게 했다.

View File

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

View File

@@ -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<string> Paths, string Description) ParseFrontMatter(string content)
private static (string Content, List<string> Paths, string Description, List<string> 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<string>(), "");
return (content, new List<string>(), "", new List<string>(), 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<string>(), "");
return (content, new List<string>(), "", new List<string>(), true);
var paths = new List<string>();
var tags = new List<string>();
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<string> patterns, string? projectRoot, string? currentWorkFolder)
@@ -861,6 +901,12 @@ public class MemoryInstructionDocument
[JsonPropertyName("description")]
public string Description { get; set; } = "";
[JsonPropertyName("tags")]
public List<string> Tags { get; set; } = new();
[JsonPropertyName("enabled")]
public bool Enabled { get; set; } = true;
[JsonPropertyName("loadOrder")]
public int LoadOrder { get; set; }

View File

@@ -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,