aiexecode 1.0.157

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/LICENSE +68 -0
  2. package/README.md +347 -0
  3. package/config_template/mcp_config.json +3 -0
  4. package/config_template/package_name_store.json +5 -0
  5. package/config_template/settings.json +5 -0
  6. package/index.js +879 -0
  7. package/mcp-agent-lib/example/01-basic-usage.js +82 -0
  8. package/mcp-agent-lib/example/02-quick-start.js +52 -0
  9. package/mcp-agent-lib/example/03-http-server.js +76 -0
  10. package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
  11. package/mcp-agent-lib/example/05-error-handling.js +116 -0
  12. package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
  13. package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
  14. package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
  15. package/mcp-agent-lib/example/README.md +346 -0
  16. package/mcp-agent-lib/index.js +19 -0
  17. package/mcp-agent-lib/init.sh +3 -0
  18. package/mcp-agent-lib/package-lock.json +1216 -0
  19. package/mcp-agent-lib/package.json +53 -0
  20. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  21. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  22. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  23. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  24. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  25. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  26. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  27. package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
  28. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
  29. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  30. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  31. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  32. package/mcp-agent-lib/src/mcp_client.js +1860 -0
  33. package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
  34. package/package.json +72 -0
  35. package/payload_viewer/out/404/index.html +1 -0
  36. package/payload_viewer/out/404.html +1 -0
  37. package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
  38. package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
  39. package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
  40. package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
  41. package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
  42. package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
  43. package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
  44. package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
  45. package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  46. package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
  47. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  48. package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
  49. package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
  50. package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
  51. package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
  52. package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
  53. package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
  54. package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
  55. package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
  56. package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
  57. package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  58. package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  59. package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  60. package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  61. package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  62. package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  63. package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
  64. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
  65. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
  66. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
  67. package/payload_viewer/out/favicon.ico +0 -0
  68. package/payload_viewer/out/file.svg +1 -0
  69. package/payload_viewer/out/globe.svg +1 -0
  70. package/payload_viewer/out/index.html +1 -0
  71. package/payload_viewer/out/index.txt +23 -0
  72. package/payload_viewer/out/next.svg +1 -0
  73. package/payload_viewer/out/vercel.svg +1 -0
  74. package/payload_viewer/out/window.svg +1 -0
  75. package/payload_viewer/web_server.js +861 -0
  76. package/prompts/completion_judge.txt +128 -0
  77. package/prompts/orchestrator.txt +1213 -0
  78. package/src/LLMClient/client.js +1375 -0
  79. package/src/LLMClient/converters/input-normalizer.js +238 -0
  80. package/src/LLMClient/converters/responses-to-claude.js +503 -0
  81. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  82. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  83. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  84. package/src/LLMClient/errors.js +398 -0
  85. package/src/LLMClient/index.js +36 -0
  86. package/src/ai_based/completion_judge.js +421 -0
  87. package/src/ai_based/orchestrator.js +527 -0
  88. package/src/ai_based/pip_package_installer.js +173 -0
  89. package/src/ai_based/pip_package_lookup.js +197 -0
  90. package/src/cli/mcp_cli.js +70 -0
  91. package/src/cli/mcp_commands.js +255 -0
  92. package/src/commands/agents.js +18 -0
  93. package/src/commands/apikey.js +55 -0
  94. package/src/commands/bg.js +140 -0
  95. package/src/commands/commands.js +56 -0
  96. package/src/commands/debug.js +54 -0
  97. package/src/commands/exit.js +19 -0
  98. package/src/commands/help.js +35 -0
  99. package/src/commands/mcp.js +128 -0
  100. package/src/commands/model.js +176 -0
  101. package/src/commands/setup.js +13 -0
  102. package/src/commands/skills.js +51 -0
  103. package/src/commands/tools.js +165 -0
  104. package/src/commands/viewer.js +147 -0
  105. package/src/config/ai_models.js +312 -0
  106. package/src/config/config.js +10 -0
  107. package/src/config/constants.js +71 -0
  108. package/src/config/feature_flags.js +15 -0
  109. package/src/frontend/App.js +1263 -0
  110. package/src/frontend/README.md +81 -0
  111. package/src/frontend/components/AutocompleteMenu.js +47 -0
  112. package/src/frontend/components/BackgroundProcessList.js +175 -0
  113. package/src/frontend/components/BlankLine.js +62 -0
  114. package/src/frontend/components/ConversationItem.js +893 -0
  115. package/src/frontend/components/CurrentModelView.js +43 -0
  116. package/src/frontend/components/FileDiffViewer.js +616 -0
  117. package/src/frontend/components/Footer.js +25 -0
  118. package/src/frontend/components/Header.js +42 -0
  119. package/src/frontend/components/HelpView.js +154 -0
  120. package/src/frontend/components/Input.js +344 -0
  121. package/src/frontend/components/LoadingIndicator.js +31 -0
  122. package/src/frontend/components/ModelListView.js +49 -0
  123. package/src/frontend/components/ModelUpdatedView.js +22 -0
  124. package/src/frontend/components/SessionSpinner.js +66 -0
  125. package/src/frontend/components/SetupWizard.js +242 -0
  126. package/src/frontend/components/StreamOutput.js +34 -0
  127. package/src/frontend/components/TodoList.js +56 -0
  128. package/src/frontend/components/ToolApprovalPrompt.js +452 -0
  129. package/src/frontend/design/themeColors.js +42 -0
  130. package/src/frontend/hooks/useCompletion.js +84 -0
  131. package/src/frontend/hooks/useFileCompletion.js +467 -0
  132. package/src/frontend/hooks/useKeypress.js +145 -0
  133. package/src/frontend/index.js +65 -0
  134. package/src/frontend/utils/GridRenderer.js +140 -0
  135. package/src/frontend/utils/InlineFormatter.js +156 -0
  136. package/src/frontend/utils/diffUtils.js +235 -0
  137. package/src/frontend/utils/inputBuffer.js +441 -0
  138. package/src/frontend/utils/markdownParser.js +377 -0
  139. package/src/frontend/utils/outputRedirector.js +47 -0
  140. package/src/frontend/utils/renderInkComponent.js +42 -0
  141. package/src/frontend/utils/syntaxHighlighter.js +149 -0
  142. package/src/frontend/utils/toolUIFormatter.js +261 -0
  143. package/src/system/agents_loader.js +170 -0
  144. package/src/system/ai_request.js +737 -0
  145. package/src/system/background_process.js +317 -0
  146. package/src/system/code_executer.js +1233 -0
  147. package/src/system/command_loader.js +40 -0
  148. package/src/system/command_parser.js +133 -0
  149. package/src/system/conversation_state.js +265 -0
  150. package/src/system/conversation_trimmer.js +265 -0
  151. package/src/system/custom_command_loader.js +395 -0
  152. package/src/system/file_integrity.js +466 -0
  153. package/src/system/import_analyzer.py +174 -0
  154. package/src/system/log.js +82 -0
  155. package/src/system/mcp_integration.js +304 -0
  156. package/src/system/output_helper.js +89 -0
  157. package/src/system/session.js +1393 -0
  158. package/src/system/session_memory.js +481 -0
  159. package/src/system/skill_loader.js +324 -0
  160. package/src/system/system_info.js +483 -0
  161. package/src/system/tool_approval.js +160 -0
  162. package/src/system/tool_registry.js +184 -0
  163. package/src/system/ui_events.js +279 -0
  164. package/src/tools/code_editor.js +792 -0
  165. package/src/tools/file_reader.js +385 -0
  166. package/src/tools/glob.js +263 -0
  167. package/src/tools/response_message.js +30 -0
  168. package/src/tools/ripgrep.js +554 -0
  169. package/src/tools/skill_tool.js +122 -0
  170. package/src/tools/todo_write.js +182 -0
  171. package/src/tools/web_download.py +74 -0
  172. package/src/tools/web_downloader.js +83 -0
  173. package/src/util/clone.js +174 -0
  174. package/src/util/config.js +203 -0
  175. package/src/util/config_migration.js +174 -0
  176. package/src/util/debug_log.js +49 -0
  177. package/src/util/exit_handler.js +53 -0
  178. package/src/util/file_reference_parser.js +132 -0
  179. package/src/util/mcp_config_manager.js +159 -0
  180. package/src/util/output_formatter.js +50 -0
  181. package/src/util/path_helper.js +27 -0
  182. package/src/util/path_validator.js +178 -0
  183. package/src/util/prompt_loader.js +184 -0
  184. package/src/util/rag_helper.js +101 -0
  185. package/src/util/safe_fs.js +645 -0
  186. package/src/util/setup_wizard.js +62 -0
  187. package/src/util/text_formatter.js +33 -0
  188. package/src/util/version_check.js +116 -0
@@ -0,0 +1,466 @@
1
+ // 이 파일은 OpenCode의 FileTime 추적 시스템을 AgentStudy에 맞게 구현합니다.
2
+ // 파일 편집 전 무결성을 검증하여 안전한 코드 편집을 보장합니다.
3
+
4
+ import { safeReadFile, safeMkdir, safeAppendFile } from '../util/safe_fs.js';
5
+ import crypto from 'crypto';
6
+ import { dirname, join } from 'path';
7
+ import { createDebugLogger } from '../util/debug_log.js';
8
+ import { DEBUG_LOG_DIR, isDebugModeEnabled } from '../util/config.js';
9
+
10
+ const debugLog = createDebugLogger('file_integrity.log', 'file_integrity');
11
+
12
+ // LOG_FILE을 함수로 만들어 lazy initialization
13
+ function getLogFile() {
14
+ return join(DEBUG_LOG_DIR, 'file_integrity_internal.log');
15
+ }
16
+ /**
17
+ * 파일 무결성 추적 시스템
18
+ * 각 세션별로 파일 콘텐츠 해시를 추적하고, 편집 전 파일 변경 여부를 검증합니다.
19
+ */
20
+ class FileIntegrityTracker {
21
+ constructor() {
22
+ // 세션별 파일 콘텐츠 해시 추적
23
+ this.contentHashes = new Map(); // Map<sessionID, Map<filePath, string>>
24
+ // 세션별 파일 스냅샷 저장 (edit_file_range용)
25
+ this.fileSnapshots = new Map(); // Map<sessionID, Map<filePath, {content: string, timestamp: number}>>
26
+ // 현재 활성 세션 ID
27
+ this.currentSessionID = null;
28
+ }
29
+
30
+ /**
31
+ * 현재 세션 ID를 설정합니다
32
+ * @param {string} sessionID - 세션 ID
33
+ */
34
+ setCurrentSession(sessionID) {
35
+ this.currentSessionID = sessionID;
36
+ debugLog(`========== SESSION CHANGED ==========`);
37
+ debugLog(`New session ID: ${sessionID}`);
38
+ debugLog(`[FileIntegrity] Current session set to: ${sessionID}`);
39
+ }
40
+
41
+ /**
42
+ * 현재 세션 ID를 가져옵니다
43
+ * @returns {string|null} 현재 세션 ID
44
+ */
45
+ getCurrentSession() {
46
+ return this.currentSessionID;
47
+ }
48
+
49
+ /**
50
+ * 파일 읽기를 기록합니다 (콘텐츠 해시 저장)
51
+ * @param {string} sessionID - 세션 ID
52
+ * @param {string} filePath - 파일 경로
53
+ * @param {string|Buffer} content - 파일 콘텐츠
54
+ */
55
+ async trackRead(sessionID, filePath, content) {
56
+ const timestamp = new Date().toISOString();
57
+ const internalDebugLog = [];
58
+
59
+ debugLog(`========== trackRead START ==========`);
60
+ debugLog(`sessionID: ${sessionID}`);
61
+ debugLog(`filePath: ${filePath}`);
62
+ debugLog(` - filePath type: ${typeof filePath}`);
63
+ debugLog(` - filePath length: ${filePath?.length || 0}`);
64
+ debugLog(` - filePath starts with '/': ${filePath?.startsWith('/') || false}`);
65
+ debugLog(` - filePath is absolute path: ${filePath?.startsWith('/') || false}`);
66
+ debugLog(` - Current Working Directory: ${process.cwd()}`);
67
+ debugLog(`content size: ${typeof content === 'string' ? content.length : content?.length || 0} bytes`);
68
+
69
+ internalDebugLog.push(`[${timestamp}] trackRead called`);
70
+ internalDebugLog.push(`[${timestamp}] sessionID: ${sessionID}`);
71
+ internalDebugLog.push(`[${timestamp}] filePath: ${filePath}`);
72
+ internalDebugLog.push(`[${timestamp}] filePath type: ${typeof filePath}`);
73
+ internalDebugLog.push(`[${timestamp}] filePath is absolute: ${filePath?.startsWith('/') || false}`);
74
+ internalDebugLog.push(`[${timestamp}] content size: ${typeof content === 'string' ? content.length : content?.length || 0} bytes`);
75
+
76
+ if (!this.contentHashes.has(sessionID)) {
77
+ this.contentHashes.set(sessionID, new Map());
78
+ debugLog(`Created new session map for session: ${sessionID}`);
79
+ internalDebugLog.push(`[${timestamp}] Created new session map`);
80
+ }
81
+
82
+ const hash = crypto.createHash('sha256')
83
+ .update(content, typeof content === 'string' ? 'utf8' : undefined)
84
+ .digest('hex');
85
+
86
+ debugLog(`Computed hash: ${hash.slice(0, 16)}...`);
87
+ internalDebugLog.push(`[${timestamp}] Hash: ${hash.slice(0, 16)}...`);
88
+
89
+ const sessionFiles = this.contentHashes.get(sessionID);
90
+ sessionFiles.set(filePath, hash);
91
+
92
+ debugLog(`Hash stored in session map`);
93
+ debugLog(`Total files tracked in this session: ${sessionFiles.size}`);
94
+ internalDebugLog.push(`[${timestamp}] Hash stored successfully`);
95
+ if (isDebugModeEnabled()) {
96
+ const logFile = getLogFile();
97
+ await safeMkdir(dirname(logFile), { recursive: true }).catch(() => {});
98
+ await safeAppendFile(logFile, internalDebugLog.join('\n') + '\n').catch(() => {});
99
+ }
100
+
101
+ debugLog(`========== trackRead END ==========`);
102
+ debugLog(`[FileIntegrity] Tracked read: ${sessionID}:${filePath} (hash: ${hash.slice(0, 8)}...)`);
103
+ }
104
+
105
+ /**
106
+ * 파일 콘텐츠 해시를 조회합니다
107
+ * @param {string} sessionID - 세션 ID
108
+ * @param {string} filePath - 파일 경로
109
+ * @returns {string|null} 저장된 콘텐츠 해시
110
+ */
111
+ getContentHash(sessionID, filePath) {
112
+ const sessionFiles = this.contentHashes.get(sessionID);
113
+ if (!sessionFiles) return null;
114
+ return sessionFiles.get(filePath) || null;
115
+ }
116
+
117
+ /**
118
+ * 파일 콘텐츠 해시를 삭제합니다
119
+ * 대화가 trim되어 에이전트가 파일 내용을 알 수 없게 되었을 때 호출됩니다.
120
+ * 이렇게 하면 에이전트가 파일을 다시 읽기 전까지 편집할 수 없습니다.
121
+ * @param {string} sessionID - 세션 ID
122
+ * @param {string} filePath - 파일 경로
123
+ * @returns {boolean} 삭제 성공 여부
124
+ */
125
+ clearContentHash(sessionID, filePath) {
126
+ debugLog(`========== clearContentHash START ==========`);
127
+ debugLog(`sessionID: ${sessionID}`);
128
+ debugLog(`filePath: ${filePath}`);
129
+
130
+ const sessionFiles = this.contentHashes.get(sessionID);
131
+ if (!sessionFiles) {
132
+ debugLog(`No session map found for session: ${sessionID}`);
133
+ debugLog(`========== clearContentHash END (NO SESSION) ==========`);
134
+ return false;
135
+ }
136
+
137
+ const existed = sessionFiles.has(filePath);
138
+ if (existed) {
139
+ sessionFiles.delete(filePath);
140
+ debugLog(`Hash deleted for file: ${filePath}`);
141
+ } else {
142
+ debugLog(`No hash found for file: ${filePath}`);
143
+ }
144
+
145
+ debugLog(`========== clearContentHash END ==========`);
146
+ return existed;
147
+ }
148
+
149
+ /**
150
+ * 파일 편집 전 무결성을 검증합니다
151
+ * @param {string} sessionID - 세션 ID
152
+ * @param {string} filePath - 파일 경로
153
+ * @throws {Error} 파일을 읽지 않았거나 변경된 경우
154
+ */
155
+ async assertIntegrity(sessionID, filePath) {
156
+ const timestamp = new Date().toISOString();
157
+ const assertDebugLog = [];
158
+
159
+ debugLog(`========== assertIntegrity START ==========`);
160
+ debugLog(`sessionID: ${sessionID}`);
161
+ debugLog(`filePath: ${filePath}`);
162
+ debugLog(` - filePath type: ${typeof filePath}`);
163
+ debugLog(` - filePath length: ${filePath?.length || 0}`);
164
+ debugLog(` - filePath starts with '/': ${filePath?.startsWith('/') || false}`);
165
+ debugLog(` - filePath is absolute path: ${filePath?.startsWith('/') || false}`);
166
+ debugLog(` - Current Working Directory: ${process.cwd()}`);
167
+
168
+ assertDebugLog.push(`[${timestamp}] assertIntegrity called`);
169
+ assertDebugLog.push(`[${timestamp}] sessionID: ${sessionID}`);
170
+ assertDebugLog.push(`[${timestamp}] filePath: ${filePath}`);
171
+ assertDebugLog.push(`[${timestamp}] filePath type: ${typeof filePath}`);
172
+ assertDebugLog.push(`[${timestamp}] filePath is absolute: ${filePath?.startsWith('/') || false}`);
173
+
174
+ const savedHash = this.getContentHash(sessionID, filePath);
175
+ assertDebugLog.push(`[${timestamp}] savedHash: ${savedHash || 'null'}`);
176
+ debugLog(`Saved hash lookup result: ${savedHash ? 'FOUND' : 'NOT FOUND'}`);
177
+ if (savedHash) {
178
+ debugLog(` - Hash prefix: ${savedHash.slice(0, 16)}...`);
179
+ }
180
+
181
+ if (!savedHash) {
182
+ assertDebugLog.push(`[${timestamp}] ERROR: No saved hash found`);
183
+ debugLog(`ERROR: No saved hash found for this file in session ${sessionID}`);
184
+ debugLog('========== assertIntegrity ERROR END ==========');
185
+ if (isDebugModeEnabled()) {
186
+ const logFile = getLogFile();
187
+ await safeMkdir(dirname(logFile), { recursive: true }).catch(() => {});
188
+ await safeAppendFile(logFile, assertDebugLog.join('\n') + '\n').catch(() => {});
189
+ }
190
+
191
+ throw new Error(
192
+ `You must read the file ${filePath} before editing it. Use a file reading tool first.`
193
+ );
194
+ }
195
+
196
+ if (isDebugModeEnabled()) {
197
+ const logFile = getLogFile();
198
+ await safeMkdir(dirname(logFile), { recursive: true }).catch(() => {});
199
+ await safeAppendFile(logFile, assertDebugLog.join('\n') + '\n').catch(() => {});
200
+ }
201
+
202
+ debugLog(`Reading current file content for comparison...`);
203
+ debugLog(` - Reading from path: ${filePath}`);
204
+ try {
205
+ const currentContent = await safeReadFile(filePath, 'utf8');
206
+ debugLog(`Current file read successful:`);
207
+ debugLog(` - Content length: ${currentContent.length} bytes`);
208
+
209
+ const currentHash = crypto.createHash('sha256')
210
+ .update(currentContent, 'utf8')
211
+ .digest('hex');
212
+ debugLog(`Current hash computed: ${currentHash.slice(0, 16)}...`);
213
+ debugLog(`Hash comparison:`);
214
+ debugLog(` - Saved hash: ${savedHash.slice(0, 16)}...`);
215
+ debugLog(` - Current hash: ${currentHash.slice(0, 16)}...`);
216
+ debugLog(` - Hashes match: ${savedHash === currentHash}`);
217
+
218
+ if (savedHash !== currentHash) {
219
+ debugLog(`ERROR: File has been modified since last read`);
220
+ debugLog('========== assertIntegrity ERROR END ==========');
221
+ throw new Error(
222
+ `File ${filePath} has been modified since it was last read.\n` +
223
+ `Saved hash: ${savedHash.slice(0, 16)}...\n` +
224
+ `Current hash: ${currentHash.slice(0, 16)}...\n\n` +
225
+ `Please read the file again before modifying it.`
226
+ );
227
+ }
228
+
229
+ debugLog(`File integrity verified successfully`);
230
+ debugLog('========== assertIntegrity SUCCESS END ==========');
231
+ } catch (error) {
232
+ if (error.message && (error.message.includes('ENOENT') || error.message.includes('no such file'))) {
233
+ debugLog(`ERROR: File has been deleted since last read`);
234
+ debugLog('========== assertIntegrity ERROR END ==========');
235
+ throw new Error(
236
+ `File ${filePath} has been deleted since it was last read.\n` +
237
+ `Please verify the file exists before modifying it.`
238
+ );
239
+ }
240
+ debugLog(`ERROR: Exception during integrity check: ${error.message}`);
241
+ debugLog('========== assertIntegrity EXCEPTION END ==========');
242
+ throw error;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * 파일 스냅샷을 저장합니다 (edit_file_range 호출 시)
248
+ * @param {string} sessionID - 세션 ID
249
+ * @param {string} filePath - 파일 경로
250
+ * @param {string} content - 파일 원본 내용
251
+ */
252
+ saveSnapshot(sessionID, filePath, content) {
253
+ debugLog(`========== saveSnapshot START ==========`);
254
+ debugLog(`sessionID: ${sessionID}`);
255
+ debugLog(`filePath: ${filePath}`);
256
+ debugLog(` - filePath type: ${typeof filePath}`);
257
+ debugLog(` - filePath length: ${filePath?.length || 0}`);
258
+ debugLog(` - filePath starts with '/': ${filePath?.startsWith('/') || false}`);
259
+ debugLog(` - filePath is absolute path: ${filePath?.startsWith('/') || false}`);
260
+ debugLog(` - Current Working Directory: ${process.cwd()}`);
261
+ debugLog(`content size: ${content.length} bytes`);
262
+
263
+ if (!this.fileSnapshots.has(sessionID)) {
264
+ this.fileSnapshots.set(sessionID, new Map());
265
+ debugLog(`Created new snapshot map for session: ${sessionID}`);
266
+ }
267
+
268
+ const sessionSnapshots = this.fileSnapshots.get(sessionID);
269
+ sessionSnapshots.set(filePath, {
270
+ content,
271
+ timestamp: Date.now()
272
+ });
273
+
274
+ debugLog(`Snapshot stored in session map`);
275
+ debugLog(`Total snapshots in this session: ${sessionSnapshots.size}`);
276
+ debugLog(`========== saveSnapshot END ==========`);
277
+
278
+ debugLog(`[FileIntegrity] Snapshot saved: ${sessionID}:${filePath} (${content.length} bytes)`);
279
+ }
280
+
281
+ /**
282
+ * 파일 스냅샷을 조회합니다
283
+ * @param {string} sessionID - 세션 ID
284
+ * @param {string} filePath - 파일 경로
285
+ * @returns {{content: string, timestamp: number}|null} 저장된 스냅샷
286
+ */
287
+ getSnapshot(sessionID, filePath) {
288
+ debugLog(`========== getSnapshot START ==========`);
289
+ debugLog(`sessionID: ${sessionID}`);
290
+ debugLog(`filePath: ${filePath}`);
291
+ debugLog(` - filePath type: ${typeof filePath}`);
292
+ debugLog(` - filePath length: ${filePath?.length || 0}`);
293
+ debugLog(` - filePath starts with '/': ${filePath?.startsWith('/') || false}`);
294
+ debugLog(` - filePath is absolute path: ${filePath?.startsWith('/') || false}`);
295
+ debugLog(` - Current Working Directory: ${process.cwd()}`);
296
+
297
+ const sessionSnapshots = this.fileSnapshots.get(sessionID);
298
+ if (!sessionSnapshots) {
299
+ debugLog(`No snapshot map found for session: ${sessionID}`);
300
+ debugLog(`========== getSnapshot END (NOT FOUND) ==========`);
301
+ return null;
302
+ }
303
+
304
+ debugLog(`Snapshot map found, has ${sessionSnapshots.size} snapshots`);
305
+ const snapshot = sessionSnapshots.get(filePath) || null;
306
+
307
+ if (snapshot) {
308
+ debugLog(`Snapshot FOUND for file`);
309
+ debugLog(` Content size: ${snapshot.content.length} bytes`);
310
+ debugLog(` Timestamp: ${new Date(snapshot.timestamp).toISOString()}`);
311
+ } else {
312
+ debugLog(`Snapshot NOT FOUND for file`);
313
+ debugLog(`Available snapshots in session:`);
314
+ for (const [path, snap] of sessionSnapshots.entries()) {
315
+ debugLog(` - ${path} (${snap.content.length} bytes)`);
316
+ }
317
+ }
318
+
319
+ debugLog(`========== getSnapshot END ==========`);
320
+ return snapshot;
321
+ }
322
+
323
+ }
324
+
325
+ // 싱글톤 인스턴스
326
+ const fileIntegrityTracker = new FileIntegrityTracker();
327
+
328
+ /**
329
+ * 현재 세션 ID를 설정합니다
330
+ * @param {string} sessionID - 세션 ID
331
+ */
332
+ export function setCurrentSession(sessionID) {
333
+ fileIntegrityTracker.setCurrentSession(sessionID);
334
+ }
335
+
336
+ /**
337
+ * 현재 세션 ID를 가져옵니다
338
+ * @returns {string|null} 현재 세션 ID
339
+ */
340
+ export function getCurrentSession() {
341
+ return fileIntegrityTracker.getCurrentSession();
342
+ }
343
+
344
+ /**
345
+ * 파일 읽기를 추적합니다 (현재 세션 기준)
346
+ * @param {string} filePath - 파일 경로
347
+ * @param {string|Buffer} content - 파일 콘텐츠
348
+ */
349
+ export async function trackFileRead(filePath, content) {
350
+ debugLog(`========== trackFileRead (export wrapper) START ==========`);
351
+ debugLog(`filePath: ${filePath}`);
352
+
353
+ const sessionID = fileIntegrityTracker.getCurrentSession();
354
+ debugLog(`Current session ID: ${sessionID || 'NULL'}`);
355
+
356
+ if (!sessionID) {
357
+ debugLog(`ERROR: No current session set, cannot track file read`);
358
+ debugLog('========== trackFileRead (export wrapper) END ==========');
359
+ debugLog('[FileIntegrity] No current session set, skipping file read tracking');
360
+ return;
361
+ }
362
+
363
+ await fileIntegrityTracker.trackRead(sessionID, filePath, content);
364
+ debugLog('========== trackFileRead (export wrapper) END ==========');
365
+ }
366
+
367
+ /**
368
+ * 파일 편집 전 무결성을 검증합니다 (현재 세션 기준)
369
+ * @param {string} filePath - 파일 경로
370
+ */
371
+ export async function assertFileIntegrity(filePath) {
372
+ debugLog(`========== assertFileIntegrity (export wrapper) START ==========`);
373
+ debugLog(`filePath: ${filePath}`);
374
+
375
+ const sessionID = fileIntegrityTracker.getCurrentSession();
376
+ debugLog(`Current session ID: ${sessionID || 'NULL'}`);
377
+
378
+ if (!sessionID) {
379
+ debugLog(`ERROR: No current session set`);
380
+ debugLog('========== assertFileIntegrity (export wrapper) END ==========');
381
+ throw new Error('[FileIntegrity] No current session set');
382
+ }
383
+
384
+ await fileIntegrityTracker.assertIntegrity(sessionID, filePath);
385
+ debugLog('========== assertFileIntegrity (export wrapper) END ==========');
386
+ }
387
+
388
+ /**
389
+ * 파일 콘텐츠 해시를 삭제합니다 (현재 세션 기준)
390
+ * 대화가 trim되어 에이전트가 파일 내용을 알 수 없게 되었을 때 호출됩니다.
391
+ * @param {string} filePath - 파일 경로
392
+ * @returns {boolean} 삭제 성공 여부
393
+ */
394
+ export function clearFileContentHash(filePath) {
395
+ debugLog(`========== clearFileContentHash (export wrapper) START ==========`);
396
+ debugLog(`filePath: ${filePath}`);
397
+
398
+ const sessionID = fileIntegrityTracker.getCurrentSession();
399
+ debugLog(`Current session ID: ${sessionID || 'NULL'}`);
400
+
401
+ if (!sessionID) {
402
+ debugLog(`ERROR: No current session set, cannot clear hash`);
403
+ debugLog('========== clearFileContentHash (export wrapper) END ==========');
404
+ return false;
405
+ }
406
+
407
+ const result = fileIntegrityTracker.clearContentHash(sessionID, filePath);
408
+ debugLog(`Hash clear result: ${result}`);
409
+ debugLog('========== clearFileContentHash (export wrapper) END ==========');
410
+ return result;
411
+ }
412
+
413
+ /**
414
+ * 파일 스냅샷을 저장합니다 (현재 세션 기준)
415
+ * @param {string} filePath - 파일 경로
416
+ * @param {string} content - 파일 원본 내용
417
+ */
418
+ export function saveFileSnapshot(filePath, content) {
419
+ debugLog(`========== saveFileSnapshot (export wrapper) START ==========`);
420
+ debugLog(`filePath: ${filePath}`);
421
+ debugLog(`content size: ${content?.length || 0} bytes`);
422
+
423
+ const sessionID = fileIntegrityTracker.getCurrentSession();
424
+ debugLog(`Current session ID: ${sessionID || 'NULL'}`);
425
+
426
+ if (!sessionID) {
427
+ debugLog(`ERROR: No current session set, cannot save snapshot`);
428
+ debugLog('========== saveFileSnapshot (export wrapper) END ==========');
429
+ debugLog('[FileIntegrity] No current session set, skipping snapshot save');
430
+ return;
431
+ }
432
+
433
+ fileIntegrityTracker.saveSnapshot(sessionID, filePath, content);
434
+ debugLog('========== saveFileSnapshot (export wrapper) END ==========');
435
+ }
436
+
437
+ /**
438
+ * 파일 스냅샷을 조회합니다 (현재 세션 기준)
439
+ * @param {string} filePath - 파일 경로
440
+ * @returns {{content: string, timestamp: number}|null} 저장된 스냅샷
441
+ */
442
+ export function getFileSnapshot(filePath) {
443
+ debugLog(`========== getFileSnapshot (export wrapper) START ==========`);
444
+ debugLog(`filePath: ${filePath}`);
445
+
446
+ const sessionID = fileIntegrityTracker.getCurrentSession();
447
+ debugLog(`Current session ID: ${sessionID || 'NULL'}`);
448
+
449
+ if (!sessionID) {
450
+ debugLog(`ERROR: No current session set, returning null`);
451
+ debugLog('========== getFileSnapshot (export wrapper) END ==========');
452
+ return null;
453
+ }
454
+
455
+ const snapshot = fileIntegrityTracker.getSnapshot(sessionID, filePath);
456
+ debugLog(`Snapshot result: ${snapshot ? 'FOUND' : 'NOT FOUND'}`);
457
+ if (snapshot) {
458
+ debugLog(` Content size: ${snapshot.content.length} bytes`);
459
+ debugLog(` Timestamp: ${new Date(snapshot.timestamp).toISOString()}`);
460
+ }
461
+ debugLog('========== getFileSnapshot (export wrapper) END ==========');
462
+
463
+ return snapshot;
464
+ }
465
+
466
+ export { fileIntegrityTracker };
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Python 코드 AST 분석기
4
+ - import 구문에서 사용된 모든 모듈/패키지 추출
5
+ - 표준 라이브러리와 외부 패키지 구분
6
+ - JSON 형태로 결과 반환
7
+ """
8
+
9
+ import ast
10
+ import sys
11
+ import json
12
+ import os
13
+ import importlib.util
14
+ import pkgutil
15
+
16
+ class ImportAnalyzer(ast.NodeVisitor):
17
+ def __init__(self):
18
+ self.imports = set()
19
+ self.from_imports = set()
20
+
21
+ def visit_Import(self, node):
22
+ """import module 형태 처리"""
23
+ for alias in node.names:
24
+ self.imports.add(alias.name)
25
+ self.generic_visit(node)
26
+
27
+ def visit_ImportFrom(self, node):
28
+ """from module import ... 형태 처리"""
29
+ if node.module:
30
+ self.from_imports.add(node.module)
31
+ self.generic_visit(node)
32
+
33
+ def get_top_level_package(module_name):
34
+ """모듈명에서 최상위 패키지명 추출"""
35
+ if '.' in module_name:
36
+ return module_name.split('.')[0]
37
+ return module_name
38
+
39
+ def is_standard_library(module_name):
40
+ """표준 라이브러리인지 확인"""
41
+ stdlib_modules = {
42
+ 'os', 'sys', 'json', 'ast', 'collections', 'itertools', 'functools',
43
+ 'datetime', 'time', 're', 'math', 'random', 'hashlib', 'base64',
44
+ 'urllib', 'http', 'socket', 'threading', 'subprocess', 'pathlib',
45
+ 'io', 'csv', 'xml', 'html', 'email', 'logging', 'argparse',
46
+ 'configparser', 'pickle', 'sqlite3', 'uuid', 'copy', 'weakref',
47
+ 'gc', 'traceback', 'warnings', 'typing', 'enum', 'dataclasses',
48
+ 'contextlib', 'operator', 'heapq', 'bisect', 'array', 'struct',
49
+ 'codecs', 'locale', 'gettext', 'calendar', 'sched', 'queue',
50
+ 'threading', 'multiprocessing', 'concurrent', 'asyncio', 'ssl',
51
+ 'ftplib', 'poplib', 'imaplib', 'smtplib', 'telnetlib', 'socketserver',
52
+ 'xmlrpc', 'gzip', 'bz2', 'lzma', 'zipfile', 'tarfile', 'tempfile',
53
+ 'shutil', 'glob', 'fnmatch', 'stat', 'filecmp', 'mmap'
54
+ }
55
+
56
+ top_level = get_top_level_package(module_name)
57
+
58
+ # 표준 라이브러리에 포함된 모듈인지 확인
59
+ if top_level in stdlib_modules:
60
+ return True
61
+
62
+ # 추가 확인: importlib을 사용한 정확한 확인
63
+ try:
64
+ spec = importlib.util.find_spec(top_level)
65
+ if spec and spec.origin:
66
+ # 표준 라이브러리 경로에 있는지 확인
67
+ stdlib_path = os.path.dirname(os.__file__)
68
+ return spec.origin.startswith(stdlib_path)
69
+ except (ImportError, ModuleNotFoundError, ValueError):
70
+ pass
71
+
72
+ return False
73
+
74
+ def analyze_python_code(code_text):
75
+ """Python 코드를 AST로 분석하여 사용된 패키지 추출"""
76
+ try:
77
+ # AST 파싱
78
+ tree = ast.parse(code_text)
79
+
80
+ # Import 분석
81
+ analyzer = ImportAnalyzer()
82
+ analyzer.visit(tree)
83
+
84
+ # 모든 import된 모듈 수집
85
+ all_modules = set()
86
+ all_modules.update(analyzer.imports)
87
+ all_modules.update(analyzer.from_imports)
88
+
89
+ # 최상위 패키지명으로 변환
90
+ top_level_packages = set()
91
+ for module in all_modules:
92
+ top_level = get_top_level_package(module)
93
+ top_level_packages.add(top_level)
94
+
95
+ # 표준 라이브러리와 외부 패키지 구분
96
+ external_packages = []
97
+ standard_modules = []
98
+
99
+ for package in sorted(top_level_packages):
100
+ if is_standard_library(package):
101
+ standard_modules.append(package)
102
+ else:
103
+ external_packages.append(package)
104
+
105
+ return {
106
+ 'success': True,
107
+ 'external_packages': external_packages,
108
+ 'standard_modules': standard_modules,
109
+ 'all_imports': sorted(list(all_modules)),
110
+ 'analysis_summary': {
111
+ 'total_imports': len(all_modules),
112
+ 'external_count': len(external_packages),
113
+ 'standard_count': len(standard_modules)
114
+ }
115
+ }
116
+
117
+ except SyntaxError as e:
118
+ return {
119
+ 'success': False,
120
+ 'error': f'구문 오류: {str(e)}',
121
+ 'error_type': 'SyntaxError',
122
+ 'line_number': getattr(e, 'lineno', None)
123
+ }
124
+ except Exception as e:
125
+ return {
126
+ 'success': False,
127
+ 'error': f'분석 오류: {str(e)}',
128
+ 'error_type': type(e).__name__
129
+ }
130
+
131
+ def main():
132
+ """메인 함수"""
133
+ if len(sys.argv) < 2:
134
+ print(json.dumps({
135
+ 'success': False,
136
+ 'error': '사용법: python ast-analyzer.py <python_file_path>',
137
+ 'error_type': 'ArgumentError'
138
+ }))
139
+ return
140
+
141
+ file_path = sys.argv[1]
142
+
143
+ if not os.path.exists(file_path):
144
+ print(json.dumps({
145
+ 'success': False,
146
+ 'error': f'파일이 존재하지 않습니다: {file_path}',
147
+ 'error_type': 'FileNotFoundError'
148
+ }))
149
+ return
150
+
151
+ try:
152
+ # 파일 읽기
153
+ with open(file_path, 'r', encoding='utf-8') as f:
154
+ code_text = f.read()
155
+
156
+ # AST 분석 실행
157
+ result = analyze_python_code(code_text)
158
+ result['analyzed_file'] = file_path
159
+
160
+ # JSON 형태로 결과 출력
161
+ print(json.dumps(result, ensure_ascii=False, indent=2))
162
+
163
+ except Exception as e:
164
+ print(json.dumps({
165
+ 'success': False,
166
+ 'error': f'파일 처리 오류: {str(e)}',
167
+ 'error_type': type(e).__name__,
168
+ 'analyzed_file': file_path
169
+ }))
170
+
171
+ if __name__ == '__main__':
172
+ main()
173
+
174
+