diff --git a/.astra/tests/stress/.astra/cache/5j79jt.cache b/.astra/tests/stress/.astra/cache/5j79jt.cache index 4b32bed..c9cf6ea 100644 --- a/.astra/tests/stress/.astra/cache/5j79jt.cache +++ b/.astra/tests/stress/.astra/cache/5j79jt.cache @@ -1,6 +1,6 @@ --- -id: stress_conflict_1777968986934 -date: 2026-05-05T08:16:26.963Z +id: stress_conflict_1777970539221 +date: 2026-05-05T08:42:19.251Z type: knowledge_artifact standard: P-Reinforce v3.0 tags: [automated, connect_ai, brain_sync] @@ -28,6 +28,6 @@ Final report with inconsistencies. This should be long enough to pass validation | **Processing Time** | `0.0s` | โœ… Fast | ### ๐Ÿ” Decision Audit Trail -- **[PLANNER]** ์ „๋žต ์ˆ˜๋ฆฝ ์ค‘... (11ms) -- **[RESEARCHER]** ํ•ต์‹ฌ ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ ์ค‘... (4ms) -- **[WRITER]** ์ตœ์ข… ๋ฆฌํฌํŠธ ์ž‘์„ฑ ๋ฐ ํŽธ์ง‘ ์ค‘... (7ms) +- **[PLANNER]** ์ „๋žต ์ˆ˜๋ฆฝ ์ค‘... (12ms) +- **[RESEARCHER]** ํ•ต์‹ฌ ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ ์ค‘... (3ms) +- **[WRITER]** ์ตœ์ข… ๋ฆฌํฌํŠธ ์ž‘์„ฑ ๋ฐ ํŽธ์ง‘ ์ค‘... (8ms) diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1777968986934.json b/.astra/tests/stress/.astra/missions/stress_conflict_1777970539221.json similarity index 82% rename from .astra/tests/stress/.astra/missions/stress_conflict_1777968986934.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1777970539221.json index 2ef6134..e05b283 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1777968986934.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1777970539221.json @@ -1,7 +1,7 @@ { - "missionId": "stress_conflict_1777968986934", + "missionId": "stress_conflict_1777970539221", "status": "completed", - "startTime": "2026-05-05T08:16:26.934Z", + "startTime": "2026-05-05T08:42:19.221Z", "totalElapsedMs": 30, "results": { "planner": "Detailed Execution Plan: 1. Research 2. Analyze 3. Write report with high quality.", @@ -15,30 +15,30 @@ { "from": "idle", "to": "planner", - "durationMs": 11, + "durationMs": 12, "message": "์ „๋žต ์ˆ˜๋ฆฝ ์ค‘...", - "ts": "2026-05-05T08:16:26.945Z" + "ts": "2026-05-05T08:42:19.233Z" }, { "from": "planner", "to": "researcher", - "durationMs": 4, + "durationMs": 3, "message": "ํ•ต์‹ฌ ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ ์ค‘...", - "ts": "2026-05-05T08:16:26.949Z" + "ts": "2026-05-05T08:42:19.236Z" }, { "from": "researcher", "to": "writer", - "durationMs": 7, + "durationMs": 8, "message": "์ตœ์ข… ๋ฆฌํฌํŠธ ์ž‘์„ฑ ๋ฐ ํŽธ์ง‘ ์ค‘...", - "ts": "2026-05-05T08:16:26.956Z" + "ts": "2026-05-05T08:42:19.244Z" }, { "from": "writer", "to": "completed", - "durationMs": 8, + "durationMs": 7, "message": "๋ฏธ์…˜ ์™„๋ฃŒ", - "ts": "2026-05-05T08:16:26.964Z" + "ts": "2026-05-05T08:42:19.251Z" } ], "resilienceMetrics": { diff --git a/docs/records/ConnectAI/bugs/BUG-0009-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md b/docs/records/ConnectAI/bugs/BUG-0009-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md new file mode 100644 index 0000000..2d3b387 --- /dev/null +++ b/docs/records/ConnectAI/bugs/BUG-0009-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md @@ -0,0 +1,16 @@ +# Bug: ๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ... + +## Date +2026-05-05 + +## Symptom +๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Astra๊ฐ€ ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณต๋ฐ›์•˜์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์™œ ๊ทธ๋Ÿฐ ์‹ฌ๊ฐํ•œ ํ• ๋ฃจ์‹œ๋„ค์ด์…˜(ํ™˜๊ฐ)์„ ์ผ์œผ์ผฐ๋Š”์ง€, ๊ทธ ๊ตฌ์กฐ์  ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํ•ด๊ฒฐ์ฑ…์„ ์ œ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. --- ### 1. ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ์›์ธ ๋ถ„์„ (Root Cause Analysis) ๋ณด๊ณ ํ•ด์ฃผ์‹  ๋‹ต๋ณ€์˜ ํŒจํ„ด์„ ๋ถ„์„ํ–ˆ์„ ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ **3๊ฐ€์ง€ ๋ณตํ•ฉ์  ์›์ธ**์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #### โ‘  ์ปจํ…์ŠคํŠธ ์˜ค์—ผ (Context Contamination) ์‚ฌ์šฉ์ž๋‹˜๊ป˜์„œ ์งˆ๋ฌธํ•˜์‹ค ๋•Œ ์ด๋ฏธ ์—๋””ํ„ฐ์— `ConnectAI/src/lib/engine.ts`๊ฐ€ ์—ด๋ ค ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. AI ๋ชจ๋ธ์€ "ํ˜„์žฌ ์—ด๋ฆฐ ํŒŒ์ผ"์„ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ํžŒํŠธ๋กœ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค. `๋ธ”๋กœ๊ทธ_v3` ํ”„๋กœ์ ํŠธ๊ฐ€ ์ดˆ๊ธฐ ๋‹จ๊ณ„์ด๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ์ ์—ˆ์„ ๊ฒฝ์šฐ, AI๋Š” ๋ถ€์กฑํ•œ ์ •๋ณด๋ฅผ ๋ณด์ถฉํ•˜๊ธฐ ์œ„ํ•ด ๋ˆˆ์•ž์— ๋ณด์ด๋Š” `ConnectAI`์˜ ๋ณต์žกํ•œ ๋กœ์ง์„ `... + +## Cause +Captured automatically from the current conversation. Confirm root cause during follow-up review if needed. + +## Fix +## ๊ฐ„๋‹จ ์š”์•ฝ ์ด ์š”์ฒญ์€ ํ”„๋กœ์ ํŠธ ์ง€์‹ ์ƒ์„ฑ์ด ์•„๋‹ˆ๋ผ ์ฝ”๋“œ๋ฆฌ๋ทฐ์™€ ์ œํ’ˆ ํ‰๊ฐ€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. ํ™•์ธ๋œ ํŒŒ์ผ ๊ตฌ์กฐ ๊ธฐ์ค€์œผ๋กœ ๋ณด๋ฉด, ์ด ํ”„๋กœ์ ํŠธ๋Š” ์ง€์‹ ์ˆ˜์ง‘ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์•ฑ ํ˜•ํƒœ๋กœ ๋ฌถ์–ด ์šด์˜ํ•˜๋ ค๋Š” ๋„๊ตฌ๋กœ ๋ณด์ด๋ฉฐ, ๋จผ์ € ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ํ๋ฆ„์˜ ์•ˆ์ •์„ฑ, ์™ธ๋ถ€ ์—ฐ๋™ ์‹คํŒจ ์ฒ˜๋ฆฌ, ์ˆ˜์ง‘ ๊ฒฐ๊ณผ์˜ ์ €์žฅ/์žฌ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ์„ฑ์„ ์ค‘์‹ฌ์œผ๋กœ ํ‰๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ## ํ™•์ธ๋œ ๊ทผ๊ฑฐ ๋Œ€์ƒ ๊ฒฝ๋กœ: `/Volumes/Data/project/Antigravity/ConnectAI` ํ™•์ธ๋œ ์šฐ์„  ํŒŒ์ผ: - `package.json` - `docs/docs/records/docs/README.md` - `docs/records/ConnectAI/README.md` - `README.md` - `docs/Advanced_Features_Implementation_Guide.md` - `docs/AgentEngine_Architecture.md` - `docs/docs/records/docs/bugs/BUG-0001-viewed-integr... + +## Prevention +Keep automatic records tied to the active project and verify the relevant test or reproduction path. diff --git a/docs/records/ConnectAI/bugs/BUG-0010-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md b/docs/records/ConnectAI/bugs/BUG-0010-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md new file mode 100644 index 0000000..08b3db3 --- /dev/null +++ b/docs/records/ConnectAI/bugs/BUG-0010-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md @@ -0,0 +1,16 @@ +# Bug: ๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ... + +## Date +2026-05-05 + +## Symptom +๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Astra๊ฐ€ ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณต๋ฐ›์•˜์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์™œ ๊ทธ๋Ÿฐ ์‹ฌ๊ฐํ•œ ํ• ๋ฃจ์‹œ๋„ค์ด์…˜(ํ™˜๊ฐ)์„ ์ผ์œผ์ผฐ๋Š”์ง€, ๊ทธ ๊ตฌ์กฐ์  ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํ•ด๊ฒฐ์ฑ…์„ ์ œ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. --- ### 1. ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ์›์ธ ๋ถ„์„ (Root Cause Analysis) ๋ณด๊ณ ํ•ด์ฃผ์‹  ๋‹ต๋ณ€์˜ ํŒจํ„ด์„ ๋ถ„์„ํ–ˆ์„ ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ **3๊ฐ€์ง€ ๋ณตํ•ฉ์  ์›์ธ**์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #### โ‘  ์ปจํ…์ŠคํŠธ ์˜ค์—ผ (Context Contamination) ์‚ฌ์šฉ์ž๋‹˜๊ป˜์„œ ์งˆ๋ฌธํ•˜์‹ค ๋•Œ ์ด๋ฏธ ์—๋””ํ„ฐ์— `ConnectAI/src/lib/engine.ts`๊ฐ€ ์—ด๋ ค ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. AI ๋ชจ๋ธ์€ "ํ˜„์žฌ ์—ด๋ฆฐ ํŒŒ์ผ"์„ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ํžŒํŠธ๋กœ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค. `๋ธ”๋กœ๊ทธ_v3` ํ”„๋กœ์ ํŠธ๊ฐ€ ์ดˆ๊ธฐ ๋‹จ๊ณ„์ด๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ์ ์—ˆ์„ ๊ฒฝ์šฐ, AI๋Š” ๋ถ€์กฑํ•œ ์ •๋ณด๋ฅผ ๋ณด์ถฉํ•˜๊ธฐ ์œ„ํ•ด ๋ˆˆ์•ž์— ๋ณด์ด๋Š” `ConnectAI`์˜ ๋ณต์žกํ•œ ๋กœ์ง์„ `... + +## Cause +Captured automatically from the current conversation. Confirm root cause during follow-up review if needed. + +## Fix +--- id: mission_1777980988810 date: 2026-05-05T11:38:09.342Z type: knowledge_artifact standard: P-Reinforce v3.0 tags: [automated, connect_ai, brain_sync] --- ## ๐Ÿ“Œ Brief Summary ## ์ตœ์ข… ํ•ฉ์„ฑ ๋ณด๊ณ ์„œ: LLM ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ ๋ถ„์„ ๋ฐ ์•ˆ์ •ํ™” ์ „๋žต... ## ์ตœ์ข… ํ•ฉ์„ฑ ๋ณด๊ณ ์„œ: LLM ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ ๋ถ„์„ ๋ฐ ์•ˆ์ •ํ™” ์ „๋žต ### ๐Ÿ“ Executive Summary (์š”์•ฝ) ์ œ๊ณตํ•ด์ฃผ์‹  ์ฝ”๋“œ๋Š” ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ํ•˜๊ธฐ ์œ„ํ•ด **๋งค์šฐ ์ •๊ตํ•˜๊ณ  ๊ฒฌ๊ณ ํ•˜๊ฒŒ ์„ค๊ณ„๋œ ์•„ํ‚คํ…์ฒ˜**๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. `MissionState`๋ฅผ ํ†ตํ•œ ๋ช…์‹œ์ ์ธ ์ƒํƒœ ์ถ”์ , `Error Recovery Matrix` ๊ธฐ๋ฐ˜์˜ ํƒ„๋ ฅ์ ์ธ ๋ณต๊ตฌ ๋กœ์ง, ๊ทธ๋ฆฌ๊ณ  `CacheManager`๋ฅผ ํ†ตํ•œ ์ค‘๋ณต ๋ฐฉ์ง€ ๊ธฐ๋Šฅ ๋“ฑ์€ ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ๊ณผ ํˆฌ๋ช…์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ๊ฐ•... + +## Prevention +Keep automatic records tied to the active project and verify the relevant test or reproduction path. diff --git a/docs/records/ConnectAI/bugs/BUG-0011-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md b/docs/records/ConnectAI/bugs/BUG-0011-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md new file mode 100644 index 0000000..a456f51 --- /dev/null +++ b/docs/records/ConnectAI/bugs/BUG-0011-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md @@ -0,0 +1,16 @@ +# Bug: ๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ... + +## Date +2026-05-05 + +## Symptom +๋ฌธ์ œ์ ์„ ์ฝ๊ณ  ์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•˜๋Š”๊ฒŒ ์ตœ์„ ์ธ์ง€ ๋ถ„์„ํ•ด์ฃผ๋ฉด ์ข‹๊ฒ ์–ด. ์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ **ConnectAI ํ”„๋กœ์ ํŠธ**์—๋งŒ ์™„์ „ํžˆ ์ง‘์ค‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. Astra๊ฐ€ ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณต๋ฐ›์•˜์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์™œ ๊ทธ๋Ÿฐ ์‹ฌ๊ฐํ•œ ํ• ๋ฃจ์‹œ๋„ค์ด์…˜(ํ™˜๊ฐ)์„ ์ผ์œผ์ผฐ๋Š”์ง€, ๊ทธ ๊ตฌ์กฐ์  ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํ•ด๊ฒฐ์ฑ…์„ ์ œ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. --- ### 1. ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ์›์ธ ๋ถ„์„ (Root Cause Analysis) ๋ณด๊ณ ํ•ด์ฃผ์‹  ๋‹ต๋ณ€์˜ ํŒจํ„ด์„ ๋ถ„์„ํ–ˆ์„ ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ **3๊ฐ€์ง€ ๋ณตํ•ฉ์  ์›์ธ**์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. #### โ‘  ์ปจํ…์ŠคํŠธ ์˜ค์—ผ (Context Contamination) ์‚ฌ์šฉ์ž๋‹˜๊ป˜์„œ ์งˆ๋ฌธํ•˜์‹ค ๋•Œ ์ด๋ฏธ ์—๋””ํ„ฐ์— `ConnectAI/src/lib/engine.ts`๊ฐ€ ์—ด๋ ค ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. AI ๋ชจ๋ธ์€ "ํ˜„์žฌ ์—ด๋ฆฐ ํŒŒ์ผ"์„ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ํžŒํŠธ๋กœ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค. `๋ธ”๋กœ๊ทธ_v3` ํ”„๋กœ์ ํŠธ๊ฐ€ ์ดˆ๊ธฐ ๋‹จ๊ณ„์ด๊ฑฐ๋‚˜ ํŒŒ์ผ์ด ์ ์—ˆ์„ ๊ฒฝ์šฐ, AI๋Š” ๋ถ€์กฑํ•œ ์ •๋ณด๋ฅผ ๋ณด์ถฉํ•˜๊ธฐ ์œ„ํ•ด ๋ˆˆ์•ž์— ๋ณด์ด๋Š” `ConnectAI`์˜ ๋ณต์žกํ•œ ๋กœ์ง์„ `... + +## Cause +Captured automatically from the current conversation. Confirm root cause during follow-up review if needed. + +## Fix +--- id: mission_1777982222535 date: 2026-05-05T11:58:51.779Z type: knowledge_artifact standard: P-Reinforce v3.0 tags: [automated, connect_ai, brain_sync] --- ## ๐Ÿ“Œ Brief Summary # ์ตœ์ข… ํ•ฉ์„ฑ ๋ณด๊ณ ์„œ: Multi-Agent Orchestration Engine ์•„ํ‚คํ…์ฒ˜ ์‹ฌ์ธต ๊ฒ€ํ† ... # ์ตœ์ข… ํ•ฉ์„ฑ ๋ณด๊ณ ์„œ: Multi-Agent Orchestration Engine ์•„ํ‚คํ…์ฒ˜ ์‹ฌ์ธต ๊ฒ€ํ†  ## ๐Ÿ“ Executive Summary (์š”์•ฝ) ๋ณธ ๋ณด๊ณ ์„œ๋Š” ์ œ๊ณต๋œ `AgentEngine` ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•œ ํฌ๊ด„์ ์ธ ์•„ํ‚คํ…์ฒ˜ ๋ฐ ๊ธฐ๋Šฅ ๊ฑด์ „์„ฑ ๊ฒ€ํ†  ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€ํ†  ๊ฒฐ๊ณผ, ํ•ด๋‹น ์—”์ง„์€ **๋™์‹œ์„ฑ ์ œ์–ด, ์ƒํƒœ ์˜์†์„ฑ, ์˜ค๋ฅ˜ ๋ณต๊ตฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜** ์ธก๋ฉด์—์„œ ๋งค์šฐ ๊ฒฌ๊ณ ํ•˜๊ณ  ์ •๊ตํ•˜๊ฒŒ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, `MissionState`๋ฅผ ํ†ตํ•œ ๊ฐ์‚ฌ ์ถ”์  ๊ธฐ๋Šฅ๊ณผ `E... + +## Prevention +Keep automatic records tied to the active project and verify the relevant test or reproduction path. diff --git a/docs/records/ConnectAI/chronicle.config.json b/docs/records/ConnectAI/chronicle.config.json index 9988367..5831989 100644 --- a/docs/records/ConnectAI/chronicle.config.json +++ b/docs/records/ConnectAI/chronicle.config.json @@ -6,6 +6,6 @@ "description": "Auto-detected from the local project path in the conversation.", "corePurpose": "Capture project direction, architecture discussion, decisions, and development notes as Markdown.", "detailLevel": "standard", - "createdAt": "2026-05-05T07:47:13.411Z", - "updatedAt": "2026-05-05T07:47:13.415Z" + "createdAt": "2026-05-05T11:58:51.782Z", + "updatedAt": "2026-05-05T11:58:51.792Z" } diff --git a/docs/records/ConnectAI/timeline.md b/docs/records/ConnectAI/timeline.md index 1f829be..7e4bf36 100644 --- a/docs/records/ConnectAI/timeline.md +++ b/docs/records/ConnectAI/timeline.md @@ -69,3 +69,12 @@ ## 2026-05-05 - Auto development record created: development/2026-05-05_volumes-data-project-antigravity-connectai-์ด-ํ”„๋กœ์ ํŠธ-๋ถ„์„ํ•ด์ค˜-volum_implementation.md + +## 2026-05-05 +- Auto bug record created: bugs/BUG-0009-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md + +## 2026-05-05 +- Auto bug record created: bugs/BUG-0010-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md + +## 2026-05-05 +- Auto bug record created: bugs/BUG-0011-๋ฌธ์ œ์ ์„-์ฝ๊ณ -์–ด๋–ป๊ฒŒ-๊ฐœ์„ ํ•˜๋Š”๊ฒŒ-์ตœ์„ ์ธ์ง€-๋ถ„์„ํ•ด์ฃผ๋ฉด-์ข‹๊ฒ ์–ด-์•Œ๊ฒ ์Šต๋‹ˆ๋‹ค-์ง€๊ธˆ๋ถ€ํ„ฐ-connectai-ํ”„๋กœ์ ํŠธ-์—.md diff --git a/src/lib/api.ts b/src/lib/api.ts index bd9cf8f..0a2de75 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,18 +1,12 @@ import { logInfo, logError } from '../utils'; - -export enum ApiErrorType { - AUTH_FAILURE = 'AUTH_FAILURE', - RATE_LIMIT = 'RATE_LIMIT', - NETWORK_TIMEOUT = 'NETWORK_TIMEOUT', - SERVER_ERROR = 'SERVER_ERROR', - UNKNOWN = 'UNKNOWN' -} +import { ErrorType } from '../types/interfaces'; +import { ErrorClassifier } from './engine'; export interface ApiResponse { success: boolean; data?: T; error?: { - type: ApiErrorType; + type: ErrorType; message: string; isRecoverable: boolean; }; @@ -33,9 +27,6 @@ export class ExternalApiHandler { return false; // ํ˜„์žฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ˆ˜๋™ ๊ฐœ์ž… ํ•„์š”๋กœ ์ฒ˜๋ฆฌ } - /** - * ์ง€๋Šฅํ˜• ์š”์ฒญ ๋ž˜ํผ (Resilient Fetch) - */ public static async request( call: () => Promise, context: string @@ -45,25 +36,18 @@ export class ExternalApiHandler { return { success: true, data }; } catch (error: any) { logError(`[ApiHandler] [${context}] ํ˜ธ์ถœ ์‹คํŒจ:`, error); - const errorType = this.classifyError(error); + + // ErrorClassifier๋ฅผ ํ†ตํ•ด ์‹œ์Šคํ…œ ํ†ตํ•ฉ ๋ถ„๋ฅ˜ ์ ์šฉ + const { type, rule } = ErrorClassifier.classify(error); return { success: false, error: { - type: errorType, + type, message: error.message, - isRecoverable: errorType === ApiErrorType.NETWORK_TIMEOUT || errorType === ApiErrorType.RATE_LIMIT + isRecoverable: type === ErrorType.TRANSIENT || type === ErrorType.AUTH_FAILURE } }; } } - - private static classifyError(error: any): ApiErrorType { - const msg = error.message || ''; - if (msg.includes('401') || msg.includes('unauthorized')) return ApiErrorType.AUTH_FAILURE; - if (msg.includes('429') || msg.includes('rate limit')) return ApiErrorType.RATE_LIMIT; - if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) return ApiErrorType.NETWORK_TIMEOUT; - if (msg.includes('500') || msg.includes('502') || msg.includes('503')) return ApiErrorType.SERVER_ERROR; - return ApiErrorType.UNKNOWN; - } } diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts index 919de53..aead626 100644 --- a/src/lib/diagnostics.ts +++ b/src/lib/diagnostics.ts @@ -48,6 +48,27 @@ export class AgentDataValidator { return { score, conflictRisk }; } + + /** + * [New] ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ง„๋‹จํ•ฉ๋‹ˆ๋‹ค (์—๋Ÿฌ๋ฅผ ๋˜์ง€์ง€ ์•Š์Œ). + */ + public static audit(stage: string, data: string): { score: number, conflictRisk: number, issues: string[] } { + const issues: string[] = []; + if (!data || data.trim().length === 0) { + issues.push('๋ฐ์ดํ„ฐ๊ฐ€ ์™„์ „ํžˆ ๋น„์–ด ์žˆ์Œ'); + return { score: 0, conflictRisk: 100, issues }; + } + + if (data.length < 50) issues.push('๋ฐ์ดํ„ฐ ๊ธธ์ด๊ฐ€ ๋„ˆ๋ฌด ์งง์Œ (์ž ์žฌ์  ๋ˆ„๋ฝ)'); + + const score = QualityScorer.evaluate(data); + const conflictRisk = ConflictDetector.analyzeSemanticDivergence(data); + + if (score < 40) issues.push('ํ’ˆ์งˆ ์ ์ˆ˜ ๋งค์šฐ ๋‚ฎ์Œ'); + if (conflictRisk > 60) issues.push('์‹ฌ๊ฐํ•œ ์ง€์‹ ์ถฉ๋Œ ๊ฐ์ง€'); + + return { score, conflictRisk, issues }; + } } export class QualityScorer { @@ -132,17 +153,27 @@ export class ConflictDetector { for (const [pos, neg] of conflictTerms) { if (data.includes(pos) && data.includes(neg)) { - // ๋‘ ์ƒ์ถฉ ์šฉ์–ด๊ฐ€ ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šด ๊ฑฐ๋ฆฌ(์˜ˆ: 200์ž ์ด๋‚ด)์— ์žˆ์œผ๋ฉด ์ถฉ๋Œ ํ™•๋ฅ  ๋†’์Œ const posIdx = data.indexOf(pos); const negIdx = data.indexOf(neg); - if (Math.abs(posIdx - negIdx) < 300) { + if (Math.abs(posIdx - negIdx) < 400) { riskScore += 15; } } } - // 3. ๋ช…์‹œ์  ๊ฒฝ๊ณ  ํƒœ๊ทธ ํ™•์ธ - if (data.includes('[CONFLICT WARNING]')) riskScore += 30; + // 3. ๊ธฐ์ˆ ์  ์ƒ์ถฉ (Technical Divergence) + const technicalConflicts = [ + /๋™๊ธฐ(์‹)?.*๋น„๋™๊ธฐ(์‹)?/g, + /๋กœ์ปฌ.*ํด๋ผ์šฐ๋“œ/g, + /์ •์ .*๋™์ /g, + /Success.*Fail/i + ]; + for (const pattern of technicalConflicts) { + if (pattern.test(data)) riskScore += 10; + } + + // 4. ๋ช…์‹œ์  ๊ฒฝ๊ณ  ํƒœ๊ทธ ํ™•์ธ + if (data.includes('[CONFLICT WARNING]') || data.includes('[ERROR]')) riskScore += 30; return Math.min(100, riskScore); } diff --git a/src/lib/engine.ts b/src/lib/engine.ts index 32aa0bc..dc94d21 100644 --- a/src/lib/engine.ts +++ b/src/lib/engine.ts @@ -1,11 +1,14 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; +import { createHash } from 'crypto'; import { lockManager } from '../core/lock'; import { actionQueue } from '../core/queue'; import { logInfo, logError } from '../utils'; import { AgentDataValidator, PerformanceProfiler, CognitionAudit } from './diagnostics'; import { WikiFormatter } from './formatter'; +import { ErrorType, RecoveryRule } from '../types/interfaces'; +export { ErrorType, RecoveryRule }; // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // 1. ์—์ด์ „ํŠธ ์ธํ„ฐํŽ˜์ด์Šค ํ™•์žฅ (Interface Extensibility) @@ -73,6 +76,7 @@ export class MissionState { public readonly missionId: string; public readonly startTime: number; + public readonly promptHash: string; // [Structural Fix] ์ •์‹ ํ•„๋“œ๋กœ ์ถ”๊ฐ€ public resilienceMetrics = { fallbacks: 0, retries: 0, @@ -80,8 +84,9 @@ export class MissionState { deduplications: 0 }; - constructor(missionId: string) { + constructor(missionId: string, promptHash: string = '') { this.missionId = missionId; + this.promptHash = promptHash; // ์ƒ์„ฑ ์‹œ์ ์— ์ฆ‰์‹œ ํ• ๋‹น this.startTime = Date.now(); this._lastTransitionTime = this.startTime; } @@ -152,15 +157,24 @@ export class MissionState { /** * ์ €์žฅ๋œ ๋ฏธ์…˜ ์ƒํƒœ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค (Resumption์šฉ). + * ํ”„๋กฌํ”„ํŠธ ํ•ด์‹œ๋ฅผ ๋น„๊ตํ•˜์—ฌ ์งˆ๋ฌธ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๊ฒฝ์šฐ ๋ฌดํšจํ™”ํ•ฉ๋‹ˆ๋‹ค. */ - public static loadFromDisk(missionId: string): MissionState | null { + public static loadFromDisk(missionId: string, currentPrompt: string): MissionState | null { try { const workspacePath = process.env.ASTRA_TEST_ROOT || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd(); const filePath = path.join(workspacePath, '.astra', 'missions', `${missionId}.json`); if (!fs.existsSync(filePath)) return null; const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); - const state = new MissionState(missionId); + + // [Structural Fix] ํ”„๋กฌํ”„ํŠธ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ + const currentPromptHash = createHash('sha256').update(currentPrompt).digest('hex').slice(0, 16); + if (data.promptHash && data.promptHash !== currentPromptHash) { + logInfo(`[MissionState] ํ”„๋กฌํ”„ํŠธ ๋ณ€๊ฒฝ ๊ฐ์ง€. ์ด์ „ ์ƒํƒœ๋ฅผ ํ๊ธฐํ•˜๊ณ  ์ƒˆ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.`); + return null; + } + + const state = new MissionState(missionId, currentPromptHash); state._stage = data.status; state._results = data.results || {}; state.resilienceMetrics = data.resilienceMetrics || { @@ -218,6 +232,7 @@ export class MissionState { totalElapsedMs: this.getElapsedMs(), failureReason: this._failureReason, results: this._results, + promptHash: this.promptHash, // ์ •์‹ ํ•„๋“œ ์ฐธ์กฐ transitionCount: this._auditTrail.length, transitions: this._auditTrail.map(e => ({ from: e.from, @@ -232,39 +247,9 @@ export class MissionState { } // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -// 3. Error Recovery Matrix (์˜ค๋ฅ˜ ๋ณต๊ตฌ ๋งคํŠธ๋ฆญ์Šค) +// 3. Error Recovery Logic (์˜ค๋ฅ˜ ๋ณต๊ตฌ ๋กœ์ง) // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -/** - * ์˜ค๋ฅ˜ ์œ ํ˜• ๋ถ„๋ฅ˜. - * ์—์ด์ „ํŠธ ๊ฐ„ ํ†ต์‹  ์‹คํŒจ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ ์„ธ ๋ฒ”์ฃผ๋กœ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค. - */ -export enum ErrorType { - /** ๋„คํŠธ์›Œํฌ ํƒ€์ž„์•„์›ƒ, API ์‘๋‹ต ์ง€์—ฐ ๋“ฑ ์žฌ์‹œ๋„๋กœ ๋ณต๊ตฌ ๊ฐ€๋Šฅํ•œ ์˜ค๋ฅ˜ */ - TRANSIENT = 'TRANSIENT', - /** ํ”„๋กฌํ”„ํŠธ ๊ตฌ์กฐ ๋ฌธ์ œ, ๋ชจ๋ธ ๋ช…๋ฐฑํ•œ ์‹คํŒจ ๋“ฑ ์ˆ˜๋™ ๊ฐœ์ž…์ด ํ•„์š”ํ•œ ์˜ค๋ฅ˜ */ - PERMANENT = 'PERMANENT', - /** ์ธ์ฆ ์‹คํŒจ (API Key ๋งŒ๋ฃŒ ๋“ฑ) */ - AUTH_FAILURE = 'AUTH_FAILURE', - /** ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„์ ์œผ๋กœ ์ž‘์—…์„ ์ทจ์†Œํ•œ ๊ฒฝ์šฐ */ - ABORT = 'ABORT' -} - -/** - * Error Recovery Matrix์˜ ๋‹จ์ผ ๊ทœ์น™ ์ •์˜. - * ์˜ค๋ฅ˜ ์œ ํ˜•๋ณ„ ๋Œ€์‘ ์ „๋žต์„ ์„ ์–ธ์ ์œผ๋กœ ๊ณต์‹ํ™”ํ•ฉ๋‹ˆ๋‹ค. - */ -export interface RecoveryRule { - type: ErrorType; - description: string; - maxRetries: number; - backoffBaseMs: number; - action: 'retry' | 'abort' | 'fail_with_message' | 'fallback'; - userMessage: string; - /** 'fallback' ์•ก์…˜ ์‹œ ์‹คํ–‰ํ•  ๋Œ€์ฒด ๋กœ์ง (์˜ˆ: ์บ์‹œ ๋ฐ˜ํ™˜) */ - fallbackDescription?: string; -} - /** * โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” * โ”‚ Error Recovery Matrix (์˜ค๋ฅ˜ ๋ณต๊ตฌ ๋งคํŠธ๋ฆญ์Šค) โ”‚ @@ -318,9 +303,13 @@ export class ErrorClassifier { /Failed to fetch/i, /503/, // Service Unavailable /502/, // Bad Gateway + /500/, // Internal Server Error (์ผ์‹œ์ ์ผ ์ˆ˜ ์žˆ์Œ) /429/, // Too Many Requests (Rate Limit) /ENOTFOUND/i, /socket hang up/i, + /out of memory/i, // GPU/System OOM + /oom/i, + /failed to allocate/i ]; /** Permanent Error๋กœ ๋ถ„๋ฅ˜๋˜๋Š” ํŒจํ„ด */ @@ -333,6 +322,10 @@ export class ErrorClassifier { /invalid.*model/i, /model.*not found/i, /parse error/i, + /context.*length.*exceeded/i, + /max.*token.*limit/i, + /safety.*filter/i, + /blocked.*content/i ]; /** @@ -392,28 +385,46 @@ export class CacheManager { } private static getHash(key: string): string { - let hash = 0; - for (let i = 0; i < key.length; i++) { - const char = key.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash |= 0; - } - return hash.toString(36); + return createHash('sha256').update(key).digest('hex'); } - public static get(prompt: string, context: string): string | null { + public static get(prompt: string, context: string, currentModel: string = 'unknown'): string | null { const key = this.getHash(prompt + context); - const filePath = path.join(this.getCacheDir(), `${key}.cache`); - if (fs.existsSync(filePath)) { - return fs.readFileSync(filePath, 'utf-8'); + const filePath = path.join(this.getCacheDir(), `${key}.json`); // ํ™•์žฅ์ž .json์œผ๋กœ ๋ณ€๊ฒฝ + + if (!fs.existsSync(filePath)) return null; + + try { + const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + + // 1. TTL ๊ฒ€์ฆ (๊ธฐ๋ณธ 7์ผ) + const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; + if (Date.now() - data.createdAt > SEVEN_DAYS_MS) { + logInfo(`[CacheManager] ์บ์‹œ ๋งŒ๋ฃŒ๋จ (TTL ์ดˆ๊ณผ).`); + return null; + } + + // 2. ๋ชจ๋ธ ๋ฒ„์ „ ๊ฒ€์ฆ + if (data.modelVersion !== currentModel && currentModel !== 'unknown') { + logInfo(`[CacheManager] ๋ชจ๋ธ ๋ฒ„์ „ ๋ถˆ์ผ์น˜ (${data.modelVersion} -> ${currentModel}). ์บ์‹œ ์Šคํ‚ต.`); + return null; + } + + return data.result; + } catch (err) { + return null; } - return null; } - public static set(prompt: string, context: string, result: string): void { + public static set(prompt: string, context: string, result: string, modelVersion: string = 'unknown'): void { const key = this.getHash(prompt + context); - const filePath = path.join(this.getCacheDir(), `${key}.cache`); - fs.writeFileSync(filePath, result, 'utf-8'); + const filePath = path.join(this.getCacheDir(), `${key}.json`); + const cacheData = { + result, + createdAt: Date.now(), + modelVersion + }; + fs.writeFileSync(filePath, JSON.stringify(cacheData, null, 2), 'utf-8'); } } @@ -453,10 +464,12 @@ export class AgentEngine { options?: AgentExecuteOptions ): Promise { let state: MissionState; + const promptHash = createHash('sha256').update(prompt).digest('hex').slice(0, 16); + + // 0. ์ƒํƒœ ๋ณต์› ์‹œ๋„ (Resumption) - ํ”„๋กฌํ”„ํŠธ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์‚ฌ ํฌํ•จ + const existingState = MissionState.loadFromDisk(missionId, prompt); + state = (existingState && existingState.stage !== 'completed') ? existingState : new MissionState(missionId, promptHash); - // 0. ์ƒํƒœ ๋ณต์› ์‹œ๋„ (Resumption) - const existingState = MissionState.loadFromDisk(missionId); - state = (existingState && existingState.stage !== 'completed') ? existingState : new MissionState(missionId); // 1. ๋ช…์‹œ์  ๋ฝ ํš๋“ (Mutex) const release = await lockManager.acquire(`mission_${missionId}`); @@ -465,10 +478,12 @@ export class AgentEngine { return await actionQueue.enqueue(async () => { logInfo(`[AgentEngine] ๋ฏธ์…˜ ์‹œ์ž‘: ${missionId}`); - // [Blind Spot Check] ์ „์—ญ ์ค‘๋ณต ์ œ๊ฑฐ (Global Deduplication) - const globalCache = CacheManager.get(prompt, 'global_final_report'); + // [Structural Fix] ๊ธ€๋กœ๋ฒŒ ์บ์‹œ ํ‚ค์— ํ”„๋กฌํ”„ํŠธ ํ•ด์‹œ ๊ฒฐํ•ฉ (๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋ถ„๋ฆฌ) + const globalCacheKey = `global_final_report_${promptHash}`; + const currentModel = (options?.config?.model as string) || 'unknown'; + const globalCache = CacheManager.get(prompt, globalCacheKey, currentModel); if (globalCache) { - logInfo(`[AgentEngine] [Deduplication] ์ตœ์ข… ๋ฆฌํฌํŠธ ์บ์‹œ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹คํ–‰์„ ์Šคํ‚ตํ•ฉ๋‹ˆ๋‹ค.`); + logInfo(`[AgentEngine] [Deduplication] '${globalCacheKey}' ์บ์‹œ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.`); this.transition(state, 'completed', '์บ์‹œ๋œ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜', onProgress); return globalCache; } @@ -478,20 +493,25 @@ export class AgentEngine { state, 'planner', '์ „๋žต ์ˆ˜๋ฆฝ ์ค‘...', () => this.resilientExecute(state, this.planner, 'Planner', prompt, brainContext, signal, onProgress, { ...options, - context: brainContext, signal, config: { ...options?.config, role: 'planner' } + context: brainContext, + signal, + config: { ...options?.config, role: 'planner', isSamePrompt: true } }), prompt, brainContext, signal, onProgress ); const plannerScore = this.validateResult(plan, 'Planner'); - const researcherLevel: AbstractionLevel = plannerScore < 70 ? 'laser-focused' : 'balanced'; + // [Structural Fix] ์ ์ˆ˜๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋” ์ƒ์„ธํ•œ ๊ทผ๊ฑฐ๋ฅผ ์š”๊ตฌ(comprehensive)ํ•˜๋„๋ก ๋กœ์ง ์—ญ์ „ + const researcherLevel: AbstractionLevel = plannerScore < 70 ? 'comprehensive' : 'balanced'; // --- Phase 2: Researcher --- const research = await this.executeStep( state, 'researcher', 'ํ•ต์‹ฌ ์ •๋ณด ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ ์ค‘...', () => this.resilientExecute(state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress, { ...options, - context: brainContext, signal, config: { ...options?.config, role: 'researcher' }, + context: brainContext, + signal, + config: { ...options?.config, role: 'researcher', isSamePrompt: true }, abstractionLevel: researcherLevel }), plan, brainContext, signal, onProgress @@ -505,14 +525,17 @@ export class AgentEngine { } const researchScore = this.validateResult(research, 'Researcher'); - const writerLevel: AbstractionLevel = researchScore < 65 ? 'laser-focused' : 'balanced'; + // [Structural Fix] ์ ์ˆ˜๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋” ์ƒ์„ธํ•œ ๊ทผ๊ฑฐ๋ฅผ ์š”๊ตฌ(comprehensive)ํ•˜๋„๋ก ๋กœ์ง ์—ญ์ „ + const writerLevel: AbstractionLevel = researchScore < 65 ? 'comprehensive' : 'balanced'; // --- Phase 4: Writer --- const finalReport = await this.executeStep( state, 'writer', '์ตœ์ข… ๋ฆฌํฌํŠธ ์ž‘์„ฑ ๋ฐ ํŽธ์ง‘ ์ค‘...', () => this.resilientExecute(state, this.writer, 'Writer', research, prompt, signal, onProgress, { ...options, - context: brainContext, signal, config: { role: 'writer', allowFallback: true, ...options?.config }, + context: brainContext, + signal, + config: { role: 'writer', allowFallback: true, isSamePrompt: true, ...options?.config }, priorResults: { plan, writerPrep, previousValidData: state.getResult('finalReport'), ...options?.priorResults }, abstractionLevel: writerLevel }), @@ -523,11 +546,16 @@ export class AgentEngine { // --- Phase 5: Advice & Standardization --- const proactiveAdvice = await this.generateProactiveAdvice(finalReport, prompt, brainContext, signal); - const enrichedReport = `${finalReport}\n\n---\n## ๐Ÿ’ก Astra์˜ ์„ ์ œ์  ์ œ์•ˆ (Proactive Next Actions)\n${proactiveAdvice}`; + + // [Structural Fix] ์ƒ์„ฑ๋œ ์ œ์•ˆ์˜ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ (์ตœ์†Œ ๊ธธ์ด 50์ž ์ด์ƒ์ผ ๋•Œ๋งŒ append) + const enrichedReport = proactiveAdvice && proactiveAdvice.length > 50 + ? `${finalReport}\n\n---\n## ๐Ÿ’ก Astra์˜ ์„ ์ œ์  ์ œ์•ˆ (Proactive Next Actions)\n${proactiveAdvice}` + : finalReport; + const standardizedReport = WikiFormatter.format(enrichedReport, state); - // ์ตœ์ข… ๊ฒฐ๊ณผ ์ „์—ญ ์บ์‹ฑ (Deduplication) - CacheManager.set(prompt, 'global_final_report', standardizedReport); + // ์ตœ์ข… ๊ฒฐ๊ณผ ์ „์—ญ ์บ์‹ฑ (Deduplication - Secure Key with Model Awareness) + CacheManager.set(prompt, `global_final_report_${promptHash}`, standardizedReport, currentModel); CognitionAudit.auditPolicyCompliance('MissionComplete', standardizedReport); this.transition(state, 'completed', '๋ฏธ์…˜ ์™„๋ฃŒ', onProgress); @@ -664,6 +692,11 @@ export class AgentEngine { case ErrorType.PERMANENT: // ์˜๊ตฌ ์˜ค๋ฅ˜ โ†’ ์žฌ์‹œ๋„ ์—†์ด ์ฆ‰์‹œ ์ค‘๋‹จ, ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€ ์ฒจ๋ถ€ logError(`[AgentEngine] [PERMANENT] ${agentName} ๋ณต๊ตฌ ๋ถˆ๊ฐ€: ${rule.userMessage}`); + + // [Self-Correction] ์—๋Ÿฌ ๋ฐœ์ƒ ์ง์ „์˜ ๋ฐ์ดํ„ฐ ์ƒํƒœ๋ฅผ ์งง๊ฒŒ ์ง„๋‹จ (์•ˆ์ „ํ•œ audit ๋ชจ๋“œ ์‚ฌ์šฉ) + const auditResult = AgentDataValidator.audit(agentName, input.substring(0, 500)); + logInfo(`[AgentEngine] [Pre-Failure Audit] Risk: ${auditResult.conflictRisk} | Issues: ${auditResult.issues.join(', ') || 'None'}`); + const enrichedError = new Error(`[${agentName}] ${rule.userMessage} (์›์ธ: ${error.message})`); (enrichedError as any).originalError = error; (enrichedError as any).errorType = ErrorType.PERMANENT; @@ -676,9 +709,19 @@ export class AgentEngine { if (transientRule.action === 'fallback' || options?.config?.allowFallback) { logInfo(`[AgentEngine] [FALLBACK] ${agentName} ์žฌ์‹œ๋„ ์†Œ์ง„. ๋Œ€์ฒด ๋ฐ์ดํ„ฐ(Cache/Stale) ๊ฒฝ๋กœ๋ฅผ ๊ฐ€๋™ํ•ฉ๋‹ˆ๋‹ค.`); state.resilienceMetrics.fallbacks++; - const cached = CacheManager.get(input, context); + + // 1. ์บ์‹œ ์‹œ๋„ (ํ˜„์žฌ ํ”„๋กฌํ”„ํŠธ/์ปจํ…์ŠคํŠธ ๋ฐ ๋ชจ๋ธ์— ์ข…์†๋จ) + const currentModel = (options?.config?.model as string) || 'unknown'; + const cached = CacheManager.get(input, context, currentModel); if (cached) return cached; - if (options?.priorResults?.['previousValidData']) return options.priorResults['previousValidData']; + + // 2. [Structural Fix] ํ”Œ๋ž˜๊ทธ ๋Œ€์‹  ์ž…๋ ฅ/์ปจํ…์ŠคํŠธ ํ•ด์‹œ๋ฅผ ์ง์ ‘ ๊ฒ€์ฆํ•˜์—ฌ Fallback ์•ˆ์ „์„ฑ ํ™•๋ณด + const inputHash = createHash('sha256').update(input).digest('hex').slice(0, 16); + const contextHash = createHash('sha256').update(context || '').digest('hex').slice(0, 16); + + if (options?.priorResults?.['previousValidData'] && (state.promptHash === inputHash || state.promptHash === contextHash)) { + return options.priorResults['previousValidData']; + } } logError(`[AgentEngine] [TRANSIENT] ${agentName} ์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜(${transientRule.maxRetries}) ์†Œ์ง„.`); @@ -785,16 +828,24 @@ export class AgentEngine { * ์ˆ˜ํ–‰๋œ ์ž‘์—… ๊ฒฐ๊ณผ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ๋‹ค์Œ ๋‹จ๊ณ„์˜ ์˜์‚ฌ๊ฒฐ์ • ํฌํฌ๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. */ private async generateProactiveAdvice(report: string, originalPrompt: string, context: string, signal: AbortSignal): Promise { - logInfo(`[AgentEngine] ์„ ์ œ์  ์ œ์•ˆ ์ƒ์„ฑ ์ค‘...`); - // ๋ณธ ๋กœ์ง์€ ๋ณ„๋„์˜ ๊ฐ€๋ฒผ์šด ์ถ”๋ก  ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๊ฑฐ๋‚˜, WriterAgent์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜์—ฌ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - // ํ˜„์žฌ๋Š” WriterAgent์—๊ฒŒ ํ•œ ๋ฒˆ ๋” ์งˆ์˜ํ•˜๊ฑฐ๋‚˜, ๋ฆฌํฌํŠธ์—์„œ ์•ก์…˜ ์•„์ดํ…œ์„ ์ถ”์ถœํ•˜๋Š” ๊ตฌ์กฐ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. - const advicePrompt = `๋‹ค์Œ ๋ฆฌํฌํŠธ๋ฅผ ์ฝ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์Œ์— ๋‚ด๋ ค์•ผ ํ•  '์ „๋žต์  ์˜์‚ฌ๊ฒฐ์ •'์ด๋‚˜ '์‹คํ–‰ ์ž‘์—…' 3๊ฐ€์ง€๋ฅผ ์ œ์•ˆํ•ด์ค˜. + // [Structural Fix] ์ ˆ๋‹จ ์—†๋Š” ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ (LLM ์ƒ์ƒ๋ ฅ ์ œํ•œ) + const advicePrompt = `์‚ฌ์šฉ์ž์˜ ์›๋ž˜ ์š”์ฒญ๊ณผ ์ž‘์„ฑ๋œ ์ตœ์ข… ๋ฆฌํฌํŠธ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ, +์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์Œ์— ๋‚ด๋ ค์•ผ ํ•  '์ „๋žต์  ์˜์‚ฌ๊ฒฐ์ •'์ด๋‚˜ '์‹คํ–‰ ์ž‘์—…' 3๊ฐ€์ง€๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ์ œ์•ˆํ•ด์ฃผ์‹ญ์‹œ์˜ค. +์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์‹ค์„ ์ง€์–ด๋‚ด์ง€ ๋ง๊ณ , ๋ฆฌํฌํŠธ์— ๋ช…์‹œ๋œ ๊ทผ๊ฑฐ๋งŒ์„ ํ™œ์šฉํ•˜์‹ญ์‹œ์˜ค. + ์›๋ž˜ ์š”์ฒญ: ${originalPrompt} -๋ฆฌํฌํŠธ ์š”์•ฝ: ${report.substring(0, 1000)}...`; +๋ฆฌํฌํŠธ ๋‚ด์šฉ: +${report}`; try { - // WriterAgent๋ฅผ Advisor ๋ชจ๋“œ๋กœ ์žฌํ™œ์šฉ - return await this.writer.execute(advicePrompt, context, signal, { config: { role: 'advisor' } }); + // Advisor ์ „์šฉ ์„ค์ •์„ ์ฃผ์ž…ํ•˜์—ฌ ์—ญํ•  ํ˜ผ์šฉ ๋ฐฉ์ง€ + return await this.writer.execute(advicePrompt, context, signal, { + config: { + role: 'advisor', + temperature: 0.1, // ์ฐฝ์˜์„ฑ ์–ต์ œ, ์‚ฌ์‹ค์„ฑ ๊ฐ•ํ™” + maxTokens: 500 + } + }); } catch (err) { return "๋‹ค์Œ ๋‹จ๊ณ„์— ๋Œ€ํ•œ ์ž๋™ ์ œ์•ˆ์„ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌํฌํŠธ์˜ ๊ฒฐ๋ก  ์„น์…˜์„ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”."; } diff --git a/src/lib/formatter.ts b/src/lib/formatter.ts index 36a9c1c..6b5301a 100644 --- a/src/lib/formatter.ts +++ b/src/lib/formatter.ts @@ -26,10 +26,17 @@ export class WikiFormatter { let cleanContent = content.replace(/^---[\s\S]*?---\n*/, ''); let formatted = frontmatter + cleanContent; - // 2. ํ•„์ˆ˜ ํ—ค๋” ๋ณด์ • + // 2. ์ง€๋Šฅํ˜• ์š”์•ฝ ์ถ”์ถœ ๋ฐ ๋ณด์ • if (!formatted.includes('## ๐Ÿ“Œ Brief Summary')) { - const summary = cleanContent.split('\n').find(l => l.trim().length > 10) || '์š”์•ฝ์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.'; - const summarySection = `## ๐Ÿ“Œ Brief Summary\n${summary.substring(0, 200)}...\n\n`; + const lines = cleanContent.split('\n'); + // '๐Ÿ“Œ', '์š”์•ฝ', 'Summary'๊ฐ€ ํฌํ•จ๋œ ์ค„์„ ๋จผ์ € ์ฐพ๊ฑฐ๋‚˜, 20์ž ์ด์ƒ์˜ ์ฒซ ๋‹จ๋ฝ์„ ์ฐพ์Œ + let summary = lines.find(l => l.includes('๐Ÿ“Œ') || l.includes('Summary') || l.includes('์š”์•ฝ'))?.replace(/^[#\s๐Ÿ“Œ]*/, ''); + + if (!summary) { + summary = lines.find(l => l.trim().length > 30 && !l.startsWith('#')) || '์š”์•ฝ ๋‚ด์šฉ์„ ์ถ”์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.'; + } + + const summarySection = `## ๐Ÿ“Œ Brief Summary\n${summary.substring(0, 300)}${summary.length > 300 ? '...' : ''}\n\n`; formatted = formatted.replace(/---\n\n/, `---\n\n${summarySection}`); } @@ -59,7 +66,11 @@ export class WikiFormatter { `| **Processing Time** | \`${(totalDuration / 1000).toFixed(1)}s\` | โœ… Fast |`, '', '### ๐Ÿ” Decision Audit Trail', - state.auditTrail.map(a => `- **[${a.to.toUpperCase()}]** ${a.message} (${a.durationFromPrev}ms)`).join('\n'), + // ์žฌ์‹œ๋„(retry) ๋“ฑ ๋ฐ˜๋ณต์ ์ธ ๋กœ๊ทธ๋Š” ์ œ์™ธํ•˜๊ณ  ํ•ต์‹ฌ ๋‹จ๊ณ„ ์ „ํ™˜๋งŒ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์ถœ๋ ฅ + state.auditTrail + .filter(a => !a.message.includes('์žฌ์‹œ๋„') && a.from !== a.to) + .map(a => `- **[${a.to.toUpperCase()}]** ${a.message} (${a.durationFromPrev}ms)`) + .join('\n'), '' ].join('\n'); diff --git a/src/retrieval/contextBudget.ts b/src/retrieval/contextBudget.ts index 2f3b71e..3aae427 100644 --- a/src/retrieval/contextBudget.ts +++ b/src/retrieval/contextBudget.ts @@ -52,12 +52,16 @@ export function selectWithinBudget( // 1. Sort by score descending const sorted = [...chunks].sort((a, b) => b.score - a.score); - // 2. Deduplicate by filePath - const seen = new Set(); - const deduped = sorted.filter((chunk) => { + // 2. [Structural Fix] ํŒŒ์ผ๋‹น ์ฒญํฌ ์ˆ˜ ์ œํ•œ ์™„ํ™” (Deduplication -> Multi-context) + const fileChunkCounts = new Map(); + const filtered = sorted.filter((chunk) => { const key = chunk.metadata.filePath || chunk.id; - if (seen.has(key)) return false; - seen.add(key); + const count = fileChunkCounts.get(key) || 0; + + // ํŒŒ์ผ๋‹น ์ตœ๋Œ€ 3๊ฐœ๊นŒ์ง€์˜ ์ฃผ์š” ๋งฅ๋ฝ ํ—ˆ์šฉ (์ •๋ณด ์œ ์‹ค ๋ฐฉ์ง€) + if (count >= 3) return false; + + fileChunkCounts.set(key, count + 1); return true; }); @@ -66,7 +70,7 @@ export function selectWithinBudget( const dropped: RetrievalChunk[] = []; let tokensUsed = 0; - for (const chunk of deduped) { + for (const chunk of filtered) { const chunkTokens = chunk.tokenEstimate || estimateTokens(chunk.content); if (selected.length >= cfg.maxChunks) { diff --git a/src/retrieval/scoring.ts b/src/retrieval/scoring.ts index 786adb0..48a8088 100644 --- a/src/retrieval/scoring.ts +++ b/src/retrieval/scoring.ts @@ -65,14 +65,12 @@ const SCORING_CONFIG = { // โ”€โ”€โ”€ Global Search State & Cache โ”€โ”€โ”€ const TOKEN_CACHE = new Map(); -const IDF_CACHE = new Map(); /** * ์บ์‹œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋น„์›๋‹ˆ๋‹ค. ๋ฌธ์„œ ์ง‘ํ•ฉ์ด ํฌ๊ฒŒ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. */ export function clearScoringCache() { TOKEN_CACHE.clear(); - IDF_CACHE.clear(); } /** @@ -85,16 +83,18 @@ export function tokenize(text: string): string[] { const normalized = text .toLowerCase() .replace(/[\u200B-\u200D\uFEFF]/g, '') - .replace(/[^\w\s๊ฐ€-ํžฃ_.-]/g, ' '); + .replace(/[^\w\s๊ฐ€-ํžฃ_+#.-]/g, ' '); // [Refinement] ์˜๋ฌธ/์ˆซ์ž์™€ ํ•œ๊ธ€ ๊ฒฝ๊ณ„์—์„œ ๋ถ„๋ฆฌํ•˜๋„๋ก ๊ฐœ์„  const splitText = normalized.replace(/([a-z0-9]+)([๊ฐ€-ํžฃ]+)/gi, '$1 $2').replace(/([๊ฐ€-ํžฃ]+)([a-z0-9]+)/gi, '$1 $2'); const tokens = splitText - .split(/[^a-z0-9๊ฐ€-ํžฃ]+/g) - .map((t) => t.trim()) + .split(/[^a-z0-9๊ฐ€-ํžฃ+#.-]+/g) // [Structural Fix] C++, C#, .net ๋“ฑ ํŠน์ˆ˜ ๊ธฐํ˜ธ ๋ณด์กด + .map((t) => t.trim().replace(/[.,]$/g, '')) // [Refinement] ๋ฌธ์žฅ ๋ ๋งˆ์นจํ‘œ/์‰ผํ‘œ ์ œ๊ฑฐ .filter((t) => { if (!t) return false; + // ํŠน์ˆ˜๋ฌธ์ž๋งŒ ๋‚จ์€ ํ† ํฐ ์ œ๊ฑฐ (๋‹จ์ผ + ๋‚˜ . ๋“ฑ) + if (/^[+#.-]+$/.test(t)) return false; // ํ•œ๊ธ€์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ ํ•œ ๊ธ€์ž๋ผ๋„ ํ—ˆ์šฉ, ๊ทธ ์™ธ(์˜๋ฌธ/์ˆซ์ž)๋Š” 2๊ธ€์ž ์ด์ƒ if (/[๊ฐ€-ํžฃ]/.test(t)) return t.length >= 1; return t.length >= 2; @@ -110,29 +110,9 @@ const synonymMap = new Map(SCORING_CONFIG.SYNONYM_DATA); /** * ๋™์˜์–ด/๊ด€๋ จ์–ด ํ™•์žฅ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. + * SCORING_CONFIG์˜ ์ค‘์•™ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. */ export function expandQuery(tokens: string[]): string[] { - const synonymMap = new Map([ - ['์„ฑ๋Šฅ', ['performance', 'optimization', '์ตœ์ ํ™”', 'speed']], - ['performance', ['์„ฑ๋Šฅ', '์ตœ์ ํ™”', 'optimization', 'speed']], - ['์•„ํ‚คํ…์ฒ˜', ['architecture', '๊ตฌ์กฐ', 'structure', 'design']], - ['architecture', ['์•„ํ‚คํ…์ฒ˜', '๊ตฌ์กฐ', 'structure', 'design']], - ['๋ฉ”๋ชจ๋ฆฌ', ['memory', '๊ธฐ์–ต', 'cache', 'storage']], - ['memory', ['๋ฉ”๋ชจ๋ฆฌ', '๊ธฐ์–ต', 'cache', 'storage']], - ['๋ฒ„๊ทธ', ['bug', 'error', '์˜ค๋ฅ˜', 'issue', 'defect']], - ['bug', ['๋ฒ„๊ทธ', 'error', '์˜ค๋ฅ˜', 'issue']], - ['์„ค๊ณ„', ['design', '์•„ํ‚คํ…์ฒ˜', 'architecture', 'pattern']], - ['design', ['์„ค๊ณ„', '์•„ํ‚คํ…์ฒ˜', 'architecture', 'pattern']], - ['๋ฐฐํฌ', ['deploy', 'deployment', 'release', 'ci', 'cd']], - ['deploy', ['๋ฐฐํฌ', 'deployment', 'release']], - ['ํ…Œ์ŠคํŠธ', ['test', 'testing', 'spec', 'jest', 'mocha']], - ['test', ['ํ…Œ์ŠคํŠธ', 'testing', 'spec']], - ['ํ”„๋กœ์ ํŠธ', ['project', 'ํ”„๋กœ๊ทธ๋žจ', 'repo', 'repository']], - ['project', ['ํ”„๋กœ์ ํŠธ', 'ํ”„๋กœ๊ทธ๋žจ', 'repo']], - ['๋ฐฉํ–ฅ', ['direction', '์ „๋žต', 'strategy', '๋ชฉํ‘œ', 'goal']], - ['direction', ['๋ฐฉํ–ฅ', '์ „๋žต', 'strategy', '๋ชฉํ‘œ']] - ]); - const expanded = new Set(tokens); for (const token of tokens) { const synonyms = synonymMap.get(token); @@ -213,7 +193,7 @@ export function scoreTfIdf( // Expand query with synonyms const expandedQuery = expandQuery(queryTokens); - // Compute IDF for each query term + // Compute IDF for each query term (Local cache per document set) const idfCache = new Map(); for (const term of expandedQuery) { if (!idfCache.has(term)) { @@ -271,15 +251,28 @@ export function scoreTfIdf( // Title match bonus for exact query term presence const titleBoost = queryTokens.some((t) => titleTokens.has(t)) ? 0.2 : 0; + // [Structural Fix] Conflict Penalty ๋ฐ ์Œ์ˆ˜ ์ ์ˆ˜ ๋ฐฉ์ง€ (Floor Zero ์ •์ฑ…) + const conflictPenalty = conflictSeverity === 'HIGH' ? -1.0 + : conflictSeverity === 'MEDIUM' ? -0.5 + : conflictSeverity === 'LOW' ? -0.2 + : 0; + + const finalScore = Math.max(0, score + recencyBoost + titleBoost + conflictPenalty); + + // [Structural Fix] Information Density: ์ฟผ๋ฆฌ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ณ„์‚ฐ ๋ฐฉ์‹ ์ •์ƒํ™” + const queryCoverage = expandedQuery.length > 0 + ? new Set(matchedTerms).size / expandedQuery.length + : 0; + return { index, - score: score + recencyBoost + titleBoost, + score: finalScore, titleBoost, recencyBoost, matchedTerms: [...new Set(matchedTerms)], conflictDetected, conflictSeverity, - informationDensity + informationDensity: queryCoverage // ๋ฐ€๋„๋ฅผ ์ฟผ๋ฆฌ ์ปค๋ฒ„๋ฆฌ์ง€๋กœ ๋Œ€์ฒด }; }); } @@ -343,6 +336,17 @@ export function extractBestExcerpt( } } + // [Structural Fix] ์ž„๊ณ„๊ฐ’์„ ์ถฉ์กฑํ•˜๋Š” ์œˆ๋„์šฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ Fallback (๋นˆ ์ปจํ…์ŠคํŠธ ๋ฐฉ์ง€) + if (bestScore <= 0) { + const fallbackSentences = [...scored] // [Structural Fix] ์›๋ณธ ๋ฐฐ์—ด ๋ณ€์ด ๋ฐฉ์ง€ (Shallow Copy) + .sort((a, b) => b.score - a.score) + .slice(0, 2) // ๊ฐ€์žฅ ๊ด€๋ จ์„ฑ ๋†’์€ ๋ฌธ์žฅ 2๊ฐœ๋งŒ ์ถ”์ถœ + .map((s) => s.sentence); + + const fallbackResult = fallbackSentences.join(' '); + return fallbackResult.length > maxLength ? fallbackResult.slice(0, maxLength - 3) + '...' : fallbackResult; + } + // 4. Result construction with semantic context padding let finalStart = bestStart; let finalEnd = bestStart + bestLen; diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index 86ba19c..86b3c7b 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -97,4 +97,33 @@ export interface IHttpService { * HTTP POST ์š”์ฒญ */ post(url: string, data: any, options?: any): Promise; +} + +// โ”€โ”€โ”€ ๊ณต์šฉ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํƒ€์ž… (Unified Error Handling) โ”€โ”€โ”€ + +/** + * ์˜ค๋ฅ˜ ์œ ํ˜• ๋ถ„๋ฅ˜. + */ +export enum ErrorType { + /** ๋„คํŠธ์›Œํฌ ํƒ€์ž„์•„์›ƒ, API ์‘๋‹ต ์ง€์—ฐ ๋“ฑ ์žฌ์‹œ๋„๋กœ ๋ณต๊ตฌ ๊ฐ€๋Šฅํ•œ ์˜ค๋ฅ˜ */ + TRANSIENT = 'TRANSIENT', + /** ํ”„๋กฌํ”„ํŠธ ๊ตฌ์กฐ ๋ฌธ์ œ, ๋ชจ๋ธ ๋ช…๋ฐฑํ•œ ์‹คํŒจ ๋“ฑ ์ˆ˜๋™ ๊ฐœ์ž…์ด ํ•„์š”ํ•œ ์˜ค๋ฅ˜ */ + PERMANENT = 'PERMANENT', + /** ์ธ์ฆ ์‹คํŒจ (API Key ๋งŒ๋ฃŒ ๋“ฑ) */ + AUTH_FAILURE = 'AUTH_FAILURE', + /** ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„์ ์œผ๋กœ ์ž‘์—…์„ ์ทจ์†Œํ•œ ๊ฒฝ์šฐ */ + ABORT = 'ABORT' +} + +/** + * ์˜ค๋ฅ˜ ๋ณต๊ตฌ ๊ทœ์น™ ์ •์˜. + */ +export interface RecoveryRule { + type: ErrorType; + description: string; + maxRetries: number; + backoffBaseMs: number; + action: 'retry' | 'abort' | 'fail_with_message' | 'fallback'; + userMessage: string; + fallbackDescription?: string; } \ No newline at end of file diff --git a/tests/agentEngine.test.ts b/tests/agentEngine.test.ts index 65c7ecc..20c6695 100644 --- a/tests/agentEngine.test.ts +++ b/tests/agentEngine.test.ts @@ -21,6 +21,7 @@ import { } from '../src/lib/engine'; import * as fs from 'fs'; import * as path from 'path'; +import { createHash } from 'crypto'; // โ”€โ”€โ”€ Setup โ”€โ”€โ”€ const getBaseDir = () => { @@ -622,12 +623,12 @@ describe('Concurrency & Stress Tests', () => { // resilientExecute์˜ fallback ๋กœ์ง์ด allowFallback ์˜ต์…˜์— ๋ฐ˜์‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ const options: AgentExecuteOptions = { - config: { allowFallback: true }, + config: { allowFallback: true, isSamePrompt: true }, priorResults: { previousValidData: expectedFallback } }; const result = await (engine as any).resilientExecute( - new MissionState('fallback_test'), + new MissionState('fallback_test', createHash('sha256').update(testPrompt).digest('hex').slice(0, 16)), failingAgent, 'FailingAgent', testPrompt, diff --git a/tests/resilience_v4.test.ts b/tests/resilience_v4.test.ts new file mode 100644 index 0000000..8e0640f --- /dev/null +++ b/tests/resilience_v4.test.ts @@ -0,0 +1,84 @@ + +import { + AgentEngine, + IAgent, + ErrorClassifier, + ErrorType, + MissionState +} from '../src/lib/engine'; +import { AgentDataValidator } from '../src/lib/diagnostics'; +import * as path from 'path'; +import * as fs from 'fs'; + +describe('ConnectAI Resilience v4.0 Validation', () => { + + describe('Enhanced Error Classification', () => { + test('GPU OOM (out of memory) should be classified as TRANSIENT', () => { + const error = new Error('Ollama error: GPU out of memory, failed to allocate weights'); + const result = ErrorClassifier.classify(error); + expect(result.type).toBe(ErrorType.TRANSIENT); + expect(result.rule.action).toBe('retry'); + }); + + test('Context length exceeded should be classified as PERMANENT', () => { + const error = new Error('Validation failed: context_length_exceeded for model gemini-pro'); + const result = ErrorClassifier.classify(error); + expect(result.type).toBe(ErrorType.PERMANENT); + expect(result.rule.action).toBe('fail_with_message'); + }); + + test('Safety filter triggers should be classified as PERMANENT', () => { + const error = new Error('Response blocked by safety_filter: harmful content detected'); + const result = ErrorClassifier.classify(error); + expect(result.type).toBe(ErrorType.PERMANENT); + }); + + test('500 Internal Server Error should be classified as TRANSIENT', () => { + const error = new Error('HTTP 500: Internal Server Error'); + const result = ErrorClassifier.classify(error); + expect(result.type).toBe(ErrorType.TRANSIENT); + }); + }); + + describe('Safe Pre-Failure Audit', () => { + class MockPermanentOOMAgent implements IAgent { + async execute(): Promise { + // ์ด ์—๋Ÿฌ๋Š” ํŒจํ„ด์ƒ PERMANENT๋กœ ๋ถ„๋ฅ˜๋˜๋„๋ก ์œ ๋„ (ํ…Œ์ŠคํŠธ์šฉ) + throw new Error('404: model not found'); + } + } + + test('Permanent error should trigger audit without crashing', async () => { + const engine = new AgentEngine( + new MockPermanentOOMAgent(), + {} as IAgent, + {} as IAgent + ); + + const state = new MissionState('audit_test_mission'); + const input = 'This is a test input that should be audited upon failure.'; + + // audit ๋ฉ”์„œ๋“œ๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋˜์ง€์ง€ ์•Š๋Š”์ง€ ํ™•์ธ + const auditResult = AgentDataValidator.audit('Planner', input); + expect(auditResult).toHaveProperty('score'); + expect(auditResult).toHaveProperty('issues'); + + // ์‹ค์ œ resilientExecute ํ๋ฆ„์—์„œ ์—๋Ÿฌ๊ฐ€ ์ „ํŒŒ๋˜๋Š”์ง€ ํ™•์ธ + await expect((engine as any).resilientExecute( + state, + new MockPermanentOOMAgent(), + 'TestAgent', + input, + 'context', + new AbortController().signal, + () => {} + )).rejects.toThrow(/TestAgent/); + }); + + test('Audit should handle empty input gracefully', () => { + const result = AgentDataValidator.audit('Tester', ''); + expect(result.score).toBe(0); + expect(result.issues).toContain('๋ฐ์ดํ„ฐ๊ฐ€ ์™„์ „ํžˆ ๋น„์–ด ์žˆ์Œ'); + }); + }); +}); diff --git a/tests/scoring.test.ts b/tests/scoring.test.ts index 54df796..1146b42 100644 --- a/tests/scoring.test.ts +++ b/tests/scoring.test.ts @@ -77,10 +77,18 @@ describe('Scoring Engine Unit Tests (v2.72.0)', () => { // Language boundary split should handle alternating chars expect(tokens).toContain('astra'); - expect(tokens).toContain('v2'); + expect(tokens).toContain('v2.0'); // [Structural Fix] ์ (.)์ด ํฌํ•จ๋œ ๋ฒ„์ „ ๋ฒˆํ˜ธ ๋ณด์กด ํ™•์ธ expect(tokens).toContain('ํ•œ'); expect(tokens).toContain('๊ธ€'); - // Symbols should be filtered out + + // [New Feature] ๊ธฐ์ˆ  ๊ธฐํ˜ธ ๋ณด์กด ํ™•์ธ (C++, C#, .net) + const techText = 'I love C++ and C# programming on .net platform.'; + const techTokens = tokenize(techText); + expect(techTokens).toContain('c++'); + expect(techTokens).toContain('c#'); + expect(techTokens).toContain('.net'); + + // Symbols should be filtered out (except the preserved ones) expect(tokens.some(t => /^[!@#$]+$/.test(t))).toBe(false); });