gdmcode 0.1.0__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 (131) hide show
  1. gdmcode-0.1.0.dist-info/METADATA +240 -0
  2. gdmcode-0.1.0.dist-info/RECORD +131 -0
  3. gdmcode-0.1.0.dist-info/WHEEL +4 -0
  4. gdmcode-0.1.0.dist-info/entry_points.txt +2 -0
  5. src/__init__.py +1 -0
  6. src/_internal/__init__.py +0 -0
  7. src/_internal/constants.py +244 -0
  8. src/_internal/domain_skills.py +339 -0
  9. src/agent/__init__.py +0 -0
  10. src/agent/commit_classifier.py +91 -0
  11. src/agent/context_budget.py +391 -0
  12. src/agent/daemon.py +681 -0
  13. src/agent/dag_validator.py +153 -0
  14. src/agent/debug_loop.py +473 -0
  15. src/agent/impact_analyzer.py +149 -0
  16. src/agent/impact_graph.py +117 -0
  17. src/agent/loop.py +1410 -0
  18. src/agent/orchestrator.py +141 -0
  19. src/agent/regression_guard.py +251 -0
  20. src/agent/review_gate.py +648 -0
  21. src/agent/risk_scorer.py +169 -0
  22. src/agent/self_healing.py +145 -0
  23. src/agent/smart_test_selector.py +89 -0
  24. src/agent/system_prompt.py +226 -0
  25. src/agent/task_tracker.py +320 -0
  26. src/agent/test_validator.py +210 -0
  27. src/agent/tool_orchestrator.py +402 -0
  28. src/agent/transcript.py +230 -0
  29. src/agent/verification_loop.py +133 -0
  30. src/agent/work_director.py +136 -0
  31. src/agent/worktree_manager.py +53 -0
  32. src/artifacts/__init__.py +16 -0
  33. src/artifacts/artifact_store.py +456 -0
  34. src/artifacts/verification_graph.py +75 -0
  35. src/auth.py +411 -0
  36. src/cli.py +1290 -0
  37. src/commands.py +1398 -0
  38. src/config.py +762 -0
  39. src/cost_tracker.py +348 -0
  40. src/db/__init__.py +4 -0
  41. src/db/migrations.py +337 -0
  42. src/enterprise/__init__.py +3 -0
  43. src/enterprise/audit_log.py +182 -0
  44. src/enterprise/identity.py +90 -0
  45. src/enterprise/rbac.py +100 -0
  46. src/enterprise/team_config.py +125 -0
  47. src/enterprise/usage_analytics.py +261 -0
  48. src/exceptions.py +207 -0
  49. src/git_workflow.py +651 -0
  50. src/integrations/__init__.py +6 -0
  51. src/integrations/github_actions.py +106 -0
  52. src/integrations/mcp_server.py +333 -0
  53. src/integrations/sentry_integration.py +100 -0
  54. src/integrations/sentry_server.py +82 -0
  55. src/integrations/webhook_security.py +19 -0
  56. src/main.py +27 -0
  57. src/memory/__init__.py +0 -0
  58. src/memory/code_index.py +376 -0
  59. src/memory/compressor.py +378 -0
  60. src/memory/context_memory.py +135 -0
  61. src/memory/continuous_memory.py +234 -0
  62. src/memory/conventions.py +495 -0
  63. src/memory/db.py +1119 -0
  64. src/memory/document_index.py +205 -0
  65. src/memory/file_cache.py +128 -0
  66. src/memory/project_scanner.py +178 -0
  67. src/memory/session_store.py +201 -0
  68. src/models/__init__.py +0 -0
  69. src/models/client.py +715 -0
  70. src/models/definitions.py +459 -0
  71. src/models/router.py +418 -0
  72. src/models/schemas.py +389 -0
  73. src/permissions.py +294 -0
  74. src/remote/__init__.py +5 -0
  75. src/remote/command_filter.py +33 -0
  76. src/remote/models.py +31 -0
  77. src/remote/permission_handler.py +79 -0
  78. src/remote/phone_ui.py +48 -0
  79. src/remote/protocol.py +59 -0
  80. src/remote/qr.py +65 -0
  81. src/remote/server.py +586 -0
  82. src/remote/token_manager.py +61 -0
  83. src/remote/tunnel.py +212 -0
  84. src/repl.py +475 -0
  85. src/runtime/__init__.py +1 -0
  86. src/runtime/branch_farm.py +372 -0
  87. src/runtime/replay.py +351 -0
  88. src/sandbox/__init__.py +2 -0
  89. src/sandbox/hermetic.py +214 -0
  90. src/sandbox/policy.py +44 -0
  91. src/sdk/__init__.py +3 -0
  92. src/sdk/plugin_base.py +39 -0
  93. src/sdk/plugin_host.py +100 -0
  94. src/sdk/plugin_loader.py +101 -0
  95. src/security.py +409 -0
  96. src/server/__init__.py +7 -0
  97. src/server/bridge.py +427 -0
  98. src/server/bridge_cli.py +103 -0
  99. src/server/bridge_client.py +170 -0
  100. src/server/protocol_version.py +103 -0
  101. src/session/__init__.py +10 -0
  102. src/session/event_fanout.py +46 -0
  103. src/session/input_broker.py +38 -0
  104. src/session/permission_bridge.py +100 -0
  105. src/tools/__init__.py +160 -0
  106. src/tools/_atomic.py +72 -0
  107. src/tools/agent_tools.py +423 -0
  108. src/tools/ask_user_tool.py +83 -0
  109. src/tools/bash_tool.py +384 -0
  110. src/tools/browser_tool.py +352 -0
  111. src/tools/browser_tools.py +179 -0
  112. src/tools/dep_tools.py +210 -0
  113. src/tools/document_reader.py +167 -0
  114. src/tools/document_tool.py +240 -0
  115. src/tools/document_writer.py +171 -0
  116. src/tools/impact_tools.py +240 -0
  117. src/tools/playwright_tool.py +172 -0
  118. src/tools/quality_tools.py +366 -0
  119. src/tools/read_tools.py +318 -0
  120. src/tools/result_cache.py +157 -0
  121. src/tools/search_tools.py +310 -0
  122. src/tools/shell_tools.py +311 -0
  123. src/tools/write_tools.py +337 -0
  124. src/voice/__init__.py +25 -0
  125. src/voice/audio_capture.py +92 -0
  126. src/voice/audio_playback.py +68 -0
  127. src/voice/errors.py +14 -0
  128. src/voice/models.py +35 -0
  129. src/voice/providers.py +143 -0
  130. src/voice/vad.py +55 -0
  131. src/voice/voice_loop.py +156 -0
@@ -0,0 +1,234 @@
1
+ """ContinuousMemory — cross-session developer decision log and hotspot tracking.
2
+
3
+ Stores file-level decisions in a lightweight SQLite DB (.gdm/memory.db),
4
+ separate from the main GdmDatabase. Thread-safe via threading.Lock.
5
+
6
+ Privacy: file paths stored as relative; rows older than 90 days are auto-purged.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import os
12
+ import sqlite3
13
+ import threading
14
+ from datetime import datetime, timezone
15
+ from pathlib import Path
16
+ from typing import TYPE_CHECKING
17
+
18
+ __all__ = ["ContinuousMemory"]
19
+
20
+ log = logging.getLogger(__name__)
21
+
22
+ _DDL = """
23
+ CREATE TABLE IF NOT EXISTS developer_decisions (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ session_id TEXT NOT NULL,
26
+ file_context TEXT,
27
+ decision_type TEXT NOT NULL,
28
+ value TEXT NOT NULL,
29
+ turn_index INTEGER,
30
+ created_at TEXT DEFAULT (datetime('now'))
31
+ );
32
+ CREATE INDEX IF NOT EXISTS idx_decisions_file ON developer_decisions (file_context);
33
+
34
+ CREATE TABLE IF NOT EXISTS file_hotspots (
35
+ file_path TEXT NOT NULL,
36
+ project_id TEXT NOT NULL,
37
+ edit_session_count INTEGER DEFAULT 1,
38
+ total_edit_count INTEGER DEFAULT 1,
39
+ last_edited_at TEXT,
40
+ PRIMARY KEY (file_path, project_id)
41
+ );
42
+ """
43
+
44
+
45
+ class ContinuousMemory:
46
+ """Cross-session developer decision log with file hotspot detection."""
47
+
48
+ def __init__(self, db_path: str = ".gdm/memory.db") -> None:
49
+ self._db_path = db_path
50
+ self._lock = threading.Lock()
51
+ if db_path != ":memory:":
52
+ Path(db_path).parent.mkdir(parents=True, exist_ok=True)
53
+ self._conn = sqlite3.connect(db_path, check_same_thread=False)
54
+ self._conn.row_factory = sqlite3.Row
55
+ self._conn.execute("PRAGMA journal_mode=WAL")
56
+ self._conn.executescript(_DDL)
57
+ self._conn.commit()
58
+
59
+ # ------------------------------------------------------------------
60
+ # Public API
61
+ # ------------------------------------------------------------------
62
+
63
+ def log_decision(
64
+ self,
65
+ session_id: str,
66
+ file_context: str,
67
+ decision_type: str,
68
+ value: str,
69
+ turn_index: int = 0,
70
+ ) -> None:
71
+ """Log a developer decision.
72
+
73
+ Stores relative file path. Purges entries >90 days old on each call.
74
+ """
75
+ rel_context = self._to_rel(file_context) if file_context else file_context
76
+ with self._lock:
77
+ self._conn.execute(
78
+ "DELETE FROM developer_decisions "
79
+ "WHERE created_at < datetime('now', '-90 days')"
80
+ )
81
+ self._conn.execute(
82
+ "INSERT INTO developer_decisions "
83
+ "(session_id, file_context, decision_type, value, turn_index) "
84
+ "VALUES (?, ?, ?, ?, ?)",
85
+ (session_id, rel_context, decision_type, value, turn_index),
86
+ )
87
+ self._conn.commit()
88
+
89
+ def update_hotspot(
90
+ self, session_id: str, file_path: str, project_id: str # noqa: ARG002
91
+ ) -> None:
92
+ """Upsert into file_hotspots, incrementing counters.
93
+
94
+ Hotspot definition: edited in >50% of last 10 sessions (min 3).
95
+ Callers should invoke once per session per written file.
96
+ """
97
+ rel_path = self._to_rel(file_path) if file_path else file_path
98
+ now = datetime.now(timezone.utc).isoformat()
99
+ with self._lock:
100
+ self._conn.execute(
101
+ """
102
+ INSERT INTO file_hotspots
103
+ (file_path, project_id, edit_session_count, total_edit_count, last_edited_at)
104
+ VALUES (?, ?, 1, 1, ?)
105
+ ON CONFLICT(file_path, project_id) DO UPDATE SET
106
+ edit_session_count = edit_session_count + 1,
107
+ total_edit_count = total_edit_count + 1,
108
+ last_edited_at = excluded.last_edited_at
109
+ """,
110
+ (rel_path, project_id, now),
111
+ )
112
+ self._conn.commit()
113
+
114
+ def get_session_hints(
115
+ self, context_files: list[str], project_id: str
116
+ ) -> list[str]:
117
+ """Return <=3 hint strings for the current session.
118
+
119
+ Returns an empty list on cold start (<3 distinct sessions).
120
+ Never raises — all DB errors are swallowed.
121
+ """
122
+ try:
123
+ return self._build_hints(context_files, project_id)
124
+ except Exception as exc: # noqa: BLE001
125
+ log.debug("ContinuousMemory.get_session_hints failed: %s", exc)
126
+ return []
127
+
128
+ def clear(self) -> None:
129
+ """Wipe all rows from developer_decisions and file_hotspots."""
130
+ with self._lock:
131
+ self._conn.execute("DELETE FROM developer_decisions")
132
+ self._conn.execute("DELETE FROM file_hotspots")
133
+ self._conn.commit()
134
+
135
+ # ------------------------------------------------------------------
136
+ # Private helpers
137
+ # ------------------------------------------------------------------
138
+
139
+ def _build_hints(
140
+ self, context_files: list[str], project_id: str
141
+ ) -> list[str]:
142
+ session_count = self._count_sessions()
143
+ if session_count < 3:
144
+ return []
145
+
146
+ hints: list[str] = []
147
+ window = min(session_count, 10)
148
+ threshold = window * 0.5
149
+
150
+ # Hotspot hints — limit to relevant context files first
151
+ rel_files = [
152
+ self._to_rel(f) if f else f
153
+ for f in (context_files or [])
154
+ ]
155
+ if rel_files:
156
+ placeholders = ",".join("?" * len(rel_files))
157
+ rows = self._conn.execute(
158
+ f"SELECT file_path, edit_session_count FROM file_hotspots "
159
+ f"WHERE project_id = ? AND file_path IN ({placeholders}) "
160
+ f"ORDER BY edit_session_count DESC",
161
+ (project_id, *rel_files),
162
+ ).fetchall()
163
+ for row in rows:
164
+ if row["edit_session_count"] >= 3 and row["edit_session_count"] > threshold:
165
+ hints.append(
166
+ f"{row['file_path']} was edited in "
167
+ f"{row['edit_session_count']}/{window} recent sessions "
168
+ f"\u2014 likely a hotspot"
169
+ )
170
+ if len(hints) >= 3:
171
+ return hints
172
+
173
+ # Fallback hotspot hints when no context files provided
174
+ if not rel_files and len(hints) < 3:
175
+ rows = self._conn.execute(
176
+ "SELECT file_path, edit_session_count FROM file_hotspots "
177
+ "WHERE project_id = ? AND edit_session_count >= 3 "
178
+ "ORDER BY edit_session_count DESC LIMIT 3",
179
+ (project_id,),
180
+ ).fetchall()
181
+ for row in rows:
182
+ if row["edit_session_count"] > threshold:
183
+ hints.append(
184
+ f"{row['file_path']} was edited in "
185
+ f"{row['edit_session_count']}/{window} recent sessions "
186
+ f"\u2014 likely a hotspot"
187
+ )
188
+ if len(hints) >= 3:
189
+ return hints
190
+
191
+ # Decision hints for remaining slots
192
+ if len(hints) < 3:
193
+ if rel_files:
194
+ placeholders = ",".join("?" * len(rel_files))
195
+ dec_rows = self._conn.execute(
196
+ f"SELECT decision_type, value FROM developer_decisions "
197
+ f"WHERE file_context IN ({placeholders}) "
198
+ f"ORDER BY created_at DESC LIMIT 20",
199
+ tuple(rel_files),
200
+ ).fetchall()
201
+ else:
202
+ dec_rows = self._conn.execute(
203
+ "SELECT decision_type, value FROM developer_decisions "
204
+ "ORDER BY created_at DESC LIMIT 20"
205
+ ).fetchall()
206
+ seen: set[str] = set()
207
+ for row in dec_rows:
208
+ key = f"{row['decision_type']}:{row['value']}"
209
+ if key not in seen:
210
+ seen.add(key)
211
+ hints.append(
212
+ f"Last session used {row['value']} for {row['decision_type']}"
213
+ )
214
+ if len(hints) >= 3:
215
+ break
216
+
217
+ return hints[:3]
218
+
219
+ def _count_sessions(self) -> int:
220
+ row = self._conn.execute(
221
+ "SELECT COUNT(DISTINCT session_id) AS n FROM developer_decisions"
222
+ ).fetchone()
223
+ return row["n"] if row else 0
224
+
225
+ @staticmethod
226
+ def _to_rel(path: str) -> str:
227
+ """Convert an absolute path to relative, with cross-drive fallback on Windows."""
228
+ if not path or not os.path.isabs(path):
229
+ return path
230
+ try:
231
+ return os.path.relpath(path)
232
+ except ValueError:
233
+ # Different drives on Windows — use basename as fallback
234
+ return os.path.basename(path)