gencode-ai 0.1.0 → 0.1.2

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 (356) hide show
  1. package/.gencode/settings.local.json +7 -0
  2. package/README.md +20 -102
  3. package/dist/agent/agent.d.ts +43 -2
  4. package/dist/agent/agent.d.ts.map +1 -1
  5. package/dist/agent/agent.js +90 -17
  6. package/dist/agent/agent.js.map +1 -1
  7. package/dist/agent/types.d.ts +9 -1
  8. package/dist/agent/types.d.ts.map +1 -1
  9. package/dist/cli/components/AllModelsSelector.d.ts +11 -0
  10. package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
  11. package/dist/cli/components/AllModelsSelector.js +153 -0
  12. package/dist/cli/components/AllModelsSelector.js.map +1 -0
  13. package/dist/cli/components/App.d.ts +8 -1
  14. package/dist/cli/components/App.d.ts.map +1 -1
  15. package/dist/cli/components/App.js +276 -40
  16. package/dist/cli/components/App.js.map +1 -1
  17. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  18. package/dist/cli/components/CommandSuggestions.js +3 -0
  19. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  20. package/dist/cli/components/Header.d.ts +1 -1
  21. package/dist/cli/components/Header.d.ts.map +1 -1
  22. package/dist/cli/components/Header.js +4 -6
  23. package/dist/cli/components/Header.js.map +1 -1
  24. package/dist/cli/components/Logo.d.ts +1 -0
  25. package/dist/cli/components/Logo.d.ts.map +1 -1
  26. package/dist/cli/components/Logo.js +16 -3
  27. package/dist/cli/components/Logo.js.map +1 -1
  28. package/dist/cli/components/Messages.d.ts +17 -3
  29. package/dist/cli/components/Messages.d.ts.map +1 -1
  30. package/dist/cli/components/Messages.js +70 -18
  31. package/dist/cli/components/Messages.js.map +1 -1
  32. package/dist/cli/components/ModelSelector.d.ts +7 -7
  33. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  34. package/dist/cli/components/ModelSelector.js +116 -33
  35. package/dist/cli/components/ModelSelector.js.map +1 -1
  36. package/dist/cli/components/PermissionPrompt.d.ts +60 -0
  37. package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
  38. package/dist/cli/components/PermissionPrompt.js +192 -0
  39. package/dist/cli/components/PermissionPrompt.js.map +1 -0
  40. package/dist/cli/components/ProviderManager.d.ts +8 -0
  41. package/dist/cli/components/ProviderManager.d.ts.map +1 -0
  42. package/dist/cli/components/ProviderManager.js +280 -0
  43. package/dist/cli/components/ProviderManager.js.map +1 -0
  44. package/dist/cli/components/Spinner.d.ts +7 -2
  45. package/dist/cli/components/Spinner.d.ts.map +1 -1
  46. package/dist/cli/components/Spinner.js +116 -25
  47. package/dist/cli/components/Spinner.js.map +1 -1
  48. package/dist/cli/components/TodoList.d.ts +7 -0
  49. package/dist/cli/components/TodoList.d.ts.map +1 -0
  50. package/dist/cli/components/TodoList.js +34 -0
  51. package/dist/cli/components/TodoList.js.map +1 -0
  52. package/dist/cli/components/index.d.ts +1 -0
  53. package/dist/cli/components/index.d.ts.map +1 -1
  54. package/dist/cli/components/index.js +1 -0
  55. package/dist/cli/components/index.js.map +1 -1
  56. package/dist/cli/components/markdown.d.ts +9 -0
  57. package/dist/cli/components/markdown.d.ts.map +1 -0
  58. package/dist/cli/components/markdown.js +129 -0
  59. package/dist/cli/components/markdown.js.map +1 -0
  60. package/dist/cli/components/theme.d.ts +5 -0
  61. package/dist/cli/components/theme.d.ts.map +1 -1
  62. package/dist/cli/components/theme.js +7 -0
  63. package/dist/cli/components/theme.js.map +1 -1
  64. package/dist/cli/index.js +66 -12
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/config/index.d.ts +14 -4
  67. package/dist/config/index.d.ts.map +1 -1
  68. package/dist/config/index.js +19 -3
  69. package/dist/config/index.js.map +1 -1
  70. package/dist/config/levels.d.ts +49 -0
  71. package/dist/config/levels.d.ts.map +1 -0
  72. package/dist/config/levels.js +222 -0
  73. package/dist/config/levels.js.map +1 -0
  74. package/dist/config/loader.d.ts +46 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +153 -0
  77. package/dist/config/loader.js.map +1 -0
  78. package/dist/config/manager.d.ts +115 -15
  79. package/dist/config/manager.d.ts.map +1 -1
  80. package/dist/config/manager.js +260 -34
  81. package/dist/config/manager.js.map +1 -1
  82. package/dist/config/manager.test.d.ts +5 -0
  83. package/dist/config/manager.test.d.ts.map +1 -0
  84. package/dist/config/manager.test.js +192 -0
  85. package/dist/config/manager.test.js.map +1 -0
  86. package/dist/config/merger.d.ts +56 -0
  87. package/dist/config/merger.d.ts.map +1 -0
  88. package/dist/config/merger.js +177 -0
  89. package/dist/config/merger.js.map +1 -0
  90. package/dist/config/providers-config.d.ts +28 -0
  91. package/dist/config/providers-config.d.ts.map +1 -0
  92. package/dist/config/providers-config.js +79 -0
  93. package/dist/config/providers-config.js.map +1 -0
  94. package/dist/config/test-utils.d.ts +24 -0
  95. package/dist/config/test-utils.d.ts.map +1 -0
  96. package/dist/config/test-utils.js +55 -0
  97. package/dist/config/test-utils.js.map +1 -0
  98. package/dist/config/types.d.ts +108 -9
  99. package/dist/config/types.d.ts.map +1 -1
  100. package/dist/config/types.js +53 -2
  101. package/dist/config/types.js.map +1 -1
  102. package/dist/memory/import-resolver.d.ts +46 -0
  103. package/dist/memory/import-resolver.d.ts.map +1 -0
  104. package/dist/memory/import-resolver.js +117 -0
  105. package/dist/memory/import-resolver.js.map +1 -0
  106. package/dist/memory/index.d.ts +7 -6
  107. package/dist/memory/index.d.ts.map +1 -1
  108. package/dist/memory/index.js +7 -5
  109. package/dist/memory/index.js.map +1 -1
  110. package/dist/memory/init-prompt.d.ts +22 -0
  111. package/dist/memory/init-prompt.d.ts.map +1 -0
  112. package/dist/memory/init-prompt.js +103 -0
  113. package/dist/memory/init-prompt.js.map +1 -0
  114. package/dist/memory/memory-manager.d.ts +119 -0
  115. package/dist/memory/memory-manager.d.ts.map +1 -0
  116. package/dist/memory/memory-manager.js +587 -0
  117. package/dist/memory/memory-manager.js.map +1 -0
  118. package/dist/memory/rules-parser.d.ts +38 -0
  119. package/dist/memory/rules-parser.d.ts.map +1 -0
  120. package/dist/memory/rules-parser.js +69 -0
  121. package/dist/memory/rules-parser.js.map +1 -0
  122. package/dist/memory/test-utils.d.ts +20 -0
  123. package/dist/memory/test-utils.d.ts.map +1 -0
  124. package/dist/memory/test-utils.js +44 -0
  125. package/dist/memory/test-utils.js.map +1 -0
  126. package/dist/memory/types.d.ts +70 -63
  127. package/dist/memory/types.d.ts.map +1 -1
  128. package/dist/memory/types.js +42 -2
  129. package/dist/memory/types.js.map +1 -1
  130. package/dist/permissions/audit.d.ts +82 -0
  131. package/dist/permissions/audit.d.ts.map +1 -0
  132. package/dist/permissions/audit.js +229 -0
  133. package/dist/permissions/audit.js.map +1 -0
  134. package/dist/permissions/index.d.ts +11 -1
  135. package/dist/permissions/index.d.ts.map +1 -1
  136. package/dist/permissions/index.js +15 -0
  137. package/dist/permissions/index.js.map +1 -1
  138. package/dist/permissions/manager.d.ts +149 -13
  139. package/dist/permissions/manager.d.ts.map +1 -1
  140. package/dist/permissions/manager.js +480 -35
  141. package/dist/permissions/manager.js.map +1 -1
  142. package/dist/permissions/manager.test.d.ts +5 -0
  143. package/dist/permissions/manager.test.d.ts.map +1 -0
  144. package/dist/permissions/manager.test.js +213 -0
  145. package/dist/permissions/manager.test.js.map +1 -0
  146. package/dist/permissions/persistence.d.ts +74 -0
  147. package/dist/permissions/persistence.d.ts.map +1 -0
  148. package/dist/permissions/persistence.js +248 -0
  149. package/dist/permissions/persistence.js.map +1 -0
  150. package/dist/permissions/persistence.test.d.ts +5 -0
  151. package/dist/permissions/persistence.test.d.ts.map +1 -0
  152. package/dist/permissions/persistence.test.js +171 -0
  153. package/dist/permissions/persistence.test.js.map +1 -0
  154. package/dist/permissions/prompt-matcher.d.ts +64 -0
  155. package/dist/permissions/prompt-matcher.d.ts.map +1 -0
  156. package/dist/permissions/prompt-matcher.js +415 -0
  157. package/dist/permissions/prompt-matcher.js.map +1 -0
  158. package/dist/permissions/prompt-matcher.test.d.ts +5 -0
  159. package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
  160. package/dist/permissions/prompt-matcher.test.js +107 -0
  161. package/dist/permissions/prompt-matcher.test.js.map +1 -0
  162. package/dist/permissions/types.d.ts +157 -0
  163. package/dist/permissions/types.d.ts.map +1 -1
  164. package/dist/permissions/types.js +43 -8
  165. package/dist/permissions/types.js.map +1 -1
  166. package/dist/prompts/index.d.ts +92 -0
  167. package/dist/prompts/index.d.ts.map +1 -0
  168. package/dist/prompts/index.js +241 -0
  169. package/dist/prompts/index.js.map +1 -0
  170. package/dist/providers/gemini.d.ts.map +1 -1
  171. package/dist/providers/gemini.js +14 -3
  172. package/dist/providers/gemini.js.map +1 -1
  173. package/dist/providers/index.d.ts +5 -3
  174. package/dist/providers/index.d.ts.map +1 -1
  175. package/dist/providers/index.js +13 -1
  176. package/dist/providers/index.js.map +1 -1
  177. package/dist/providers/registry.d.ts +66 -0
  178. package/dist/providers/registry.d.ts.map +1 -0
  179. package/dist/providers/registry.js +158 -0
  180. package/dist/providers/registry.js.map +1 -0
  181. package/dist/providers/search/brave.d.ts +14 -0
  182. package/dist/providers/search/brave.d.ts.map +1 -0
  183. package/dist/providers/search/brave.js +87 -0
  184. package/dist/providers/search/brave.js.map +1 -0
  185. package/dist/providers/search/exa.d.ts +12 -0
  186. package/dist/providers/search/exa.d.ts.map +1 -0
  187. package/dist/providers/search/exa.js +158 -0
  188. package/dist/providers/search/exa.js.map +1 -0
  189. package/dist/providers/search/index.d.ts +31 -0
  190. package/dist/providers/search/index.d.ts.map +1 -0
  191. package/dist/providers/search/index.js +75 -0
  192. package/dist/providers/search/index.js.map +1 -0
  193. package/dist/providers/search/serper.d.ts +14 -0
  194. package/dist/providers/search/serper.d.ts.map +1 -0
  195. package/dist/providers/search/serper.js +87 -0
  196. package/dist/providers/search/serper.js.map +1 -0
  197. package/dist/providers/search/types.d.ts +21 -0
  198. package/dist/providers/search/types.d.ts.map +1 -0
  199. package/dist/providers/search/types.js +5 -0
  200. package/dist/providers/search/types.js.map +1 -0
  201. package/dist/providers/store.d.ts +104 -0
  202. package/dist/providers/store.d.ts.map +1 -0
  203. package/dist/providers/store.js +171 -0
  204. package/dist/providers/store.js.map +1 -0
  205. package/dist/providers/types.d.ts +7 -1
  206. package/dist/providers/types.d.ts.map +1 -1
  207. package/dist/providers/vertex-ai.d.ts +33 -0
  208. package/dist/providers/vertex-ai.d.ts.map +1 -0
  209. package/dist/providers/vertex-ai.js +407 -0
  210. package/dist/providers/vertex-ai.js.map +1 -0
  211. package/dist/tools/builtin/bash.d.ts.map +1 -1
  212. package/dist/tools/builtin/bash.js +2 -1
  213. package/dist/tools/builtin/bash.js.map +1 -1
  214. package/dist/tools/builtin/edit.d.ts.map +1 -1
  215. package/dist/tools/builtin/edit.js +2 -1
  216. package/dist/tools/builtin/edit.js.map +1 -1
  217. package/dist/tools/builtin/glob.d.ts.map +1 -1
  218. package/dist/tools/builtin/glob.js +2 -1
  219. package/dist/tools/builtin/glob.js.map +1 -1
  220. package/dist/tools/builtin/grep.d.ts.map +1 -1
  221. package/dist/tools/builtin/grep.js +2 -1
  222. package/dist/tools/builtin/grep.js.map +1 -1
  223. package/dist/tools/builtin/read.d.ts.map +1 -1
  224. package/dist/tools/builtin/read.js +2 -1
  225. package/dist/tools/builtin/read.js.map +1 -1
  226. package/dist/tools/builtin/todowrite.d.ts +15 -0
  227. package/dist/tools/builtin/todowrite.d.ts.map +1 -0
  228. package/dist/tools/builtin/todowrite.js +88 -0
  229. package/dist/tools/builtin/todowrite.js.map +1 -0
  230. package/dist/tools/builtin/webfetch.d.ts +20 -0
  231. package/dist/tools/builtin/webfetch.d.ts.map +1 -0
  232. package/dist/tools/builtin/webfetch.js +228 -0
  233. package/dist/tools/builtin/webfetch.js.map +1 -0
  234. package/dist/tools/builtin/websearch.d.ts +17 -0
  235. package/dist/tools/builtin/websearch.d.ts.map +1 -0
  236. package/dist/tools/builtin/websearch.js +87 -0
  237. package/dist/tools/builtin/websearch.js.map +1 -0
  238. package/dist/tools/builtin/write.d.ts.map +1 -1
  239. package/dist/tools/builtin/write.js +2 -1
  240. package/dist/tools/builtin/write.js.map +1 -1
  241. package/dist/tools/index.d.ts +18 -0
  242. package/dist/tools/index.d.ts.map +1 -1
  243. package/dist/tools/index.js +28 -2
  244. package/dist/tools/index.js.map +1 -1
  245. package/dist/tools/types.d.ts +41 -0
  246. package/dist/tools/types.d.ts.map +1 -1
  247. package/dist/tools/types.js +16 -0
  248. package/dist/tools/types.js.map +1 -1
  249. package/dist/tools/utils/ssrf.d.ts +18 -0
  250. package/dist/tools/utils/ssrf.d.ts.map +1 -0
  251. package/dist/tools/utils/ssrf.js +70 -0
  252. package/dist/tools/utils/ssrf.js.map +1 -0
  253. package/docs/README.md +5 -4
  254. package/docs/config-system-comparison.md +707 -0
  255. package/docs/memory-system.md +238 -0
  256. package/docs/permissions.md +368 -0
  257. package/docs/proposals/0001-web-fetch-tool.md +32 -2
  258. package/docs/proposals/0002-web-search-tool.md +59 -2
  259. package/docs/proposals/0005-todo-system.md +350 -85
  260. package/docs/proposals/0006-memory-system.md +11 -10
  261. package/docs/proposals/0012-ask-user-question.md +941 -206
  262. package/docs/proposals/0023-permission-enhancements.md +61 -2
  263. package/docs/proposals/0041-configuration-system.md +587 -0
  264. package/docs/proposals/0042-prompt-optimization.md +866 -0
  265. package/docs/proposals/README.md +8 -6
  266. package/docs/providers.md +220 -0
  267. package/jest.config.js +26 -0
  268. package/package.json +14 -3
  269. package/src/agent/agent.ts +120 -18
  270. package/src/agent/types.ts +9 -1
  271. package/src/cli/components/App.tsx +369 -47
  272. package/src/cli/components/CommandSuggestions.tsx +3 -0
  273. package/src/cli/components/Header.tsx +11 -17
  274. package/src/cli/components/Logo.tsx +76 -9
  275. package/src/cli/components/Messages.tsx +146 -38
  276. package/src/cli/components/ModelSelector.tsx +169 -52
  277. package/src/cli/components/PermissionPrompt.tsx +388 -0
  278. package/src/cli/components/ProviderManager.tsx +534 -0
  279. package/src/cli/components/Spinner.tsx +138 -25
  280. package/src/cli/components/TodoList.tsx +54 -0
  281. package/src/cli/components/index.ts +6 -0
  282. package/src/cli/components/markdown.ts +157 -0
  283. package/src/cli/components/theme.ts +7 -0
  284. package/src/cli/index.tsx +76 -13
  285. package/src/config/index.ts +79 -4
  286. package/src/config/levels.test.ts +163 -0
  287. package/src/config/levels.ts +285 -0
  288. package/src/config/loader.test.ts +120 -0
  289. package/src/config/loader.ts +178 -0
  290. package/src/config/manager.test.ts +215 -0
  291. package/src/config/manager.ts +328 -40
  292. package/src/config/merger.test.ts +360 -0
  293. package/src/config/merger.ts +221 -0
  294. package/src/config/providers-config.ts +85 -0
  295. package/src/config/test-utils.ts +79 -0
  296. package/src/config/types.ts +186 -9
  297. package/src/memory/import-resolver.test.ts +117 -0
  298. package/src/memory/import-resolver.ts +149 -0
  299. package/src/memory/index.ts +11 -0
  300. package/src/memory/init-prompt.ts +113 -0
  301. package/src/memory/memory-manager.test.ts +198 -0
  302. package/src/memory/memory-manager.ts +716 -0
  303. package/src/memory/rules-parser.test.ts +182 -0
  304. package/src/memory/rules-parser.ts +82 -0
  305. package/src/memory/test-utils.ts +60 -0
  306. package/src/memory/types.ts +119 -0
  307. package/src/permissions/audit.ts +284 -0
  308. package/src/permissions/index.ts +20 -1
  309. package/src/permissions/manager.test.ts +260 -0
  310. package/src/permissions/manager.ts +592 -40
  311. package/src/permissions/persistence.test.ts +220 -0
  312. package/src/permissions/persistence.ts +301 -0
  313. package/src/permissions/prompt-matcher.test.ts +213 -0
  314. package/src/permissions/prompt-matcher.ts +472 -0
  315. package/src/permissions/types.ts +236 -8
  316. package/src/prompts/index.test.ts +279 -0
  317. package/src/prompts/index.ts +306 -0
  318. package/src/prompts/system/anthropic.txt +29 -0
  319. package/src/prompts/system/base.txt +124 -0
  320. package/src/prompts/system/gemini.txt +35 -0
  321. package/src/prompts/system/generic.txt +128 -0
  322. package/src/prompts/system/openai.txt +29 -0
  323. package/src/prompts/tools/bash.txt +60 -0
  324. package/src/prompts/tools/edit.txt +29 -0
  325. package/src/prompts/tools/glob.txt +35 -0
  326. package/src/prompts/tools/grep.txt +43 -0
  327. package/src/prompts/tools/read.txt +22 -0
  328. package/src/prompts/tools/todowrite.txt +71 -0
  329. package/src/prompts/tools/webfetch.txt +34 -0
  330. package/src/prompts/tools/websearch.txt +41 -0
  331. package/src/prompts/tools/write.txt +23 -0
  332. package/src/providers/gemini.ts +20 -4
  333. package/src/providers/index.ts +18 -3
  334. package/src/providers/registry.ts +198 -0
  335. package/src/providers/search/brave.ts +132 -0
  336. package/src/providers/search/exa.ts +217 -0
  337. package/src/providers/search/index.ts +79 -0
  338. package/src/providers/search/serper.ts +133 -0
  339. package/src/providers/search/types.ts +24 -0
  340. package/src/providers/store.ts +216 -0
  341. package/src/providers/types.ts +9 -1
  342. package/src/providers/vertex-ai.ts +594 -0
  343. package/src/tools/builtin/bash.ts +2 -1
  344. package/src/tools/builtin/edit.ts +2 -1
  345. package/src/tools/builtin/glob.ts +2 -1
  346. package/src/tools/builtin/grep.ts +2 -1
  347. package/src/tools/builtin/read.ts +2 -1
  348. package/src/tools/builtin/todowrite.ts +102 -0
  349. package/src/tools/builtin/webfetch.ts +261 -0
  350. package/src/tools/builtin/websearch.ts +103 -0
  351. package/src/tools/builtin/write.ts +2 -1
  352. package/src/tools/index.ts +28 -2
  353. package/src/tools/types.ts +32 -0
  354. package/src/tools/utils/ssrf.ts +79 -0
  355. package/tsconfig.json +1 -1
  356. package/CLAUDE.md +0 -70
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Permission Audit - Log permission decisions for transparency
3
+ *
4
+ * Maintains an in-memory audit log with optional file persistence.
5
+ * Useful for debugging, security review, and compliance.
6
+ */
7
+
8
+ import * as fs from 'fs/promises';
9
+ import * as path from 'path';
10
+ import * as os from 'os';
11
+ import type { PermissionAuditEntry, AuditDecision } from './types.js';
12
+
13
+ const AUDIT_FILE = 'permission-audit.json';
14
+ const MAX_MEMORY_ENTRIES = 1000;
15
+ const MAX_FILE_ENTRIES = 10000;
16
+ const GLOBAL_DIR = path.join(os.homedir(), '.gencode');
17
+
18
+ /**
19
+ * Summarize tool input for audit (avoid storing sensitive data)
20
+ */
21
+ function summarizeInput(tool: string, input: unknown): string {
22
+ if (input === null || input === undefined) return '';
23
+
24
+ if (typeof input === 'string') {
25
+ return input.slice(0, 100);
26
+ }
27
+
28
+ if (typeof input !== 'object') {
29
+ return String(input).slice(0, 100);
30
+ }
31
+
32
+ const obj = input as Record<string, unknown>;
33
+
34
+ // Tool-specific summaries
35
+ switch (tool) {
36
+ case 'Bash':
37
+ return (obj.command as string)?.slice(0, 100) ?? '';
38
+
39
+ case 'Read':
40
+ case 'Write':
41
+ case 'Edit':
42
+ case 'Glob':
43
+ return (obj.file_path as string) ?? (obj.path as string) ?? '';
44
+
45
+ case 'Grep':
46
+ return `${obj.pattern ?? ''} in ${obj.path ?? '.'}`;
47
+
48
+ case 'WebFetch':
49
+ return (obj.url as string)?.slice(0, 100) ?? '';
50
+
51
+ case 'WebSearch':
52
+ return (obj.query as string)?.slice(0, 100) ?? '';
53
+
54
+ default:
55
+ // Generic summary
56
+ const keys = Object.keys(obj).slice(0, 3);
57
+ return keys.map((k) => `${k}:${String(obj[k]).slice(0, 20)}`).join(', ');
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Permission Audit Logger
63
+ */
64
+ export class PermissionAudit {
65
+ private entries: PermissionAuditEntry[] = [];
66
+ private persistToFile: boolean;
67
+ private filePath: string;
68
+
69
+ constructor(options: { persistToFile?: boolean; auditDir?: string } = {}) {
70
+ this.persistToFile = options.persistToFile ?? false;
71
+ const dir = options.auditDir ?? GLOBAL_DIR;
72
+ this.filePath = path.join(dir, AUDIT_FILE);
73
+ }
74
+
75
+ /**
76
+ * Log a permission decision
77
+ */
78
+ async log(
79
+ tool: string,
80
+ input: unknown,
81
+ decision: AuditDecision,
82
+ reason: string,
83
+ options: {
84
+ matchedRule?: string;
85
+ sessionId?: string;
86
+ } = {}
87
+ ): Promise<void> {
88
+ const entry: PermissionAuditEntry = {
89
+ timestamp: new Date(),
90
+ tool,
91
+ inputSummary: summarizeInput(tool, input),
92
+ decision,
93
+ reason,
94
+ matchedRule: options.matchedRule,
95
+ sessionId: options.sessionId,
96
+ };
97
+
98
+ // Add to memory
99
+ this.entries.push(entry);
100
+
101
+ // Trim if too large
102
+ if (this.entries.length > MAX_MEMORY_ENTRIES) {
103
+ this.entries = this.entries.slice(-MAX_MEMORY_ENTRIES);
104
+ }
105
+
106
+ // Persist if enabled
107
+ if (this.persistToFile) {
108
+ await this.appendToFile(entry);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Append entry to file
114
+ */
115
+ private async appendToFile(entry: PermissionAuditEntry): Promise<void> {
116
+ try {
117
+ // Ensure directory exists
118
+ const dir = path.dirname(this.filePath);
119
+ await fs.mkdir(dir, { recursive: true });
120
+
121
+ // Load existing entries
122
+ let fileEntries: PermissionAuditEntry[] = [];
123
+ try {
124
+ const content = await fs.readFile(this.filePath, 'utf-8');
125
+ fileEntries = JSON.parse(content);
126
+ } catch {
127
+ // File doesn't exist
128
+ }
129
+
130
+ // Add new entry
131
+ fileEntries.push(entry);
132
+
133
+ // Trim if too large
134
+ if (fileEntries.length > MAX_FILE_ENTRIES) {
135
+ fileEntries = fileEntries.slice(-MAX_FILE_ENTRIES);
136
+ }
137
+
138
+ // Write back
139
+ await fs.writeFile(
140
+ this.filePath,
141
+ JSON.stringify(fileEntries, null, 2),
142
+ 'utf-8'
143
+ );
144
+ } catch {
145
+ // Silently fail - audit should not break the app
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Get recent audit entries
151
+ */
152
+ getRecent(count: number = 50): PermissionAuditEntry[] {
153
+ return this.entries.slice(-count);
154
+ }
155
+
156
+ /**
157
+ * Get all entries in memory
158
+ */
159
+ getAll(): PermissionAuditEntry[] {
160
+ return [...this.entries];
161
+ }
162
+
163
+ /**
164
+ * Get entries by tool
165
+ */
166
+ getByTool(tool: string): PermissionAuditEntry[] {
167
+ return this.entries.filter((e) => e.tool === tool);
168
+ }
169
+
170
+ /**
171
+ * Get entries by decision
172
+ */
173
+ getByDecision(decision: AuditDecision): PermissionAuditEntry[] {
174
+ return this.entries.filter((e) => e.decision === decision);
175
+ }
176
+
177
+ /**
178
+ * Get entries by session
179
+ */
180
+ getBySession(sessionId: string): PermissionAuditEntry[] {
181
+ return this.entries.filter((e) => e.sessionId === sessionId);
182
+ }
183
+
184
+ /**
185
+ * Get statistics
186
+ */
187
+ getStats(): {
188
+ total: number;
189
+ allowed: number;
190
+ denied: number;
191
+ confirmed: number;
192
+ rejected: number;
193
+ byTool: Record<string, number>;
194
+ } {
195
+ const stats = {
196
+ total: this.entries.length,
197
+ allowed: 0,
198
+ denied: 0,
199
+ confirmed: 0,
200
+ rejected: 0,
201
+ byTool: {} as Record<string, number>,
202
+ };
203
+
204
+ for (const entry of this.entries) {
205
+ // Count by decision
206
+ switch (entry.decision) {
207
+ case 'allowed':
208
+ stats.allowed++;
209
+ break;
210
+ case 'denied':
211
+ stats.denied++;
212
+ break;
213
+ case 'confirmed':
214
+ stats.confirmed++;
215
+ break;
216
+ case 'rejected':
217
+ stats.rejected++;
218
+ break;
219
+ }
220
+
221
+ // Count by tool
222
+ stats.byTool[entry.tool] = (stats.byTool[entry.tool] ?? 0) + 1;
223
+ }
224
+
225
+ return stats;
226
+ }
227
+
228
+ /**
229
+ * Clear in-memory entries
230
+ */
231
+ clear(): void {
232
+ this.entries = [];
233
+ }
234
+
235
+ /**
236
+ * Load entries from file
237
+ */
238
+ async loadFromFile(): Promise<PermissionAuditEntry[]> {
239
+ try {
240
+ const content = await fs.readFile(this.filePath, 'utf-8');
241
+ return JSON.parse(content);
242
+ } catch {
243
+ return [];
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Clear file entries
249
+ */
250
+ async clearFile(): Promise<void> {
251
+ try {
252
+ await fs.writeFile(this.filePath, '[]', 'utf-8');
253
+ } catch {
254
+ // Silently fail
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Format entry for display
260
+ */
261
+ formatEntry(entry: PermissionAuditEntry): string {
262
+ const time = entry.timestamp.toLocaleTimeString('en-US', {
263
+ hour: '2-digit',
264
+ minute: '2-digit',
265
+ });
266
+
267
+ const decision = entry.decision.toUpperCase().padEnd(9);
268
+ const tool = entry.tool.padEnd(10);
269
+ const input = entry.inputSummary.slice(0, 40).padEnd(40);
270
+
271
+ return `${time} ${decision} ${tool} ${input}`;
272
+ }
273
+
274
+ /**
275
+ * Format entries as table
276
+ */
277
+ formatTable(entries: PermissionAuditEntry[]): string {
278
+ const header = 'Time Decision Tool Input';
279
+ const separator = '─'.repeat(header.length);
280
+ const rows = entries.map((e) => this.formatEntry(e));
281
+
282
+ return [header, separator, ...rows].join('\n');
283
+ }
284
+ }
@@ -1,7 +1,26 @@
1
1
  /**
2
2
  * Permission System
3
+ *
4
+ * Enhanced permission management with:
5
+ * - Pattern-based rules (Claude Code style: "Bash(git add:*)")
6
+ * - Prompt-based permissions (ExitPlanMode style)
7
+ * - Session/project/global scopes
8
+ * - Persistent allowlists
9
+ * - Audit trail
3
10
  */
4
11
 
12
+ // Types
5
13
  export * from './types.js';
14
+
15
+ // Core Manager
6
16
  export { PermissionManager } from './manager.js';
7
- export type { ConfirmCallback } from './manager.js';
17
+ export type { ConfirmCallback, SimpleConfirmCallback } from './manager.js';
18
+
19
+ // Prompt Matching
20
+ export { PromptMatcher, parsePatternString, matchesPatternString } from './prompt-matcher.js';
21
+
22
+ // Persistence
23
+ export { PermissionPersistence } from './persistence.js';
24
+
25
+ // Audit
26
+ export { PermissionAudit } from './audit.js';
@@ -0,0 +1,260 @@
1
+ /**
2
+ * PermissionManager Tests
3
+ */
4
+
5
+ import { jest } from '@jest/globals';
6
+ import { PermissionManager } from './manager.js';
7
+ import type { PermissionConfig, ApprovalAction } from './types.js';
8
+
9
+ describe('PermissionManager', () => {
10
+ describe('default behavior', () => {
11
+ it('should auto-approve read-only tools by default', async () => {
12
+ const manager = new PermissionManager();
13
+
14
+ const readDecision = await manager.checkPermission({
15
+ tool: 'Read',
16
+ input: { file_path: '/test/file.ts' },
17
+ });
18
+ expect(readDecision.allowed).toBe(true);
19
+ expect(readDecision.requiresConfirmation).toBe(false);
20
+
21
+ const globDecision = await manager.checkPermission({
22
+ tool: 'Glob',
23
+ input: { pattern: '*.ts' },
24
+ });
25
+ expect(globDecision.allowed).toBe(true);
26
+
27
+ const grepDecision = await manager.checkPermission({
28
+ tool: 'Grep',
29
+ input: { pattern: 'test' },
30
+ });
31
+ expect(grepDecision.allowed).toBe(true);
32
+ });
33
+
34
+ it('should require confirmation for write operations by default', async () => {
35
+ const manager = new PermissionManager();
36
+
37
+ const bashDecision = await manager.checkPermission({
38
+ tool: 'Bash',
39
+ input: { command: 'echo hello' },
40
+ });
41
+ expect(bashDecision.allowed).toBe(false);
42
+ expect(bashDecision.requiresConfirmation).toBe(true);
43
+
44
+ const writeDecision = await manager.checkPermission({
45
+ tool: 'Write',
46
+ input: { file_path: '/test/file.ts', content: 'test' },
47
+ });
48
+ expect(writeDecision.allowed).toBe(false);
49
+ expect(writeDecision.requiresConfirmation).toBe(true);
50
+ });
51
+ });
52
+
53
+ describe('allow rules', () => {
54
+ it('should auto-approve operations matching allow rules', async () => {
55
+ const manager = new PermissionManager({
56
+ config: {
57
+ defaultMode: 'confirm',
58
+ rules: [
59
+ { tool: 'Bash', mode: 'auto', pattern: 'git add:*' },
60
+ ],
61
+ allowedPrompts: [],
62
+ },
63
+ });
64
+
65
+ const decision = await manager.checkPermission({
66
+ tool: 'Bash',
67
+ input: { command: 'git add .' },
68
+ });
69
+ expect(decision.allowed).toBe(true);
70
+ expect(decision.requiresConfirmation).toBe(false);
71
+ });
72
+
73
+ it('should still require confirmation for non-matching operations', async () => {
74
+ const manager = new PermissionManager({
75
+ config: {
76
+ defaultMode: 'confirm',
77
+ rules: [
78
+ { tool: 'Bash', mode: 'auto', pattern: 'git add:*' },
79
+ ],
80
+ allowedPrompts: [],
81
+ },
82
+ });
83
+
84
+ const decision = await manager.checkPermission({
85
+ tool: 'Bash',
86
+ input: { command: 'rm -rf /' },
87
+ });
88
+ expect(decision.allowed).toBe(false);
89
+ expect(decision.requiresConfirmation).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe('deny rules', () => {
94
+ it('should block operations matching deny rules', async () => {
95
+ const manager = new PermissionManager({
96
+ config: {
97
+ defaultMode: 'confirm',
98
+ rules: [
99
+ { tool: 'Bash', mode: 'deny', pattern: 'rm -rf:*' },
100
+ ],
101
+ allowedPrompts: [],
102
+ },
103
+ });
104
+
105
+ const decision = await manager.checkPermission({
106
+ tool: 'Bash',
107
+ input: { command: 'rm -rf /' },
108
+ });
109
+ expect(decision.allowed).toBe(false);
110
+ expect(decision.requiresConfirmation).toBe(false);
111
+ });
112
+
113
+ it('should prioritize deny over allow rules', async () => {
114
+ const manager = new PermissionManager({
115
+ config: {
116
+ defaultMode: 'confirm',
117
+ rules: [
118
+ { tool: 'Bash', mode: 'auto', pattern: 'rm:*' },
119
+ { tool: 'Bash', mode: 'deny', pattern: 'rm -rf:*' },
120
+ ],
121
+ allowedPrompts: [],
122
+ },
123
+ });
124
+
125
+ const decision = await manager.checkPermission({
126
+ tool: 'Bash',
127
+ input: { command: 'rm -rf /' },
128
+ });
129
+ expect(decision.allowed).toBe(false);
130
+ expect(decision.requiresConfirmation).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('session caching', () => {
135
+ it('should cache session approvals', async () => {
136
+ const manager = new PermissionManager();
137
+
138
+ manager.approveForSession('Bash', 'npm test');
139
+
140
+ const decision = await manager.checkPermission({
141
+ tool: 'Bash',
142
+ input: { command: 'npm test' },
143
+ });
144
+ expect(decision.allowed).toBe(true);
145
+ });
146
+
147
+ it('should clear session cache', async () => {
148
+ const manager = new PermissionManager();
149
+
150
+ manager.approveForSession('Bash', 'npm test');
151
+ manager.clearSessionApprovals();
152
+
153
+ const decision = await manager.checkPermission({
154
+ tool: 'Bash',
155
+ input: { command: 'npm test' },
156
+ });
157
+ expect(decision.requiresConfirmation).toBe(true);
158
+ });
159
+ });
160
+
161
+ describe('prompt-based permissions', () => {
162
+ it('should approve operations matching allowed prompts', async () => {
163
+ const manager = new PermissionManager();
164
+ manager.addAllowedPrompts([
165
+ { tool: 'Bash', prompt: 'run tests' },
166
+ ]);
167
+
168
+ const decision = await manager.checkPermission({
169
+ tool: 'Bash',
170
+ input: { command: 'npm test' },
171
+ });
172
+ expect(decision.allowed).toBe(true);
173
+ });
174
+
175
+ it('should clear allowed prompts', () => {
176
+ const manager = new PermissionManager();
177
+ manager.addAllowedPrompts([
178
+ { tool: 'Bash', prompt: 'run tests' },
179
+ ]);
180
+ manager.clearAllowedPrompts();
181
+
182
+ expect(manager.getAllowedPrompts()).toHaveLength(0);
183
+ });
184
+ });
185
+
186
+ describe('confirmation callback', () => {
187
+ it('should call enhanced confirm callback when confirmation required', async () => {
188
+ const manager = new PermissionManager();
189
+ const mockCallback = jest.fn().mockResolvedValue('allow_once' as ApprovalAction);
190
+ manager.setConfirmCallback(mockCallback);
191
+
192
+ const result = await manager.requestPermission('Bash', { command: 'echo hello' });
193
+
194
+ expect(mockCallback).toHaveBeenCalledWith(
195
+ 'Bash',
196
+ { command: 'echo hello' },
197
+ expect.any(Array)
198
+ );
199
+ expect(result).toBe(true);
200
+ });
201
+
202
+ it('should call saveRuleCallback when allow_always is selected', async () => {
203
+ const manager = new PermissionManager();
204
+ const mockSaveCallback = jest.fn().mockResolvedValue(undefined);
205
+ const mockConfirmCallback = jest.fn().mockResolvedValue('allow_always' as ApprovalAction);
206
+
207
+ manager.setConfirmCallback(mockConfirmCallback);
208
+ manager.setSaveRuleCallback(mockSaveCallback);
209
+
210
+ await manager.requestPermission('Bash', { command: 'npm run build' });
211
+
212
+ expect(mockSaveCallback).toHaveBeenCalledWith('Bash', 'npm run:*');
213
+ });
214
+ });
215
+
216
+ describe('getModeForTool', () => {
217
+ it('should return auto for read-only tools', () => {
218
+ const manager = new PermissionManager();
219
+
220
+ expect(manager.getModeForTool('Read')).toBe('auto');
221
+ expect(manager.getModeForTool('Glob')).toBe('auto');
222
+ expect(manager.getModeForTool('Grep')).toBe('auto');
223
+ });
224
+
225
+ it('should return confirm for write tools', () => {
226
+ const manager = new PermissionManager();
227
+
228
+ expect(manager.getModeForTool('Bash')).toBe('confirm');
229
+ expect(manager.getModeForTool('Write')).toBe('confirm');
230
+ expect(manager.getModeForTool('Edit')).toBe('confirm');
231
+ });
232
+ });
233
+
234
+ describe('audit logging', () => {
235
+ it('should log permission decisions', async () => {
236
+ const manager = new PermissionManager({ enableAudit: true });
237
+
238
+ await manager.checkPermission({
239
+ tool: 'Read',
240
+ input: { file_path: '/test/file.ts' },
241
+ });
242
+
243
+ const auditLog = manager.getAuditLog(10);
244
+ expect(auditLog.length).toBeGreaterThan(0);
245
+ expect(auditLog[0].tool).toBe('Read');
246
+ });
247
+
248
+ it('should provide audit stats', async () => {
249
+ const manager = new PermissionManager({ enableAudit: true });
250
+
251
+ await manager.checkPermission({
252
+ tool: 'Read',
253
+ input: { file_path: '/test/file.ts' },
254
+ });
255
+
256
+ const stats = manager.getAuditStats();
257
+ expect(stats.allowed).toBeGreaterThanOrEqual(1);
258
+ });
259
+ });
260
+ });