gobby 0.2.5__py3-none-any.whl

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 (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,393 @@
1
+ """Workflow audit log storage manager.
2
+
3
+ Provides persistent storage for workflow decisions (tool permissions,
4
+ rule evaluations, step transitions) for explainability and debugging.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from dataclasses import dataclass, field
10
+ from datetime import UTC, datetime
11
+ from typing import Any
12
+
13
+ from gobby.storage.database import DatabaseProtocol, LocalDatabase
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class WorkflowAuditEntry:
20
+ """A single workflow audit log entry."""
21
+
22
+ session_id: str
23
+ step: str
24
+ event_type: str # 'tool_call', 'rule_eval', 'transition', 'exit_check', 'approval'
25
+ result: str # 'allow', 'block', 'transition', 'skip', 'approved', 'rejected', 'pending'
26
+ reason: str | None = None
27
+ tool_name: str | None = None
28
+ rule_id: str | None = None
29
+ condition: str | None = None
30
+ context: dict[str, Any] = field(default_factory=dict)
31
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
32
+ id: int | None = None
33
+
34
+
35
+ class WorkflowAuditManager:
36
+ """Manages workflow audit log entries in SQLite."""
37
+
38
+ def __init__(self, db: DatabaseProtocol | None = None):
39
+ """Initialize the audit manager.
40
+
41
+ Args:
42
+ db: Optional database instance. If None, creates a new one.
43
+ """
44
+ self._db = db or LocalDatabase()
45
+
46
+ @property
47
+ def db(self) -> DatabaseProtocol:
48
+ """Get database instance."""
49
+ return self._db
50
+
51
+ def log(
52
+ self,
53
+ session_id: str,
54
+ step: str,
55
+ event_type: str,
56
+ result: str,
57
+ reason: str | None = None,
58
+ tool_name: str | None = None,
59
+ rule_id: str | None = None,
60
+ condition: str | None = None,
61
+ context: dict[str, Any] | None = None,
62
+ ) -> int | None:
63
+ """Log a workflow audit entry.
64
+
65
+ Args:
66
+ session_id: The session this entry belongs to
67
+ step: Current workflow step
68
+ event_type: Type of event ('tool_call', 'rule_eval', etc.)
69
+ result: Result of the evaluation ('allow', 'block', etc.)
70
+ reason: Human-readable explanation
71
+ tool_name: Name of tool (for tool_call events)
72
+ rule_id: Rule identifier (for rule_eval events)
73
+ condition: The 'when' clause evaluated
74
+ context: Additional JSON context
75
+
76
+ Returns:
77
+ The inserted row ID, or None on failure.
78
+ """
79
+ try:
80
+ timestamp = datetime.now(UTC).isoformat()
81
+ context_json = json.dumps(context) if context else None
82
+
83
+ cursor = self.db.execute(
84
+ """
85
+ INSERT INTO workflow_audit_log
86
+ (session_id, timestamp, step, event_type, tool_name, rule_id, condition, result, reason, context)
87
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
88
+ """,
89
+ (
90
+ session_id,
91
+ timestamp,
92
+ step,
93
+ event_type,
94
+ tool_name,
95
+ rule_id,
96
+ condition,
97
+ result,
98
+ reason,
99
+ context_json,
100
+ ),
101
+ )
102
+ return cursor.lastrowid
103
+ except Exception as e:
104
+ logger.error(f"Failed to log audit entry: {e}")
105
+ return None
106
+
107
+ def log_tool_call(
108
+ self,
109
+ session_id: str,
110
+ step: str,
111
+ tool_name: str,
112
+ result: str,
113
+ reason: str | None = None,
114
+ context: dict[str, Any] | None = None,
115
+ ) -> int | None:
116
+ """Log a tool call permission check.
117
+
118
+ Args:
119
+ session_id: Session ID
120
+ step: Current step
121
+ tool_name: Name of the tool
122
+ result: 'allow' or 'block'
123
+ reason: Why the tool was allowed/blocked
124
+ context: Additional context (tool args, etc.)
125
+
126
+ Returns:
127
+ Row ID or None.
128
+ """
129
+ return self.log(
130
+ session_id=session_id,
131
+ step=step,
132
+ event_type="tool_call",
133
+ result=result,
134
+ reason=reason,
135
+ tool_name=tool_name,
136
+ context=context,
137
+ )
138
+
139
+ def log_rule_eval(
140
+ self,
141
+ session_id: str,
142
+ step: str,
143
+ rule_id: str,
144
+ condition: str,
145
+ result: str,
146
+ reason: str | None = None,
147
+ context: dict[str, Any] | None = None,
148
+ ) -> int | None:
149
+ """Log a rule evaluation.
150
+
151
+ Args:
152
+ session_id: Session ID
153
+ step: Current step
154
+ rule_id: Identifier for the rule
155
+ condition: The 'when' clause
156
+ result: 'allow', 'block', 'skip'
157
+ reason: Why the rule fired/didn't fire
158
+ context: Additional context
159
+
160
+ Returns:
161
+ Row ID or None.
162
+ """
163
+ return self.log(
164
+ session_id=session_id,
165
+ step=step,
166
+ event_type="rule_eval",
167
+ result=result,
168
+ reason=reason,
169
+ rule_id=rule_id,
170
+ condition=condition,
171
+ context=context,
172
+ )
173
+
174
+ def log_transition(
175
+ self,
176
+ session_id: str,
177
+ from_step: str,
178
+ to_step: str,
179
+ reason: str | None = None,
180
+ context: dict[str, Any] | None = None,
181
+ ) -> int | None:
182
+ """Log a step transition.
183
+
184
+ Args:
185
+ session_id: Session ID
186
+ from_step: Step transitioning from
187
+ to_step: Step transitioning to
188
+ reason: Why the transition occurred
189
+ context: Additional context (trigger condition, etc.)
190
+
191
+ Returns:
192
+ Row ID or None.
193
+ """
194
+ ctx = context or {}
195
+ ctx["from_step"] = from_step
196
+ ctx["to_step"] = to_step
197
+ return self.log(
198
+ session_id=session_id,
199
+ step=from_step,
200
+ event_type="transition",
201
+ result="transition",
202
+ reason=reason or f"Transitioned to '{to_step}'",
203
+ context=ctx,
204
+ )
205
+
206
+ def log_exit_check(
207
+ self,
208
+ session_id: str,
209
+ step: str,
210
+ condition: str,
211
+ result: str,
212
+ reason: str | None = None,
213
+ context: dict[str, Any] | None = None,
214
+ ) -> int | None:
215
+ """Log an exit condition check.
216
+
217
+ Args:
218
+ session_id: Session ID
219
+ step: Current step
220
+ condition: The exit condition being checked
221
+ result: 'met' or 'unmet'
222
+ reason: Why the condition was met/unmet
223
+ context: Additional context
224
+
225
+ Returns:
226
+ Row ID or None.
227
+ """
228
+ return self.log(
229
+ session_id=session_id,
230
+ step=step,
231
+ event_type="exit_check",
232
+ result=result,
233
+ reason=reason,
234
+ condition=condition,
235
+ context=context,
236
+ )
237
+
238
+ def log_approval(
239
+ self,
240
+ session_id: str,
241
+ step: str,
242
+ result: str,
243
+ condition_id: str | None = None,
244
+ prompt: str | None = None,
245
+ context: dict[str, Any] | None = None,
246
+ ) -> int | None:
247
+ """Log an approval gate event.
248
+
249
+ Args:
250
+ session_id: Session ID
251
+ step: Current step
252
+ result: 'approved', 'rejected', 'pending', 'timeout'
253
+ condition_id: The approval condition ID
254
+ prompt: The approval prompt shown
255
+ context: Additional context
256
+
257
+ Returns:
258
+ Row ID or None.
259
+ """
260
+ ctx = context or {}
261
+ if condition_id:
262
+ ctx["condition_id"] = condition_id
263
+ if prompt:
264
+ ctx["prompt"] = prompt
265
+ return self.log(
266
+ session_id=session_id,
267
+ step=step,
268
+ event_type="approval",
269
+ result=result,
270
+ reason=prompt,
271
+ context=ctx,
272
+ )
273
+
274
+ def get_entries(
275
+ self,
276
+ session_id: str | None = None,
277
+ event_type: str | None = None,
278
+ result: str | None = None,
279
+ limit: int = 50,
280
+ offset: int = 0,
281
+ ) -> list[WorkflowAuditEntry]:
282
+ """Get audit log entries with optional filters.
283
+
284
+ Args:
285
+ session_id: Filter by session ID
286
+ event_type: Filter by event type
287
+ result: Filter by result
288
+ limit: Maximum entries to return
289
+ offset: Number of entries to skip
290
+
291
+ Returns:
292
+ List of WorkflowAuditEntry objects.
293
+ """
294
+ conditions = []
295
+ params: list[Any] = []
296
+
297
+ if session_id:
298
+ conditions.append("session_id = ?")
299
+ params.append(session_id)
300
+ if event_type:
301
+ conditions.append("event_type = ?")
302
+ params.append(event_type)
303
+ if result:
304
+ conditions.append("result = ?")
305
+ params.append(result)
306
+
307
+ where_clause = " AND ".join(conditions) if conditions else "1=1"
308
+ params.extend([limit, offset])
309
+
310
+ # nosec B608: where_clause built from hardcoded condition strings, values parameterized
311
+ rows = self.db.fetchall(
312
+ f"""
313
+ SELECT id, session_id, timestamp, step, event_type, tool_name,
314
+ rule_id, condition, result, reason, context
315
+ FROM workflow_audit_log
316
+ WHERE {where_clause}
317
+ ORDER BY timestamp DESC
318
+ LIMIT ? OFFSET ?
319
+ """, # nosec B608
320
+ tuple(params),
321
+ )
322
+
323
+ entries = []
324
+ for row in rows:
325
+ context_data = {}
326
+ if row["context"]:
327
+ try:
328
+ context_data = json.loads(row["context"])
329
+ except json.JSONDecodeError:
330
+ pass
331
+
332
+ timestamp = (
333
+ datetime.fromisoformat(row["timestamp"]) if row["timestamp"] else datetime.now(UTC)
334
+ )
335
+
336
+ entries.append(
337
+ WorkflowAuditEntry(
338
+ id=row["id"],
339
+ session_id=row["session_id"],
340
+ timestamp=timestamp,
341
+ step=row["step"],
342
+ event_type=row["event_type"],
343
+ tool_name=row["tool_name"],
344
+ rule_id=row["rule_id"],
345
+ condition=row["condition"],
346
+ result=row["result"],
347
+ reason=row["reason"],
348
+ context=context_data,
349
+ )
350
+ )
351
+
352
+ return entries
353
+
354
+ def cleanup_old_entries(self, days: int = 7) -> int:
355
+ """Delete audit entries older than the specified number of days.
356
+
357
+ Args:
358
+ days: Number of days to retain entries (default: 7)
359
+
360
+ Returns:
361
+ Number of entries deleted.
362
+ """
363
+ try:
364
+ cursor = self.db.execute(
365
+ """
366
+ DELETE FROM workflow_audit_log
367
+ WHERE datetime(timestamp) < datetime('now', ? || ' days')
368
+ """,
369
+ (f"-{days}",),
370
+ )
371
+ return cursor.rowcount
372
+ except Exception as e:
373
+ logger.error(f"Failed to cleanup audit entries: {e}")
374
+ return 0
375
+
376
+ def get_entry_count(self, session_id: str | None = None) -> int:
377
+ """Get the total number of audit entries.
378
+
379
+ Args:
380
+ session_id: Optional session ID filter
381
+
382
+ Returns:
383
+ Total count of entries.
384
+ """
385
+ if session_id:
386
+ row = self.db.fetchone(
387
+ "SELECT COUNT(*) as count FROM workflow_audit_log WHERE session_id = ?",
388
+ (session_id,),
389
+ )
390
+ else:
391
+ row = self.db.fetchone("SELECT COUNT(*) as count FROM workflow_audit_log")
392
+
393
+ return row["count"] if row else 0