계층형 메모리 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:
@@ -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` 범위가 함께 표시됩니다.
|
||||
|
||||
@@ -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 파일처럼 어떤 규칙이 어느 범위에 적용되는지 빠르게 읽을 수 있도록 정리했다.
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; } = "";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user