메모리 규칙 설명 메타와 슬래시 조회 기능 확장
Some checks failed
Release Gate / gate (push) Has been cancelled

계층형 메모리 frontmatter에 description 메타를 추가해 rule 파일의 의도와 적용 범위를 /memory 결과에서 함께 읽을 수 있도록 정리했습니다.

MemoryTool에 show_scope 액션을 추가해 managed, user, project, local 메모리 파일의 실제 내용을 슬래시 명령으로 직접 확인할 수 있게 했습니다.

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

View File

@@ -1402,3 +1402,6 @@ MIT License
- 업데이트: 2026-04-07 00:22 (KST)
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)에 `paths:` frontmatter 지원을 추가해 `.ax/rules/*.md` 같은 계층형 메모리 문서가 특정 작업 폴더 범위에서만 적용되도록 했습니다.
- 이제 메모리 문서 상단에 `---`, `paths:`, `- src/**`, `---` 형태를 쓰면 현재 작업 폴더가 프로젝트 루트 기준으로 그 패턴에 맞을 때만 로드됩니다. AX 메모리 규칙을 `claw-code`의 경로별 rule 파일처럼 더 세밀하게 제어할 수 있습니다.
- 업데이트: 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` 범위가 함께 표시됩니다.

View File

@@ -5175,3 +5175,13 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)
- 간단한 glob 매처를 추가해 `*`, `**`, `?` 패턴을 지원한다.
- 현재는 `.ax/rules/*.md` 같은 프로젝트 규칙 파일을 `claw-code`의 경로 범위 rules와 비슷하게 운영할 수 있는 수준까지 올라온 상태다.
## 2026-04-07 00:31 (KST)
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)
- 계층형 메모리 frontmatter에 `description:` 메타를 추가했다.
- `paths:` 범위 판정 뒤 실제 주입 문서에는 설명과 경로 범위를 함께 보관해, `/memory list``/memory search`에서 규칙 파일의 의도를 더 잘 읽을 수 있게 했다.
- scope별 원본 메모리 파일을 직접 읽을 수 있는 `ReadInstructionFile(...)`도 추가했다.
- [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 파일처럼 어떤 규칙이 어느 범위에 적용되는지 빠르게 읽을 수 있도록 정리했다.

View File

@@ -16,6 +16,7 @@ public class MemoryTool : IAgentTool
"대화 간 지속되는 메모리로, 새 대화에서도 이전에 학습한 내용을 활용할 수 있습니다.\n" +
"- action=\"save\": 학습 메모리 저장 (type, content 필수)\n" +
"- action=\"save_scope\": 계층형 메모리 파일에 저장 (scope, content 필수)\n" +
"- action=\"show_scope\": 계층형 메모리 파일 본문 조회 (scope 필수)\n" +
"- action=\"search\": 관련 메모리 검색 (query 필수)\n" +
"- action=\"list\": 현재 메모리 전체 목록 + 계층형 메모리 파일 목록\n" +
"- action=\"delete\": 학습 메모리 삭제 (id 필수)\n" +
@@ -27,7 +28,7 @@ public class MemoryTool : IAgentTool
{
Properties = new()
{
["action"] = new() { Type = "string", Description = "save | save_scope | search | list | delete | delete_scope" },
["action"] = new() { Type = "string", Description = "save | save_scope | show_scope | search | list | delete | delete_scope" },
["type"] = new() { Type = "string", Description = "메모리 유형: rule | preference | fact | correction. save 시 필수." },
["scope"] = new() { Type = "string", Description = "계층형 메모리 대상: managed | user | project | local. save_scope/delete_scope 시 필수." },
["content"] = new() { Type = "string", Description = "저장할 내용. save 시 필수." },
@@ -58,11 +59,12 @@ public class MemoryTool : IAgentTool
{
"save" => ExecuteSave(args, memoryService, context),
"save_scope" => ExecuteSaveScope(args, memoryService, context),
"show_scope" => ExecuteShowScope(args, memoryService, context),
"search" => ExecuteSearch(args, memoryService),
"list" => ExecuteList(memoryService),
"delete" => ExecuteDelete(args, memoryService),
"delete_scope" => ExecuteDeleteScope(args, memoryService, context),
_ => ToolResult.Fail($"알 수 없는 액션: {action}. save | save_scope | search | list | delete | delete_scope 중 선택하세요."),
_ => ToolResult.Fail($"알 수 없는 액션: {action}. save | save_scope | show_scope | search | list | delete | delete_scope 중 선택하세요."),
});
}
@@ -104,7 +106,11 @@ public class MemoryTool : IAgentTool
{
sb.AppendLine($"계층형 메모리 {docs.Count}개:");
foreach (var doc in docs)
sb.AppendLine($" [{doc.Label}] {doc.Path}");
{
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();
}
@@ -129,7 +135,11 @@ public class MemoryTool : IAgentTool
{
sb.AppendLine($"계층형 메모리 파일 {docs.Count}개:");
foreach (var doc in docs)
sb.AppendLine($" • [{doc.Label}] {doc.Path}");
{
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();
}
@@ -186,4 +196,21 @@ public class MemoryTool : IAgentTool
? ToolResult.Ok($"{result.Message}\n경로: {result.Path}")
: ToolResult.Fail(result.Message);
}
private static ToolResult ExecuteShowScope(JsonElement args, AgentMemoryService svc, AgentContext context)
{
var scope = args.TryGetProperty("scope", out var s) ? s.GetString() ?? "" : "";
if (string.IsNullOrWhiteSpace(scope))
return ToolResult.Fail("scope가 필요합니다. managed | user | project | local 중 선택하세요.");
var path = svc.GetWritableInstructionPath(scope, context.WorkFolder);
if (string.IsNullOrWhiteSpace(path))
return ToolResult.Fail("해당 scope의 메모리 파일 경로를 결정할 수 없습니다.");
var content = svc.ReadInstructionFile(scope, context.WorkFolder);
if (content == null)
return ToolResult.Ok($"메모리 파일이 아직 없습니다.\n경로: {path}");
return ToolResult.Ok($"메모리 파일 경로: {path}\n\n{content}");
}
}

View File

@@ -415,7 +415,8 @@ public class AgentMemoryService
Label = label,
Path = fullPath,
Content = frontMatter.Content.Trim(),
Paths = frontMatter.Paths
Paths = frontMatter.Paths,
Description = frontMatter.Description
});
}
catch (Exception ex)
@@ -618,11 +619,31 @@ public class AgentMemoryService
}
}
private static (string Content, List<string> Paths) ParseFrontMatter(string content)
public string? ReadInstructionFile(string scope, string? workFolder)
{
lock (_lock)
{
var path = GetWritableInstructionPath(scope, workFolder);
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return null;
try
{
return File.ReadAllText(path);
}
catch (Exception ex)
{
LogService.Warn($"메모리 파일 읽기 실패 ({path}): {ex.Message}");
return null;
}
}
}
private static (string Content, List<string> Paths, string Description) 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>(), "");
var endIndex = -1;
for (var i = 1; i < lines.Count; i++)
@@ -635,13 +656,20 @@ public class AgentMemoryService
}
if (endIndex < 1)
return (content, new List<string>());
return (content, new List<string>(), "");
var paths = new List<string>();
var description = "";
var inPaths = false;
for (var i = 1; i < endIndex; i++)
{
var line = lines[i].Trim();
if (line.StartsWith("description:", StringComparison.OrdinalIgnoreCase))
{
description = line["description:".Length..].Trim().Trim('"');
inPaths = false;
continue;
}
if (line.StartsWith("paths:", StringComparison.OrdinalIgnoreCase))
{
inPaths = true;
@@ -664,7 +692,7 @@ public class AgentMemoryService
}
var stripped = string.Join("\n", lines.Skip(endIndex + 1)).Trim();
return (stripped, paths);
return (stripped, paths, description);
}
private static bool ShouldApplyToCurrentWorkFolder(IReadOnlyList<string> patterns, string? projectRoot, string? currentWorkFolder)
@@ -773,4 +801,7 @@ public class MemoryInstructionDocument
[JsonPropertyName("paths")]
public List<string> Paths { get; set; } = new();
[JsonPropertyName("description")]
public string Description { get; set; } = "";
}