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.
- gdmcode-0.1.0.dist-info/METADATA +240 -0
- gdmcode-0.1.0.dist-info/RECORD +131 -0
- gdmcode-0.1.0.dist-info/WHEEL +4 -0
- gdmcode-0.1.0.dist-info/entry_points.txt +2 -0
- src/__init__.py +1 -0
- src/_internal/__init__.py +0 -0
- src/_internal/constants.py +244 -0
- src/_internal/domain_skills.py +339 -0
- src/agent/__init__.py +0 -0
- src/agent/commit_classifier.py +91 -0
- src/agent/context_budget.py +391 -0
- src/agent/daemon.py +681 -0
- src/agent/dag_validator.py +153 -0
- src/agent/debug_loop.py +473 -0
- src/agent/impact_analyzer.py +149 -0
- src/agent/impact_graph.py +117 -0
- src/agent/loop.py +1410 -0
- src/agent/orchestrator.py +141 -0
- src/agent/regression_guard.py +251 -0
- src/agent/review_gate.py +648 -0
- src/agent/risk_scorer.py +169 -0
- src/agent/self_healing.py +145 -0
- src/agent/smart_test_selector.py +89 -0
- src/agent/system_prompt.py +226 -0
- src/agent/task_tracker.py +320 -0
- src/agent/test_validator.py +210 -0
- src/agent/tool_orchestrator.py +402 -0
- src/agent/transcript.py +230 -0
- src/agent/verification_loop.py +133 -0
- src/agent/work_director.py +136 -0
- src/agent/worktree_manager.py +53 -0
- src/artifacts/__init__.py +16 -0
- src/artifacts/artifact_store.py +456 -0
- src/artifacts/verification_graph.py +75 -0
- src/auth.py +411 -0
- src/cli.py +1290 -0
- src/commands.py +1398 -0
- src/config.py +762 -0
- src/cost_tracker.py +348 -0
- src/db/__init__.py +4 -0
- src/db/migrations.py +337 -0
- src/enterprise/__init__.py +3 -0
- src/enterprise/audit_log.py +182 -0
- src/enterprise/identity.py +90 -0
- src/enterprise/rbac.py +100 -0
- src/enterprise/team_config.py +125 -0
- src/enterprise/usage_analytics.py +261 -0
- src/exceptions.py +207 -0
- src/git_workflow.py +651 -0
- src/integrations/__init__.py +6 -0
- src/integrations/github_actions.py +106 -0
- src/integrations/mcp_server.py +333 -0
- src/integrations/sentry_integration.py +100 -0
- src/integrations/sentry_server.py +82 -0
- src/integrations/webhook_security.py +19 -0
- src/main.py +27 -0
- src/memory/__init__.py +0 -0
- src/memory/code_index.py +376 -0
- src/memory/compressor.py +378 -0
- src/memory/context_memory.py +135 -0
- src/memory/continuous_memory.py +234 -0
- src/memory/conventions.py +495 -0
- src/memory/db.py +1119 -0
- src/memory/document_index.py +205 -0
- src/memory/file_cache.py +128 -0
- src/memory/project_scanner.py +178 -0
- src/memory/session_store.py +201 -0
- src/models/__init__.py +0 -0
- src/models/client.py +715 -0
- src/models/definitions.py +459 -0
- src/models/router.py +418 -0
- src/models/schemas.py +389 -0
- src/permissions.py +294 -0
- src/remote/__init__.py +5 -0
- src/remote/command_filter.py +33 -0
- src/remote/models.py +31 -0
- src/remote/permission_handler.py +79 -0
- src/remote/phone_ui.py +48 -0
- src/remote/protocol.py +59 -0
- src/remote/qr.py +65 -0
- src/remote/server.py +586 -0
- src/remote/token_manager.py +61 -0
- src/remote/tunnel.py +212 -0
- src/repl.py +475 -0
- src/runtime/__init__.py +1 -0
- src/runtime/branch_farm.py +372 -0
- src/runtime/replay.py +351 -0
- src/sandbox/__init__.py +2 -0
- src/sandbox/hermetic.py +214 -0
- src/sandbox/policy.py +44 -0
- src/sdk/__init__.py +3 -0
- src/sdk/plugin_base.py +39 -0
- src/sdk/plugin_host.py +100 -0
- src/sdk/plugin_loader.py +101 -0
- src/security.py +409 -0
- src/server/__init__.py +7 -0
- src/server/bridge.py +427 -0
- src/server/bridge_cli.py +103 -0
- src/server/bridge_client.py +170 -0
- src/server/protocol_version.py +103 -0
- src/session/__init__.py +10 -0
- src/session/event_fanout.py +46 -0
- src/session/input_broker.py +38 -0
- src/session/permission_bridge.py +100 -0
- src/tools/__init__.py +160 -0
- src/tools/_atomic.py +72 -0
- src/tools/agent_tools.py +423 -0
- src/tools/ask_user_tool.py +83 -0
- src/tools/bash_tool.py +384 -0
- src/tools/browser_tool.py +352 -0
- src/tools/browser_tools.py +179 -0
- src/tools/dep_tools.py +210 -0
- src/tools/document_reader.py +167 -0
- src/tools/document_tool.py +240 -0
- src/tools/document_writer.py +171 -0
- src/tools/impact_tools.py +240 -0
- src/tools/playwright_tool.py +172 -0
- src/tools/quality_tools.py +366 -0
- src/tools/read_tools.py +318 -0
- src/tools/result_cache.py +157 -0
- src/tools/search_tools.py +310 -0
- src/tools/shell_tools.py +311 -0
- src/tools/write_tools.py +337 -0
- src/voice/__init__.py +25 -0
- src/voice/audio_capture.py +92 -0
- src/voice/audio_playback.py +68 -0
- src/voice/errors.py +14 -0
- src/voice/models.py +35 -0
- src/voice/providers.py +143 -0
- src/voice/vad.py +55 -0
- 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)
|