에이전트 선택적 탐색 구조 개선과 경고 정리 반영
Some checks failed
Release Gate / gate (push) Has been cancelled

- claude-code 선택적 탐색 흐름을 참고해 Cowork/Code 시스템 프롬프트에서 folder_map 상시 선행 지시를 완화하고 glob/grep 기반 좁은 탐색을 우선하도록 조정함

- FolderMapTool 기본 depth를 2로, include_files 기본값을 false로 낮추고 MultiReadTool 최대 파일 수를 8개로 줄여 초기 과탐색 폭을 보수적으로 조정함

- AgentLoopExplorationPolicy partial을 추가해 탐색 범위 분류, broad-scan corrective hint, exploration_breadth 성능 로그를 연결함

- AgentLoopService에 탐색 범위 가이드 주입과 실행 중 탐색 폭 추적을 추가하고, 좁은 질문에서 반복적인 folder_map/대량 multi_read를 교정하도록 정리함

- DocxToHtmlConverter nullable 경고를 수정해 Release 빌드 경고 0 / 오류 0 기준을 다시 충족함

- README와 docs/DEVELOPMENT.md에 2026-04-09 10:36 (KST) 기준 개발 이력을 반영함
This commit is contained in:
2026-04-09 14:27:59 +09:00
parent 7931566212
commit 33c1db4dae
119 changed files with 4453 additions and 6943 deletions

View File

@@ -84,15 +84,15 @@ public class ExcelSkill : IAgentTool
{
// path 미제공 시 title 또는 sheet_name에서 자동 생성
string path;
if (args.TryGetProperty("path", out var pathEl) && pathEl.ValueKind == JsonValueKind.String
&& !string.IsNullOrWhiteSpace(pathEl.GetString()))
if (args.SafeTryGetProperty("path", out var pathEl) && pathEl.ValueKind == JsonValueKind.String
&& !string.IsNullOrWhiteSpace(pathEl.SafeGetString()))
{
path = pathEl.GetString()!;
path = pathEl.SafeGetString()!;
}
else
{
var hint = (args.TryGetProperty("title", out var tEl) ? tEl.GetString() : null)
?? (args.TryGetProperty("sheet_name", out var snEl) ? snEl.GetString() : null)
var hint = (args.SafeTryGetProperty("title", out var tEl) ? tEl.SafeGetString() : null)
?? (args.SafeTryGetProperty("sheet_name", out var snEl) ? snEl.SafeGetString() : null)
?? "workbook";
var safe = System.Text.RegularExpressions.Regex.Replace(hint, @"[\\/:*?""<>|]", "_").Trim().TrimEnd('.');
if (safe.Length > 60) safe = safe[..60].TrimEnd();
@@ -116,7 +116,7 @@ public class ExcelSkill : IAgentTool
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
// Determine if we are in multi-sheet mode
var multiSheetMode = args.TryGetProperty("sheets", out var sheetsArr)
var multiSheetMode = args.SafeTryGetProperty("sheets", out var sheetsArr)
&& sheetsArr.ValueKind == JsonValueKind.Array
&& sheetsArr.GetArrayLength() > 0;
@@ -137,16 +137,16 @@ public class ExcelSkill : IAgentTool
private static ToolResult GenerateSingleSheetWorkbook(JsonElement args, string fullPath)
{
if (!args.TryGetProperty("headers", out var headers) || headers.ValueKind != JsonValueKind.Array)
if (!args.SafeTryGetProperty("headers", out var headers) || headers.ValueKind != JsonValueKind.Array)
return ToolResult.Fail("필수 파라미터 누락: 'headers'");
if (!args.TryGetProperty("rows", out var rows) || rows.ValueKind != JsonValueKind.Array)
if (!args.SafeTryGetProperty("rows", out var rows) || rows.ValueKind != JsonValueKind.Array)
return ToolResult.Fail("필수 파라미터 누락: 'rows'");
var sheetName = args.TryGetProperty("sheet_name", out var sn) ? sn.GetString() ?? "Sheet1" : "Sheet1";
var tableStyle = args.TryGetProperty("style", out var st) ? st.GetString() ?? "styled" : "styled";
var themeName = args.TryGetProperty("theme", out var th) ? th.GetString() : null;
var sheetName = args.SafeTryGetProperty("sheet_name", out var sn) ? sn.SafeGetString() ?? "Sheet1" : "Sheet1";
var tableStyle = args.SafeTryGetProperty("style", out var st) ? st.SafeGetString() ?? "styled" : "styled";
var themeName = args.SafeTryGetProperty("theme", out var th) ? th.SafeGetString() : null;
var isStyled = tableStyle != "plain";
var freezeHeader= args.TryGetProperty("freeze_header", out var fh) ? fh.GetBoolean() : isStyled;
var freezeHeader= args.SafeTryGetProperty("freeze_header", out var fh) ? fh.GetBoolean() : isStyled;
var theme = GetTheme(themeName);
var numFmts = ParseNumberFormats(args, "number_formats");
@@ -168,8 +168,8 @@ public class ExcelSkill : IAgentTool
var colCount = headers.GetArrayLength();
var summaryArg = args.TryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = args.TryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var summaryArg = args.SafeTryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = args.SafeTryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var rowCount = WriteSheetContent(worksheetPart, args, sheetName, headers, rows,
isStyled, freezeHeader, theme, numFmts, alignments, customFmts,
@@ -218,7 +218,7 @@ public class ExcelSkill : IAgentTool
}
// Use first sheet's theme for the shared stylesheet
var firstThemeName = sheetsArr[0].TryGetProperty("theme", out var ft) ? ft.GetString() : null;
var firstThemeName = sheetsArr[0].SafeTryGetProperty("theme", out var ft) ? ft.SafeGetString() : null;
var firstTheme = GetTheme(firstThemeName);
var combinedCustomFmts = CollectCustomFormats(allNumFmts);
@@ -235,21 +235,21 @@ public class ExcelSkill : IAgentTool
foreach (var sheetDef in sheetsArr.EnumerateArray())
{
var sheetName = sheetDef.TryGetProperty("name", out var snEl) ? snEl.GetString() ?? $"Sheet{sheetId}" : $"Sheet{sheetId}";
var tableStyle = sheetDef.TryGetProperty("style", out var stEl) ? stEl.GetString() ?? "styled" : "styled";
var themeName = sheetDef.TryGetProperty("theme", out var thEl) ? thEl.GetString() : firstThemeName;
var sheetName = sheetDef.SafeTryGetProperty("name", out var snEl) ? snEl.SafeGetString() ?? $"Sheet{sheetId}" : $"Sheet{sheetId}";
var tableStyle = sheetDef.SafeTryGetProperty("style", out var stEl) ? stEl.SafeGetString() ?? "styled" : "styled";
var themeName = sheetDef.SafeTryGetProperty("theme", out var thEl) ? thEl.SafeGetString() : firstThemeName;
var isStyled = tableStyle != "plain";
var freezeHeader = sheetDef.TryGetProperty("freeze_header", out var fhEl) ? fhEl.GetBoolean() : isStyled;
var freezeHeader = sheetDef.SafeTryGetProperty("freeze_header", out var fhEl) ? fhEl.GetBoolean() : isStyled;
var theme = GetTheme(themeName);
var numFmts = ParseNumberFormats(sheetDef, "number_formats");
var alignments = ParseAlignments(sheetDef, "col_alignments");
if (!sheetDef.TryGetProperty("headers", out var headers) || headers.ValueKind != JsonValueKind.Array) continue;
if (!sheetDef.TryGetProperty("rows", out var rows) || rows.ValueKind != JsonValueKind.Array) continue;
if (!sheetDef.SafeTryGetProperty("headers", out var headers) || headers.ValueKind != JsonValueKind.Array) continue;
if (!sheetDef.SafeTryGetProperty("rows", out var rows) || rows.ValueKind != JsonValueKind.Array) continue;
var colCount = headers.GetArrayLength();
var summaryArg = sheetDef.TryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = sheetDef.TryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var summaryArg = sheetDef.SafeTryGetProperty("summary_row", out var sumEl) ? sumEl : default;
var mergesArg = sheetDef.SafeTryGetProperty("merges", out var mergeEl) ? mergeEl : default;
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
@@ -315,7 +315,7 @@ public class ExcelSkill : IAgentTool
{
CellReference = cellRef,
DataType = CellValues.String,
CellValue = new CellValue(h.ValueKind != JsonValueKind.Undefined ? h.GetString() ?? "" : ""),
CellValue = new CellValue(h.ValueKind != JsonValueKind.Undefined ? h.SafeGetString() ?? "" : ""),
StyleIndex = isStyled ? (uint)1 : 0,
};
headerRow.Append(cell);
@@ -374,7 +374,7 @@ public class ExcelSkill : IAgentTool
var mergeCells = new MergeCells();
foreach (var merge in mergesArg.EnumerateArray())
{
var range = merge.GetString();
var range = merge.SafeGetString();
if (!string.IsNullOrEmpty(range))
mergeCells.Append(new MergeCell { Reference = range });
}
@@ -678,7 +678,7 @@ public class ExcelSkill : IAgentTool
private static Columns? CreateColumns(JsonElement args, int colCount)
{
var hasWidths = args.TryGetProperty("col_widths", out var widthsArr) && widthsArr.ValueKind == JsonValueKind.Array;
var hasWidths = args.SafeTryGetProperty("col_widths", out var widthsArr) && widthsArr.ValueKind == JsonValueKind.Array;
var columns = new Columns();
for (int i = 0; i < colCount; i++)
@@ -706,8 +706,8 @@ public class ExcelSkill : IAgentTool
private static void AddSummaryRow(SheetData sheetData, JsonElement summary,
uint rowNum, int colCount, int dataRowCount, bool isStyled)
{
var label = summary.TryGetProperty("label", out var lbl) ? lbl.GetString() ?? "합계" : "합계";
var colFormulas = summary.TryGetProperty("columns", out var cols) ? cols : default;
var label = summary.SafeTryGetProperty("label", out var lbl) ? lbl.SafeGetString() ?? "합계" : "합계";
var colFormulas = summary.SafeTryGetProperty("columns", out var cols) ? cols : default;
var summaryRow = new Row { RowIndex = rowNum };
@@ -730,9 +730,9 @@ public class ExcelSkill : IAgentTool
};
if (colFormulas.ValueKind == JsonValueKind.Object &&
colFormulas.TryGetProperty(colLetter, out var funcName))
colFormulas.SafeTryGetProperty(colLetter, out var funcName))
{
var func = funcName.GetString()?.ToUpper() ?? "SUM";
var func = funcName.SafeGetString()?.ToUpper() ?? "SUM";
var startRow = 2;
var endRow = startRow + dataRowCount - 1;
cell.CellFormula = new CellFormula($"={func}({colLetter}{startRow}:{colLetter}{endRow})");
@@ -751,20 +751,20 @@ public class ExcelSkill : IAgentTool
private static List<string?> ParseNumberFormats(JsonElement args, string key)
{
var result = new List<string?>();
if (!args.TryGetProperty(key, out var arr) || arr.ValueKind != JsonValueKind.Array)
if (!args.SafeTryGetProperty(key, out var arr) || arr.ValueKind != JsonValueKind.Array)
return result;
foreach (var el in arr.EnumerateArray())
result.Add(el.ValueKind == JsonValueKind.Null ? null : el.GetString());
result.Add(el.ValueKind == JsonValueKind.Null ? null : el.SafeGetString());
return result;
}
private static List<string?> ParseAlignments(JsonElement args, string key)
{
var result = new List<string?>();
if (!args.TryGetProperty(key, out var arr) || arr.ValueKind != JsonValueKind.Array)
if (!args.SafeTryGetProperty(key, out var arr) || arr.ValueKind != JsonValueKind.Array)
return result;
foreach (var el in arr.EnumerateArray())
result.Add(el.ValueKind == JsonValueKind.Null ? null : el.GetString()?.ToLower());
result.Add(el.ValueKind == JsonValueKind.Null ? null : el.SafeGetString()?.ToLower());
return result;
}