codeframe-ai 0.9.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 (197) hide show
  1. codeframe/__init__.py +11 -0
  2. codeframe/__main__.py +20 -0
  3. codeframe/adapters/__init__.py +5 -0
  4. codeframe/adapters/e2b/__init__.py +13 -0
  5. codeframe/adapters/e2b/adapter.py +342 -0
  6. codeframe/adapters/e2b/budget.py +71 -0
  7. codeframe/adapters/e2b/credential_scanner.py +134 -0
  8. codeframe/adapters/llm/__init__.py +92 -0
  9. codeframe/adapters/llm/anthropic.py +414 -0
  10. codeframe/adapters/llm/base.py +444 -0
  11. codeframe/adapters/llm/mock.py +281 -0
  12. codeframe/adapters/llm/openai.py +483 -0
  13. codeframe/agents/__init__.py +8 -0
  14. codeframe/agents/dependency_resolver.py +714 -0
  15. codeframe/auth/__init__.py +16 -0
  16. codeframe/auth/api_key_router.py +238 -0
  17. codeframe/auth/api_keys.py +156 -0
  18. codeframe/auth/dependencies.py +358 -0
  19. codeframe/auth/manager.py +178 -0
  20. codeframe/auth/models.py +30 -0
  21. codeframe/auth/router.py +93 -0
  22. codeframe/auth/schemas.py +15 -0
  23. codeframe/auth/scopes.py +53 -0
  24. codeframe/cli/__init__.py +12 -0
  25. codeframe/cli/__main__.py +20 -0
  26. codeframe/cli/api_client.py +275 -0
  27. codeframe/cli/app.py +5688 -0
  28. codeframe/cli/auth.py +122 -0
  29. codeframe/cli/auth_commands.py +958 -0
  30. codeframe/cli/commands/__init__.py +5 -0
  31. codeframe/cli/config_commands.py +79 -0
  32. codeframe/cli/dashboard_commands.py +67 -0
  33. codeframe/cli/engines_commands.py +205 -0
  34. codeframe/cli/env_commands.py +409 -0
  35. codeframe/cli/helpers.py +56 -0
  36. codeframe/cli/hooks_commands.py +208 -0
  37. codeframe/cli/import_commands.py +129 -0
  38. codeframe/cli/pr_commands.py +549 -0
  39. codeframe/cli/proof_commands.py +415 -0
  40. codeframe/cli/stats_commands.py +311 -0
  41. codeframe/cli/telemetry_runtime.py +153 -0
  42. codeframe/cli/validators.py +123 -0
  43. codeframe/config/rate_limits.py +165 -0
  44. codeframe/core/__init__.py +15 -0
  45. codeframe/core/adapters/__init__.py +43 -0
  46. codeframe/core/adapters/agent_adapter.py +114 -0
  47. codeframe/core/adapters/builtin.py +326 -0
  48. codeframe/core/adapters/claude_code.py +62 -0
  49. codeframe/core/adapters/codex.py +393 -0
  50. codeframe/core/adapters/git_utils.py +40 -0
  51. codeframe/core/adapters/kilocode.py +126 -0
  52. codeframe/core/adapters/opencode.py +48 -0
  53. codeframe/core/adapters/streaming_chat.py +483 -0
  54. codeframe/core/adapters/subprocess_adapter.py +213 -0
  55. codeframe/core/adapters/verification_wrapper.py +269 -0
  56. codeframe/core/agent.py +2183 -0
  57. codeframe/core/agents_config.py +569 -0
  58. codeframe/core/api_key_service.py +211 -0
  59. codeframe/core/artifacts.py +428 -0
  60. codeframe/core/blocker_detection.py +218 -0
  61. codeframe/core/blockers.py +433 -0
  62. codeframe/core/checkpoints.py +481 -0
  63. codeframe/core/conductor.py +2255 -0
  64. codeframe/core/config.py +827 -0
  65. codeframe/core/config_watcher.py +268 -0
  66. codeframe/core/context.py +542 -0
  67. codeframe/core/context_packager.py +234 -0
  68. codeframe/core/credentials.py +735 -0
  69. codeframe/core/dependency_analyzer.py +229 -0
  70. codeframe/core/dependency_graph.py +290 -0
  71. codeframe/core/diagnostic_agent.py +712 -0
  72. codeframe/core/diagnostics.py +616 -0
  73. codeframe/core/editor.py +556 -0
  74. codeframe/core/engine_registry.py +256 -0
  75. codeframe/core/engine_stats.py +231 -0
  76. codeframe/core/environment.py +697 -0
  77. codeframe/core/events.py +375 -0
  78. codeframe/core/executor.py +1005 -0
  79. codeframe/core/fix_tracker.py +480 -0
  80. codeframe/core/gates.py +1322 -0
  81. codeframe/core/git.py +477 -0
  82. codeframe/core/github_connect_service.py +178 -0
  83. codeframe/core/github_integration_config.py +118 -0
  84. codeframe/core/github_issues_service.py +449 -0
  85. codeframe/core/hooks.py +184 -0
  86. codeframe/core/importers/__init__.py +1 -0
  87. codeframe/core/importers/ralph.py +540 -0
  88. codeframe/core/installer.py +650 -0
  89. codeframe/core/models.py +1026 -0
  90. codeframe/core/notifications_config.py +183 -0
  91. codeframe/core/planner.py +437 -0
  92. codeframe/core/prd.py +670 -0
  93. codeframe/core/prd_discovery.py +1118 -0
  94. codeframe/core/prd_stress_test.py +499 -0
  95. codeframe/core/progress.py +126 -0
  96. codeframe/core/proof/__init__.py +34 -0
  97. codeframe/core/proof/capture.py +79 -0
  98. codeframe/core/proof/evidence.py +56 -0
  99. codeframe/core/proof/ledger.py +574 -0
  100. codeframe/core/proof/models.py +162 -0
  101. codeframe/core/proof/obligations.py +103 -0
  102. codeframe/core/proof/runner.py +233 -0
  103. codeframe/core/proof/scope.py +81 -0
  104. codeframe/core/proof/stubs.py +156 -0
  105. codeframe/core/quick_fixes.py +558 -0
  106. codeframe/core/react_agent.py +1650 -0
  107. codeframe/core/reconciliation.py +183 -0
  108. codeframe/core/replay.py +788 -0
  109. codeframe/core/review.py +285 -0
  110. codeframe/core/runtime.py +1134 -0
  111. codeframe/core/sandbox/__init__.py +27 -0
  112. codeframe/core/sandbox/context.py +98 -0
  113. codeframe/core/sandbox/worktree.py +20 -0
  114. codeframe/core/schedule.py +396 -0
  115. codeframe/core/stall_detector.py +71 -0
  116. codeframe/core/stall_monitor.py +134 -0
  117. codeframe/core/state_machine.py +121 -0
  118. codeframe/core/streaming.py +502 -0
  119. codeframe/core/task_tree.py +400 -0
  120. codeframe/core/tasks.py +1022 -0
  121. codeframe/core/telemetry.py +232 -0
  122. codeframe/core/templates.py +221 -0
  123. codeframe/core/tools.py +942 -0
  124. codeframe/core/workspace.py +887 -0
  125. codeframe/core/worktrees.py +276 -0
  126. codeframe/git/__init__.py +5 -0
  127. codeframe/git/github_integration.py +505 -0
  128. codeframe/lib/__init__.py +0 -0
  129. codeframe/lib/audit_logger.py +248 -0
  130. codeframe/lib/metrics_tracker.py +800 -0
  131. codeframe/lib/quality/__init__.py +7 -0
  132. codeframe/lib/quality/complexity_analyzer.py +316 -0
  133. codeframe/lib/quality/owasp_patterns.py +284 -0
  134. codeframe/lib/quality/security_scanner.py +250 -0
  135. codeframe/lib/rate_limiter.py +312 -0
  136. codeframe/notifications/__init__.py +0 -0
  137. codeframe/notifications/webhook.py +380 -0
  138. codeframe/planning/__init__.py +30 -0
  139. codeframe/planning/issue_generator.py +219 -0
  140. codeframe/planning/prd_template_functions.py +137 -0
  141. codeframe/planning/prd_templates.py +975 -0
  142. codeframe/planning/task_scheduler.py +511 -0
  143. codeframe/planning/task_templates.py +533 -0
  144. codeframe/platform_store/__init__.py +5 -0
  145. codeframe/platform_store/database.py +277 -0
  146. codeframe/platform_store/repositories/__init__.py +24 -0
  147. codeframe/platform_store/repositories/api_key_repository.py +245 -0
  148. codeframe/platform_store/repositories/audit_repository.py +67 -0
  149. codeframe/platform_store/repositories/base.py +295 -0
  150. codeframe/platform_store/repositories/interactive_sessions.py +165 -0
  151. codeframe/platform_store/repositories/token_repository.py +598 -0
  152. codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
  153. codeframe/platform_store/schema_manager.py +321 -0
  154. codeframe/templates/AGENTS.md.default +94 -0
  155. codeframe/tui/__init__.py +5 -0
  156. codeframe/tui/app.py +256 -0
  157. codeframe/tui/data_service.py +103 -0
  158. codeframe/ui/__init__.py +0 -0
  159. codeframe/ui/dependencies.py +103 -0
  160. codeframe/ui/models.py +999 -0
  161. codeframe/ui/response_models.py +201 -0
  162. codeframe/ui/routers/__init__.py +5 -0
  163. codeframe/ui/routers/_helpers.py +29 -0
  164. codeframe/ui/routers/batches_v2.py +315 -0
  165. codeframe/ui/routers/blockers_v2.py +320 -0
  166. codeframe/ui/routers/checkpoints_v2.py +310 -0
  167. codeframe/ui/routers/costs_v2.py +322 -0
  168. codeframe/ui/routers/diagnose_v2.py +225 -0
  169. codeframe/ui/routers/discovery_v2.py +417 -0
  170. codeframe/ui/routers/environment_v2.py +284 -0
  171. codeframe/ui/routers/events_v2.py +75 -0
  172. codeframe/ui/routers/gates_v2.py +166 -0
  173. codeframe/ui/routers/git_v2.py +284 -0
  174. codeframe/ui/routers/github_integrations_v2.py +532 -0
  175. codeframe/ui/routers/interactive_sessions_v2.py +238 -0
  176. codeframe/ui/routers/pr_v2.py +709 -0
  177. codeframe/ui/routers/prd_v2.py +695 -0
  178. codeframe/ui/routers/proof_v2.py +755 -0
  179. codeframe/ui/routers/review_v2.py +360 -0
  180. codeframe/ui/routers/schedule_v2.py +214 -0
  181. codeframe/ui/routers/session_chat_ws.py +354 -0
  182. codeframe/ui/routers/settings_v2.py +562 -0
  183. codeframe/ui/routers/streaming_v2.py +155 -0
  184. codeframe/ui/routers/tasks_v2.py +1098 -0
  185. codeframe/ui/routers/templates_v2.py +232 -0
  186. codeframe/ui/routers/terminal_ws.py +267 -0
  187. codeframe/ui/routers/workspace_v2.py +527 -0
  188. codeframe/ui/server.py +568 -0
  189. codeframe/ui/shared.py +241 -0
  190. codeframe/workspace/__init__.py +5 -0
  191. codeframe/workspace/manager.py +249 -0
  192. codeframe_ai-0.9.0.dist-info/METADATA +517 -0
  193. codeframe_ai-0.9.0.dist-info/RECORD +197 -0
  194. codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
  195. codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
  196. codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
  197. codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,175 @@
1
+ """Repository for the workspace registry (issue #601).
2
+
3
+ The registry lives in the global control-plane DB and stores only project
4
+ metadata + a pointer (``repo_path``) to each per-workspace
5
+ ``.codeframe/state.db``. It enables server-side listing of "your projects" so
6
+ the web UI switcher can be backed by the server (with a localStorage fallback)
7
+ instead of living purely in the browser.
8
+
9
+ It is intentionally NOT a revival of the v1 global ``projects`` table — no
10
+ domain data is stored here, only pointers and metadata.
11
+ """
12
+
13
+ import uuid
14
+ from datetime import datetime, timezone
15
+ from typing import Any, Dict, List, Optional
16
+ import logging
17
+
18
+ from codeframe.platform_store.repositories.base import BaseRepository
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class WorkspaceRegistryRepository(BaseRepository):
24
+ """Repository for workspace registry operations."""
25
+
26
+ def upsert(
27
+ self,
28
+ repo_path: str,
29
+ name: Optional[str] = None,
30
+ tech_stack: Optional[str] = None,
31
+ owner_user_id: Optional[int] = None,
32
+ ) -> Dict[str, Any]:
33
+ """Register (or refresh) a workspace by its repo path.
34
+
35
+ Idempotent on ``repo_path``: a second call for the same path updates the
36
+ existing row (name/tech_stack/owner + ``last_opened_at``) instead of
37
+ creating a duplicate, preserving the original ``id`` and ``created_at``.
38
+
39
+ Args:
40
+ repo_path: Absolute path to the repository (unique key).
41
+ name: Human-readable display name (defaults to last path segment).
42
+ tech_stack: Natural-language tech stack description.
43
+ owner_user_id: Owning user id (nullable until auth is enforced).
44
+
45
+ Returns:
46
+ The registry entry as a dict.
47
+ """
48
+ now = datetime.now(timezone.utc).isoformat()
49
+ new_id = str(uuid.uuid4())
50
+
51
+ # INSERT ... ON CONFLICT(repo_path) DO UPDATE keeps the original id and
52
+ # created_at while refreshing the mutable metadata and recency stamp.
53
+ self._execute(
54
+ """
55
+ INSERT INTO workspaces_registry (
56
+ id, repo_path, name, owner_user_id, tech_stack,
57
+ created_at, last_opened_at
58
+ )
59
+ VALUES (?, ?, ?, ?, ?, ?, ?)
60
+ ON CONFLICT(repo_path) DO UPDATE SET
61
+ -- COALESCE so a refresh that omits name/tech_stack (None) keeps the
62
+ -- previously-stored value instead of nulling it.
63
+ name = COALESCE(excluded.name, workspaces_registry.name),
64
+ owner_user_id = excluded.owner_user_id,
65
+ tech_stack = COALESCE(excluded.tech_stack, workspaces_registry.tech_stack),
66
+ last_opened_at = excluded.last_opened_at
67
+ """,
68
+ (new_id, repo_path, name, owner_user_id, tech_stack, now, now),
69
+ )
70
+ self._commit()
71
+
72
+ # Re-read so callers always get the canonical row (stable id on conflict).
73
+ entry = self.get_by_path(repo_path)
74
+ if entry is None:
75
+ # Should be unreachable right after a successful upsert. Raise a real
76
+ # error (not assert, which `python -O` strips) so the dict-return
77
+ # contract holds in every execution mode.
78
+ raise RuntimeError(
79
+ f"workspaces_registry upsert succeeded but row not found: {repo_path}"
80
+ )
81
+ return entry
82
+
83
+ def list_all(self, owner_user_id: Optional[int] = None) -> List[Dict[str, Any]]:
84
+ """List all registered workspaces, optionally filtered by owner.
85
+
86
+ Args:
87
+ owner_user_id: When provided, only entries owned by this user are
88
+ returned. When ``None``, all entries are returned (the current
89
+ behaviour until auth is enforced on v2 routers).
90
+
91
+ Returns:
92
+ List of registry entries ordered by ``last_opened_at`` descending.
93
+ """
94
+ if owner_user_id is not None:
95
+ rows = self._fetchall(
96
+ """
97
+ SELECT id, repo_path, name, owner_user_id, tech_stack,
98
+ created_at, last_opened_at
99
+ FROM workspaces_registry
100
+ WHERE owner_user_id = ?
101
+ ORDER BY last_opened_at DESC
102
+ """,
103
+ (owner_user_id,),
104
+ )
105
+ else:
106
+ rows = self._fetchall(
107
+ """
108
+ SELECT id, repo_path, name, owner_user_id, tech_stack,
109
+ created_at, last_opened_at
110
+ FROM workspaces_registry
111
+ ORDER BY last_opened_at DESC
112
+ """
113
+ )
114
+
115
+ return [self._row_to_workspace_registry(row) for row in rows]
116
+
117
+ def get_by_id(self, workspace_id: str) -> Optional[Dict[str, Any]]:
118
+ """Get a registry entry by its id, or None if not found."""
119
+ row = self._fetchone(
120
+ """
121
+ SELECT id, repo_path, name, owner_user_id, tech_stack,
122
+ created_at, last_opened_at
123
+ FROM workspaces_registry
124
+ WHERE id = ?
125
+ """,
126
+ (workspace_id,),
127
+ )
128
+ return self._row_to_workspace_registry(row) if row is not None else None
129
+
130
+ def get_by_path(self, repo_path: str) -> Optional[Dict[str, Any]]:
131
+ """Get a registry entry by its repo path, or None if not found."""
132
+ row = self._fetchone(
133
+ """
134
+ SELECT id, repo_path, name, owner_user_id, tech_stack,
135
+ created_at, last_opened_at
136
+ FROM workspaces_registry
137
+ WHERE repo_path = ?
138
+ """,
139
+ (repo_path,),
140
+ )
141
+ return self._row_to_workspace_registry(row) if row is not None else None
142
+
143
+ def update_last_opened(self, workspace_id: str) -> None:
144
+ """Bump ``last_opened_at`` for a registry entry to maintain recency."""
145
+ now = datetime.now(timezone.utc).isoformat()
146
+ self._execute(
147
+ "UPDATE workspaces_registry SET last_opened_at = ? WHERE id = ?",
148
+ (now, workspace_id),
149
+ )
150
+ self._commit()
151
+
152
+ def delete(self, workspace_id: str) -> bool:
153
+ """Deregister a workspace (registry-only — never touches disk files).
154
+
155
+ Returns:
156
+ True if a row was removed, False if the id was not found.
157
+ """
158
+ cursor = self._execute(
159
+ "DELETE FROM workspaces_registry WHERE id = ?",
160
+ (workspace_id,),
161
+ )
162
+ self._commit()
163
+ return cursor.rowcount > 0
164
+
165
+ def _row_to_workspace_registry(self, row) -> Dict[str, Any]:
166
+ """Convert a database row to a registry dict."""
167
+ return {
168
+ "id": row["id"],
169
+ "repo_path": row["repo_path"],
170
+ "name": row["name"],
171
+ "owner_user_id": row["owner_user_id"],
172
+ "tech_stack": row["tech_stack"],
173
+ "created_at": self._ensure_rfc3339(row["created_at"]),
174
+ "last_opened_at": self._ensure_rfc3339(row["last_opened_at"]),
175
+ }
@@ -0,0 +1,321 @@
1
+ """Database schema management for CodeFRAME.
2
+
3
+ Handles schema creation, migrations, and database initialization.
4
+ Extracted from the monolithic Database class for better maintainability.
5
+ """
6
+
7
+ import sqlite3
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class SchemaManager:
14
+ """Manages database schema creation and migrations.
15
+
16
+ Responsible for creating all database tables, indexes, and ensuring
17
+ schema consistency across the application.
18
+ """
19
+
20
+ def __init__(self, conn: sqlite3.Connection):
21
+ """Initialize schema manager with database connection.
22
+
23
+ Args:
24
+ conn: Active sqlite3.Connection
25
+ """
26
+ self.conn = conn
27
+
28
+ def create_schema(self) -> None:
29
+ """Create all database tables and indexes.
30
+
31
+ Creates the minimal control-plane schema (auth, api keys, audit log,
32
+ interactive sessions). v2 domain data lives in per-workspace DBs.
33
+ This method is idempotent - safe to call multiple times.
34
+ """
35
+ cursor = self.conn.cursor()
36
+
37
+ # Authentication tables (users, accounts, sessions, verification, api_keys)
38
+ self._create_auth_tables(cursor)
39
+
40
+ # Audit log table
41
+ self._create_audit_log_table(cursor)
42
+
43
+ # Interactive session tables
44
+ self._create_interactive_session_tables(cursor)
45
+
46
+ # Workspace registry table (issue #601)
47
+ self._create_workspaces_registry_table(cursor)
48
+
49
+ # Create indexes
50
+ self._create_indexes(cursor)
51
+
52
+ self.conn.commit()
53
+
54
+ # Ensure default admin user exists
55
+ self._ensure_default_admin_user()
56
+
57
+ def _create_auth_tables(self, cursor: sqlite3.Cursor) -> None:
58
+ """Create authentication tables (fastapi-users compatible)."""
59
+ cursor.execute(
60
+ """
61
+ CREATE TABLE IF NOT EXISTS users (
62
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
63
+ email TEXT UNIQUE NOT NULL,
64
+ name TEXT,
65
+ hashed_password TEXT NOT NULL,
66
+ is_active INTEGER DEFAULT 1,
67
+ is_superuser INTEGER DEFAULT 0,
68
+ is_verified INTEGER DEFAULT 0,
69
+ email_verified INTEGER DEFAULT 0,
70
+ image TEXT,
71
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
72
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
73
+ )
74
+ """
75
+ )
76
+
77
+ # Accounts table (BetterAuth compatible - stores passwords and OAuth)
78
+ cursor.execute(
79
+ """
80
+ CREATE TABLE IF NOT EXISTS accounts (
81
+ id TEXT PRIMARY KEY,
82
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
83
+ account_id TEXT NOT NULL,
84
+ provider_id TEXT NOT NULL,
85
+ password TEXT,
86
+ access_token TEXT,
87
+ refresh_token TEXT,
88
+ id_token TEXT,
89
+ access_token_expires_at TIMESTAMP,
90
+ refresh_token_expires_at TIMESTAMP,
91
+ scope TEXT,
92
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
93
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
94
+ UNIQUE(user_id, provider_id)
95
+ )
96
+ """
97
+ )
98
+
99
+ # Create index on user_id for faster login lookups
100
+ cursor.execute(
101
+ """
102
+ CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id)
103
+ """
104
+ )
105
+
106
+ # Sessions table (BetterAuth compatible)
107
+ cursor.execute(
108
+ """
109
+ CREATE TABLE IF NOT EXISTS sessions (
110
+ id TEXT PRIMARY KEY,
111
+ token TEXT UNIQUE NOT NULL,
112
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
113
+ expires_at TIMESTAMP NOT NULL,
114
+ ip_address TEXT,
115
+ user_agent TEXT,
116
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
117
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
118
+ )
119
+ """
120
+ )
121
+
122
+ # Verification table (BetterAuth compatible)
123
+ # Used for email verification tokens when requireEmailVerification is enabled
124
+ cursor.execute(
125
+ """
126
+ CREATE TABLE IF NOT EXISTS verification (
127
+ id TEXT PRIMARY KEY,
128
+ identifier TEXT NOT NULL,
129
+ value TEXT NOT NULL,
130
+ expires_at TIMESTAMP NOT NULL
131
+ )
132
+ """
133
+ )
134
+
135
+ # API Keys table for programmatic access
136
+ cursor.execute(
137
+ """
138
+ CREATE TABLE IF NOT EXISTS api_keys (
139
+ id TEXT PRIMARY KEY,
140
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
141
+ name TEXT NOT NULL,
142
+ key_hash TEXT NOT NULL,
143
+ prefix TEXT NOT NULL,
144
+ scopes TEXT NOT NULL,
145
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
146
+ last_used_at TIMESTAMP,
147
+ expires_at TIMESTAMP,
148
+ is_active INTEGER DEFAULT 1
149
+ )
150
+ """
151
+ )
152
+
153
+ # Indexes for api_keys table
154
+ cursor.execute(
155
+ """
156
+ CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix)
157
+ """
158
+ )
159
+ cursor.execute(
160
+ """
161
+ CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id)
162
+ """
163
+ )
164
+
165
+ def _create_interactive_session_tables(self, cursor: sqlite3.Cursor) -> None:
166
+ """Create interactive_sessions and session_messages tables."""
167
+ cursor.execute(
168
+ """
169
+ CREATE TABLE IF NOT EXISTS interactive_sessions (
170
+ id TEXT PRIMARY KEY,
171
+ workspace_path TEXT NOT NULL,
172
+ task_id TEXT,
173
+ state TEXT NOT NULL DEFAULT 'active'
174
+ CHECK (state IN ('active', 'paused', 'ended')),
175
+ agent_type TEXT NOT NULL DEFAULT 'claude',
176
+ model TEXT,
177
+ cost_usd REAL DEFAULT 0.0,
178
+ input_tokens INTEGER DEFAULT 0,
179
+ output_tokens INTEGER DEFAULT 0,
180
+ created_at TEXT NOT NULL,
181
+ updated_at TEXT NOT NULL,
182
+ ended_at TEXT
183
+ )
184
+ """
185
+ )
186
+
187
+ cursor.execute(
188
+ """
189
+ CREATE TABLE IF NOT EXISTS session_messages (
190
+ id TEXT PRIMARY KEY,
191
+ session_id TEXT NOT NULL REFERENCES interactive_sessions(id) ON DELETE CASCADE,
192
+ role TEXT NOT NULL
193
+ CHECK (role IN ('user', 'assistant', 'tool_use', 'tool_result', 'thinking', 'system', 'error')),
194
+ content TEXT NOT NULL,
195
+ metadata TEXT,
196
+ created_at TEXT NOT NULL
197
+ )
198
+ """
199
+ )
200
+
201
+ def _create_workspaces_registry_table(self, cursor: sqlite3.Cursor) -> None:
202
+ """Create the workspaces_registry table (issue #601).
203
+
204
+ Stores cross-workspace, cross-device project metadata plus a pointer
205
+ (``repo_path``) to each per-workspace ``.codeframe/state.db``. It does
206
+ NOT hold any domain data — per-workspace isolation is unchanged. This is
207
+ deliberately not a revival of the v1 global ``projects`` table.
208
+ """
209
+ cursor.execute(
210
+ """
211
+ CREATE TABLE IF NOT EXISTS workspaces_registry (
212
+ id TEXT PRIMARY KEY,
213
+ repo_path TEXT UNIQUE NOT NULL,
214
+ name TEXT,
215
+ owner_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
216
+ tech_stack TEXT,
217
+ created_at TEXT NOT NULL,
218
+ last_opened_at TEXT NOT NULL
219
+ )
220
+ """
221
+ )
222
+
223
+ def _create_audit_log_table(self, cursor: sqlite3.Cursor) -> None:
224
+ """Create the audit log table."""
225
+ cursor.execute(
226
+ """
227
+ CREATE TABLE IF NOT EXISTS audit_logs (
228
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
229
+ event_type TEXT NOT NULL,
230
+ user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
231
+ resource_type TEXT NOT NULL,
232
+ resource_id INTEGER,
233
+ ip_address TEXT,
234
+ metadata TEXT,
235
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
236
+ )
237
+ """
238
+ )
239
+
240
+ def _create_indexes(self, cursor: sqlite3.Cursor) -> None:
241
+ """Create indexes for the live platform tables."""
242
+ # Interactive session indexes
243
+ cursor.execute(
244
+ "CREATE INDEX IF NOT EXISTS idx_interactive_sessions_workspace "
245
+ "ON interactive_sessions(workspace_path, state)"
246
+ )
247
+ cursor.execute(
248
+ "CREATE INDEX IF NOT EXISTS idx_interactive_sessions_state "
249
+ "ON interactive_sessions(state, created_at DESC)"
250
+ )
251
+ cursor.execute(
252
+ "CREATE INDEX IF NOT EXISTS idx_session_messages_session "
253
+ "ON session_messages(session_id, created_at)"
254
+ )
255
+
256
+ # Audit log indexes
257
+ cursor.execute(
258
+ "CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id "
259
+ "ON audit_logs(user_id, timestamp DESC)"
260
+ )
261
+ cursor.execute(
262
+ "CREATE INDEX IF NOT EXISTS idx_audit_logs_event_type "
263
+ "ON audit_logs(event_type, timestamp DESC)"
264
+ )
265
+ cursor.execute(
266
+ "CREATE INDEX IF NOT EXISTS idx_audit_logs_resource "
267
+ "ON audit_logs(resource_type, resource_id, timestamp DESC)"
268
+ )
269
+
270
+ # Authentication indexes (api_keys/accounts indexes are created inline
271
+ # with their tables in _create_auth_tables)
272
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)")
273
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id)")
274
+ cursor.execute(
275
+ "CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)"
276
+ )
277
+
278
+ # Workspace registry indexes (issue #601)
279
+ cursor.execute(
280
+ "CREATE INDEX IF NOT EXISTS idx_workspaces_registry_owner "
281
+ "ON workspaces_registry(owner_user_id)"
282
+ )
283
+ cursor.execute(
284
+ "CREATE INDEX IF NOT EXISTS idx_workspaces_registry_last_opened "
285
+ "ON workspaces_registry(last_opened_at DESC)"
286
+ )
287
+
288
+ def _ensure_default_admin_user(self) -> None:
289
+ """Ensure default admin user exists in database for initial setup.
290
+
291
+ Creates admin user with id=1 if it doesn't exist. This provides
292
+ a bootstrap user for test fixtures and initial database setup.
293
+
294
+ SECURITY: The admin user has a disabled password placeholder
295
+ that cannot match any bcrypt hash, so it cannot be used for
296
+ direct login. Users must register through the auth system.
297
+
298
+ Uses INSERT OR IGNORE to avoid conflicts with test fixtures.
299
+ """
300
+ cursor = self.conn.cursor()
301
+
302
+ # Create user record (FastAPI Users compatible)
303
+ # hashed_password uses a placeholder that cannot match any bcrypt hash
304
+ cursor.execute(
305
+ """
306
+ INSERT OR IGNORE INTO users (
307
+ id, email, name, hashed_password,
308
+ is_active, is_superuser, is_verified, email_verified
309
+ )
310
+ VALUES (1, 'admin@localhost', 'Admin User', '!DISABLED!', 1, 1, 1, 1)
311
+ """
312
+ )
313
+ user_created = cursor.rowcount > 0
314
+
315
+ if user_created:
316
+ logger.debug(
317
+ "Created default admin user (id=1) for test fixtures. "
318
+ "This account has a disabled password and cannot be used for login."
319
+ )
320
+
321
+ self.conn.commit()
@@ -0,0 +1,94 @@
1
+ # AGENTS.md - Project Instructions for AI Coding Agents
2
+
3
+ This file provides instructions for AI coding agents working in this repository.
4
+ For more information, see: https://agents.md/
5
+
6
+ ---
7
+
8
+ ## Tooling
9
+
10
+ - **package_manager**: uv (for Python), npm (for JavaScript)
11
+ - **test_runner**: pytest (Python), jest (JavaScript)
12
+ - **linter**: ruff (Python), eslint (JavaScript)
13
+ - **formatter**: black (Python), prettier (JavaScript)
14
+
15
+ ## Commands
16
+
17
+ - **install**: `uv sync` or `npm install`
18
+ - **test**: `uv run pytest` or `npm test`
19
+ - **lint**: `uv run ruff check --fix .` or `npm run lint`
20
+ - **build**: `uv run python -m build` or `npm run build`
21
+
22
+ ## Always Do
23
+
24
+ These actions are safe and the agent should perform autonomously:
25
+
26
+ - Choose between equivalent implementation approaches
27
+ - Decide file organization and naming conventions
28
+ - Select library versions (prefer latest stable)
29
+ - Handle existing files appropriately (overwrite, merge, or extend)
30
+ - Choose test frameworks and configurations based on project patterns
31
+ - Make code style decisions following existing codebase conventions
32
+ - Install dependencies using the project's package manager
33
+ - Create directories and files as needed
34
+ - Fix linting and formatting errors automatically
35
+ - Run tests after making changes
36
+ - Use type hints in Python code
37
+ - Follow existing import ordering patterns
38
+
39
+ ## Ask First
40
+
41
+ These decisions require user confirmation before proceeding:
42
+
43
+ - Delete critical configuration files (.env, package.json, pyproject.toml)
44
+ - Make breaking changes to public APIs
45
+ - Modify security-sensitive code (auth, encryption, access control)
46
+ - Remove or disable existing tests
47
+ - Change database schemas or migrations
48
+ - Upgrade major versions of core dependencies
49
+ - Refactor code outside the immediate scope of the task
50
+
51
+ ## Never Do
52
+
53
+ These actions are forbidden:
54
+
55
+ - Commit secrets, API keys, passwords, or credentials to the repository
56
+ - Delete the .git directory or git history
57
+ - Push directly to main/master branch without explicit permission
58
+ - Modify files outside the workspace directory
59
+ - Execute commands that could cause data loss (rm -rf /, DROP DATABASE)
60
+ - Bypass security validation or access controls
61
+ - Install packages from untrusted sources
62
+ - Disable or ignore security warnings
63
+ - Access external services without explicit permission
64
+ - Make network requests to unknown hosts
65
+
66
+ ## Code Style
67
+
68
+ - **python_imports**: isort with black profile, absolute imports preferred
69
+ - **python_docstrings**: Google style docstrings for public functions
70
+ - **python_type_hints**: Required for function signatures
71
+ - **javascript_modules**: ES modules (import/export)
72
+ - **line_length**: 88 for Python, 100 for JavaScript
73
+ - **indentation**: 4 spaces for Python, 2 spaces for JavaScript
74
+
75
+ ## Project Structure
76
+
77
+ Describe your project structure here:
78
+
79
+ ```
80
+ project/
81
+ ├── src/ # Source code
82
+ ├── tests/ # Test files
83
+ ├── docs/ # Documentation
84
+ ├── scripts/ # Utility scripts
85
+ └── README.md # Project documentation
86
+ ```
87
+
88
+ ## Notes
89
+
90
+ Add any project-specific notes for the agent here:
91
+
92
+ - Describe any unusual patterns or conventions
93
+ - Note any known issues or quirks
94
+ - Explain any domain-specific terminology
@@ -0,0 +1,5 @@
1
+ """TUI dashboard for CodeFRAME.
2
+
3
+ Terminal-based dashboard using Textual for power users.
4
+ Read-only view of workspace state — tasks, events, blockers.
5
+ """