메모리 규칙 경로 범위 적용을 위한 paths 프런트매터 지원 추가
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
계층형 메모리 문서에 YAML 유사 paths 프런트매터를 추가해 현재 작업 폴더 경로에 따라 규칙 적용 여부를 제어할 수 있도록 했습니다. AgentMemoryService에서 프런트매터를 파싱하고 프로젝트 루트 기준 상대 경로에 대해 *, **, ? glob 매칭을 수행하도록 구현했습니다. README와 DEVELOPMENT 문서에 메모리 규칙 범위 제어 기능과 동작 방식을 2026-04-07 00:22 (KST) 기준으로 반영했고, Release 빌드 경고 0 오류 0을 확인했습니다.
This commit is contained in:
@@ -1399,3 +1399,6 @@ MIT License
|
||||
- 업데이트: 2026-04-07 00:13 (KST)
|
||||
- `claw-code`처럼 외부 메모리 include를 무조건 열어두지 않도록 안전 장치를 추가했습니다. [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [SettingsWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/SettingsWindow.xaml)에 `외부 메모리 include 허용` 설정을 추가했고 기본값은 `꺼짐`입니다.
|
||||
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)는 이 설정이 꺼져 있으면 프로젝트 바깥으로 빠지는 상대 경로, 홈 경로(`@~/...`), 절대 경로 include를 모두 차단합니다. 즉 메모리 내용 관리는 계속 `/memory` 같은 명령으로 하되, include의 보안 정책만 설정으로 다루는 구조로 정리했습니다.
|
||||
- 업데이트: 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 파일처럼 더 세밀하게 제어할 수 있습니다.
|
||||
|
||||
@@ -5165,3 +5165,13 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
||||
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)
|
||||
- `@include` 해석 시 설정을 읽어, 외부 include가 꺼져 있으면 홈 경로(`@~/...`), 절대 경로, 프로젝트 바깥으로 벗어나는 상대 경로를 차단하도록 바꿨다.
|
||||
- `claw-code`처럼 메모리 편집은 도구/명령 중심으로 하고, 외부 include는 별도 안전 정책으로 관리하는 구조를 목표로 한다.
|
||||
|
||||
## 2026-04-07 00:22 (KST)
|
||||
|
||||
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)
|
||||
- 계층형 메모리 문서에 YAML 유사 frontmatter `paths:` 규칙을 추가했다.
|
||||
- 문서 상단이 `---`로 시작하면 `paths:` 아래의 `- pattern` 목록을 읽고, 현재 작업 폴더가 프로젝트 루트 기준 상대 경로로 그 패턴과 일치할 때만 해당 문서를 메모리 계층에 포함한다.
|
||||
- frontmatter는 제거된 뒤 본문만 실제 메모리 콘텐츠로 주입되며, `paths:`가 없는 문서는 기존처럼 항상 적용된다.
|
||||
- [AgentMemoryService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/AgentMemoryService.cs)
|
||||
- 간단한 glob 매처를 추가해 `*`, `**`, `?` 패턴을 지원한다.
|
||||
- 현재는 `.ax/rules/*.md` 같은 프로젝트 규칙 파일을 `claw-code`의 경로 범위 rules와 비슷하게 운영할 수 있는 수준까지 올라온 상태다.
|
||||
|
||||
@@ -405,12 +405,17 @@ public class AgentMemoryService
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
return;
|
||||
|
||||
var frontMatter = ParseFrontMatter(content);
|
||||
if (frontMatter.Paths.Count > 0 && !ShouldApplyToCurrentWorkFolder(frontMatter.Paths, projectRoot, _currentWorkFolder))
|
||||
return;
|
||||
|
||||
_instructionDocuments.Add(new MemoryInstructionDocument
|
||||
{
|
||||
Layer = layer,
|
||||
Label = label,
|
||||
Path = fullPath,
|
||||
Content = content.Trim()
|
||||
Content = frontMatter.Content.Trim(),
|
||||
Paths = frontMatter.Paths
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -613,6 +618,91 @@ public class AgentMemoryService
|
||||
}
|
||||
}
|
||||
|
||||
private static (string Content, List<string> Paths) 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>());
|
||||
|
||||
var endIndex = -1;
|
||||
for (var i = 1; i < lines.Count; i++)
|
||||
{
|
||||
if (string.Equals(lines[i].Trim(), "---", StringComparison.Ordinal))
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex < 1)
|
||||
return (content, new List<string>());
|
||||
|
||||
var paths = new List<string>();
|
||||
var inPaths = false;
|
||||
for (var i = 1; i < endIndex; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (line.StartsWith("paths:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inPaths = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inPaths)
|
||||
{
|
||||
if (line.StartsWith("-", StringComparison.Ordinal))
|
||||
{
|
||||
var pattern = line[1..].Trim().Trim('"');
|
||||
if (!string.IsNullOrWhiteSpace(pattern))
|
||||
paths.Add(pattern);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.Length > 0)
|
||||
inPaths = false;
|
||||
}
|
||||
}
|
||||
|
||||
var stripped = string.Join("\n", lines.Skip(endIndex + 1)).Trim();
|
||||
return (stripped, paths);
|
||||
}
|
||||
|
||||
private static bool ShouldApplyToCurrentWorkFolder(IReadOnlyList<string> patterns, string? projectRoot, string? currentWorkFolder)
|
||||
{
|
||||
if (patterns.Count == 0)
|
||||
return true;
|
||||
if (string.IsNullOrWhiteSpace(projectRoot) || string.IsNullOrWhiteSpace(currentWorkFolder))
|
||||
return false;
|
||||
|
||||
var relative = Path.GetRelativePath(projectRoot, currentWorkFolder).Replace('\\', '/');
|
||||
if (string.Equals(relative, ".", StringComparison.Ordinal))
|
||||
relative = "";
|
||||
|
||||
return patterns.Any(pattern => GlobMatches(relative, pattern));
|
||||
}
|
||||
|
||||
private static bool GlobMatches(string relativePath, string pattern)
|
||||
{
|
||||
var normalizedPattern = (pattern ?? "").Replace('\\', '/').Trim();
|
||||
var normalizedPath = (relativePath ?? "").Replace('\\', '/').Trim('/');
|
||||
if (string.IsNullOrWhiteSpace(normalizedPattern))
|
||||
return false;
|
||||
|
||||
var regex = "^" + System.Text.RegularExpressions.Regex.Escape(normalizedPattern)
|
||||
.Replace(@"\*\*", "§§DOUBLESTAR§§")
|
||||
.Replace(@"\*", "[^/]*")
|
||||
.Replace(@"\?", "[^/]")
|
||||
.Replace("§§DOUBLESTAR§§", ".*")
|
||||
+ "$";
|
||||
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(normalizedPath, regex, System.Text.RegularExpressions.RegexOptions.IgnoreCase))
|
||||
return true;
|
||||
|
||||
if (!string.IsNullOrEmpty(normalizedPath))
|
||||
normalizedPath += "/";
|
||||
return System.Text.RegularExpressions.Regex.IsMatch(normalizedPath, regex, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>텍스트를 토큰으로 분리합니다.</summary>
|
||||
private static HashSet<string> Tokenize(string text)
|
||||
{
|
||||
@@ -680,4 +770,7 @@ public class MemoryInstructionDocument
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("paths")]
|
||||
public List<string> Paths { get; set; } = new();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user