codemap-python 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 (58) hide show
  1. analysis/__init__.py +1 -0
  2. analysis/architecture/__init__.py +1 -0
  3. analysis/architecture/architecture_engine.py +155 -0
  4. analysis/architecture/dependency_cycles.py +103 -0
  5. analysis/architecture/risk_radar.py +220 -0
  6. analysis/call_graph/__init__.py +1 -0
  7. analysis/call_graph/call_extractor.py +91 -0
  8. analysis/call_graph/call_graph_builder.py +1 -0
  9. analysis/call_graph/call_resolver.py +56 -0
  10. analysis/call_graph/context_models.py +1 -0
  11. analysis/call_graph/cross_file_resolver.py +122 -0
  12. analysis/call_graph/execution_tracker.py +1 -0
  13. analysis/call_graph/flow_builder.py +1 -0
  14. analysis/call_graph/models.py +1 -0
  15. analysis/core/__init__.py +1 -0
  16. analysis/core/ast_context.py +1 -0
  17. analysis/core/ast_parser.py +8 -0
  18. analysis/core/class_extractor.py +35 -0
  19. analysis/core/function_extractor.py +16 -0
  20. analysis/core/import_extractor.py +43 -0
  21. analysis/explain/__init__.py +1 -0
  22. analysis/explain/docstring_extractor.py +45 -0
  23. analysis/explain/explain_runner.py +177 -0
  24. analysis/explain/repo_summary_generator.py +138 -0
  25. analysis/explain/return_analyzer.py +114 -0
  26. analysis/explain/risk_flags.py +1 -0
  27. analysis/explain/signature_extractor.py +104 -0
  28. analysis/explain/summary_generator.py +282 -0
  29. analysis/graph/__init__.py +1 -0
  30. analysis/graph/callgraph_index.py +117 -0
  31. analysis/graph/entrypoint_detector.py +1 -0
  32. analysis/graph/impact_analyzer.py +210 -0
  33. analysis/indexing/__init__.py +1 -0
  34. analysis/indexing/import_resolver.py +156 -0
  35. analysis/indexing/symbol_index.py +150 -0
  36. analysis/runners/__init__.py +1 -0
  37. analysis/runners/phase4_runner.py +137 -0
  38. analysis/utils/__init__.py +1 -0
  39. analysis/utils/ast_helpers.py +1 -0
  40. analysis/utils/cache_manager.py +659 -0
  41. analysis/utils/path_resolver.py +1 -0
  42. analysis/utils/repo_fetcher.py +469 -0
  43. cli.py +1728 -0
  44. codemap_cli.py +11 -0
  45. codemap_python-0.1.0.dist-info/METADATA +399 -0
  46. codemap_python-0.1.0.dist-info/RECORD +58 -0
  47. codemap_python-0.1.0.dist-info/WHEEL +5 -0
  48. codemap_python-0.1.0.dist-info/entry_points.txt +2 -0
  49. codemap_python-0.1.0.dist-info/top_level.txt +5 -0
  50. security_utils.py +51 -0
  51. ui/__init__.py +1 -0
  52. ui/app.py +2160 -0
  53. ui/device_id.py +27 -0
  54. ui/static/app.js +2703 -0
  55. ui/static/styles.css +1268 -0
  56. ui/templates/index.html +231 -0
  57. ui/utils/__init__.py +1 -0
  58. ui/utils/registry_manager.py +190 -0
@@ -0,0 +1,231 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>CodeMap UI</title>
7
+ <link rel="stylesheet" href="/static/styles.css" />
8
+ </head>
9
+ <body>
10
+ <header class="topbar">
11
+ <div class="brand">
12
+ <div class="title">CodeMap</div>
13
+ <div class="repo-name-wrap">
14
+ <div id="repo-name" class="repo-name">No repo selected</div>
15
+ <span id="repo-private-badge" class="repo-badge private hidden">PRIVATE MODE</span>
16
+ </div>
17
+ </div>
18
+ <div class="workspace-controls">
19
+ <select id="repo-select" class="repo-select"></select>
20
+ <button id="add-repo-btn" class="repo-btn" type="button">+ Add repo...</button>
21
+ <button id="ai-settings-btn" class="repo-btn" type="button" onclick="var m=document.getElementById('ai-settings-modal'); if(m){ m.classList.remove('hidden'); document.body.classList.add('modal-open'); }">Settings</button>
22
+ <span id="repo-list-mode-pill" class="repo-badge">Session Mode</span>
23
+ </div>
24
+ <div id="meta" class="meta">Loading...</div>
25
+ </header>
26
+ <section id="add-repo-inline" class="add-repo-inline hidden">
27
+ <div class="add-repo-header">
28
+ <div class="section-title">Add Repository</div>
29
+ <button id="repo-inline-close" class="repo-btn" type="button">Close</button>
30
+ </div>
31
+ <div class="modal-tabs">
32
+ <button id="repo-tab-local" class="tab-btn active" type="button">Local Path</button>
33
+ <button id="repo-tab-github" class="tab-btn" type="button">GitHub</button>
34
+ </div>
35
+ <div id="repo-form-local" class="modal-form">
36
+ <label>Repo path
37
+ <input id="local-repo-path" type="text" placeholder="D:\repo\project or ./repo" />
38
+ </label>
39
+ <label>Display name (optional)
40
+ <input id="local-display-name" type="text" placeholder="defaults to folder name" />
41
+ </label>
42
+ </div>
43
+ <div id="repo-form-github" class="modal-form hidden">
44
+ <label>GitHub URL
45
+ <input id="gh-repo-url" type="text" placeholder="https://github.com/org/repo" />
46
+ </label>
47
+ <label>Ref
48
+ <input id="gh-ref" type="text" value="main" />
49
+ </label>
50
+ <label>Mode
51
+ <select id="gh-mode">
52
+ <option value="zip" selected>zip</option>
53
+ <option value="git">git</option>
54
+ </select>
55
+ </label>
56
+ <label>Token (optional for private repos)
57
+ <input id="gh-token" type="password" placeholder="Used for this run only" />
58
+ </label>
59
+ <label class="path">
60
+ <input id="gh-private-mode" type="checkbox" />
61
+ Private Repo Mode
62
+ </label>
63
+ <div id="private-mode-indicator" class="private-mode hidden" title="Token used only in memory; not stored; you can clear cached analysis anytime.">
64
+ 🔒 Private Repo Mode: Tokens are used only in memory; nothing is uploaded; you can delete cached data anytime.
65
+ </div>
66
+ </div>
67
+ <div id="repo-modal-error" class="status"></div>
68
+ <div class="modal-actions">
69
+ <button id="repo-add-btn" class="repo-btn" type="button">Add repo</button>
70
+ <button id="repo-cancel-btn" class="repo-btn" type="button">Cancel</button>
71
+ </div>
72
+ </section>
73
+ <div id="ai-settings-modal" class="confirm-modal hidden">
74
+ <div class="confirm-card settings-card">
75
+ <div class="section-title">Settings</div>
76
+ <div class="path">General</div>
77
+ <label class="path">
78
+ <input id="ai-settings-remember-repos" type="checkbox" />
79
+ Remember repositories across sessions
80
+ </label>
81
+ <div class="repo-row-actions">
82
+ <button id="ai-settings-clear-repos" class="repo-btn danger" type="button">Clear repository list</button>
83
+ </div>
84
+ <div class="divider"></div>
85
+ <div id="ai-settings-status" class="status">CodeMap runs locally with deterministic summaries and analysis.</div>
86
+ <div class="repo-row-actions">
87
+ <button id="ai-settings-cancel" class="repo-btn" type="button">Close</button>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <main class="layout shell">
93
+ <section class="panel">
94
+ <h2>Repository Tree</h2>
95
+ <div id="tree-status" class="status"></div>
96
+ <div id="repo-list" class="card">
97
+ <div class="section-title">Repositories</div>
98
+ <div id="repo-list-content" class="content muted">No repositories added yet. Add a local path or GitHub repo to begin.</div>
99
+ </div>
100
+ <div id="tree" class="tree"></div>
101
+ <div class="divider"></div>
102
+ <div id="data-privacy" class="card">
103
+ <div class="section-title">Data &amp; Privacy</div>
104
+ <div id="privacy-summary" class="path">Loading data retention...</div>
105
+ <div id="privacy-repo-list-mode" class="path">Repository list: Session-only</div>
106
+ <div id="privacy-private-banner" class="privacy-warning hidden">Private repo mode: caches auto-expire in 7 days unless you change retention.</div>
107
+ <div id="privacy-expiring" class="content muted"></div>
108
+ <div class="privacy-controls">
109
+ <label>Retention
110
+ <select id="repo-retention-select">
111
+ <option value="1">1 day</option>
112
+ <option value="7">7 days</option>
113
+ <option value="14" selected>14 days</option>
114
+ <option value="30">30 days</option>
115
+ <option value="90">90 days</option>
116
+ <option value="0">Never</option>
117
+ </select>
118
+ </label>
119
+ </div>
120
+ <div class="path">Shorter retention = more privacy.</div>
121
+ <div class="privacy-actions">
122
+ <button id="repo-retention-save-btn" class="repo-btn" type="button">Set retention</button>
123
+ <button id="cleanup-dry-btn" class="repo-btn" type="button">Run Cleanup (Dry Run)</button>
124
+ <button id="cleanup-now-btn" class="repo-btn" type="button">Run Cleanup Now</button>
125
+ <button id="delete-repo-cache-btn" class="repo-btn danger" type="button">Delete cached data (repo)</button>
126
+ <button id="delete-all-caches-btn" class="repo-btn danger" type="button">Delete ALL caches</button>
127
+ </div>
128
+ <div id="privacy-confirm" class="content muted hidden"></div>
129
+ <label class="path">
130
+ <input id="auto-clean-on-remove" type="checkbox" />
131
+ Delete cached data when I remove this repo
132
+ </label>
133
+ <div id="auto-clean-note" class="path hidden">Removing from UI will also delete local cache.</div>
134
+ <div class="path">Your analysis data is stored locally under .codemap_cache. You can delete it anytime.</div>
135
+ <div class="path">CodeMap runs locally. Analysis data is cached on this machine only.</div>
136
+ <div class="path">Retention cleanup runs locally; nothing is uploaded.</div>
137
+ <div id="privacy-result" class="content muted"></div>
138
+ </div>
139
+ </section>
140
+
141
+ <section class="panel">
142
+ <h2>File Intelligence</h2>
143
+ <div class="search-wrap">
144
+ <input id="symbol-search-input" type="text" autocomplete="off"
145
+ placeholder="Search symbols (e.g., Student.display, intro, parse...)" />
146
+ <div id="symbol-search-results" class="search-results hidden"></div>
147
+ </div>
148
+ <div class="recent-wrap">
149
+ <div id="recent-symbols-wrap">
150
+ <div class="section-title">Recent Symbols</div>
151
+ <div id="recent-symbols" class="content muted"></div>
152
+ </div>
153
+ <div id="recent-files-wrap">
154
+ <div class="section-title">Recent Files</div>
155
+ <div id="recent-files" class="content muted"></div>
156
+ </div>
157
+ </div>
158
+ <div id="file-view" class="content muted">Select a file from the tree.</div>
159
+ </section>
160
+
161
+ <section class="panel">
162
+ <h2>Symbol Intelligence</h2>
163
+ <div class="view-toggle">
164
+ <button id="tab-details" class="tab-btn active" type="button">Details</button>
165
+ <button id="tab-impact" class="tab-btn" type="button">Impact</button>
166
+ <button id="tab-graph" class="tab-btn" type="button">Graph</button>
167
+ <button id="tab-architecture" class="tab-btn" type="button">Architecture</button>
168
+ </div>
169
+ <div id="graph-controls" class="graph-controls hidden">
170
+ <label>Mode
171
+ <select id="graph-mode">
172
+ <option value="symbol" selected>Symbol</option>
173
+ <option value="file">File</option>
174
+ </select>
175
+ </label>
176
+ <label>Depth
177
+ <select id="graph-depth">
178
+ <option value="1" selected>1</option>
179
+ <option value="2">2</option>
180
+ <option value="3">3</option>
181
+ </select>
182
+ </label>
183
+ <label><input id="graph-hide-builtins" type="checkbox" checked /> Hide builtins</label>
184
+ <label><input id="graph-hide-external" type="checkbox" checked /> Hide external</label>
185
+ <input id="graph-search" type="text" placeholder="Search graph nodes..." />
186
+ </div>
187
+ <div id="impact-controls" class="graph-controls hidden">
188
+ <label>Depth
189
+ <select id="impact-depth">
190
+ <option value="1">1</option>
191
+ <option value="2" selected>2</option>
192
+ <option value="3">3</option>
193
+ </select>
194
+ </label>
195
+ <label>Max nodes
196
+ <select id="impact-max-nodes">
197
+ <option value="200" selected>200</option>
198
+ <option value="100">100</option>
199
+ <option value="50">50</option>
200
+ <option value="10">10</option>
201
+ <option value="1">1 (test truncation)</option>
202
+ </select>
203
+ </label>
204
+ </div>
205
+ <div id="symbol-view" class="content muted">Select a symbol to view summary and usages.</div>
206
+ <div id="impact-view" class="content muted hidden">Select a symbol to view impact.</div>
207
+ <div id="graph-view" class="content muted hidden">Select a symbol to view graph.</div>
208
+ <div id="architecture-view" class="content muted hidden">Select a repository to view architecture insights.</div>
209
+ </section>
210
+ </main>
211
+
212
+ <div id="toast" class="toast hidden"></div>
213
+ <div id="confirm-modal" class="confirm-modal hidden">
214
+ <div class="confirm-card">
215
+ <div id="confirm-title" class="section-title">Confirm action</div>
216
+ <div id="confirm-message" class="path"></div>
217
+ <div class="repo-row-actions">
218
+ <button id="confirm-yes" class="repo-btn danger" type="button">Yes</button>
219
+ <button id="confirm-no" class="repo-btn" type="button">Cancel</button>
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <script>
225
+ window.CODEMAP_DEFAULT_REPO = "{{ default_repo }}";
226
+ </script>
227
+ <script src="/static/app.js?v=20260306b"></script>
228
+ </body>
229
+ </html>
230
+
231
+
ui/utils/__init__.py ADDED
@@ -0,0 +1 @@
1
+ 
@@ -0,0 +1,190 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import threading
7
+ from datetime import datetime, timezone
8
+ from typing import Any, Dict, List, Optional
9
+ from urllib.parse import urlsplit, urlunsplit
10
+
11
+
12
+ _LOCK = threading.RLock()
13
+ _REGISTRY_FILE = "_registry.json"
14
+ _SENSITIVE_RE = re.compile(r"(?i)(api[_-]?key|token|authorization|bearer|basic|secret|password)")
15
+
16
+
17
+ def _cache_root(base_dir: Optional[str] = None) -> str:
18
+ if base_dir:
19
+ return os.path.abspath(base_dir)
20
+ return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".codemap_cache"))
21
+
22
+
23
+ def _registry_path(base_dir: Optional[str] = None) -> str:
24
+ return os.path.join(_cache_root(base_dir), _REGISTRY_FILE)
25
+
26
+
27
+ def _now_iso() -> str:
28
+ return datetime.now(timezone.utc).isoformat()
29
+
30
+
31
+ def _default_registry() -> Dict[str, Any]:
32
+ return {
33
+ "version": 1,
34
+ "remember_repos": False,
35
+ "repos": [],
36
+ "updated_at": _now_iso(),
37
+ }
38
+
39
+
40
+ def _safe_repo_url(repo_url: str) -> str:
41
+ value = str(repo_url or "").strip()
42
+ if not value:
43
+ return ""
44
+ try:
45
+ parts = urlsplit(value)
46
+ hostname = parts.hostname or ""
47
+ if not hostname:
48
+ return value
49
+ netloc = hostname
50
+ if parts.port:
51
+ netloc = f"{hostname}:{parts.port}"
52
+ clean = urlunsplit((parts.scheme, netloc, parts.path, "", ""))
53
+ return clean.rstrip("/")
54
+ except Exception:
55
+ return value
56
+
57
+
58
+ def _sanitize_repo_entry(entry: Dict[str, Any]) -> Dict[str, Any]:
59
+ now = _now_iso()
60
+ source = str(entry.get("source", "filesystem") or "filesystem").strip().lower()
61
+ if source not in {"filesystem", "github"}:
62
+ source = "filesystem"
63
+ sanitized = {
64
+ "repo_hash": str(entry.get("repo_hash", "") or "").strip(),
65
+ "display_name": str(entry.get("display_name", "") or "").strip(),
66
+ "source": source,
67
+ "repo_path": str(entry.get("repo_path", "") or "").strip(),
68
+ "repo_url": _safe_repo_url(str(entry.get("repo_url", "") or "")),
69
+ "ref": str(entry.get("ref", "") or "").strip(),
70
+ "mode": str(entry.get("mode", "") or "").strip().lower(),
71
+ "added_at": str(entry.get("added_at", "") or now),
72
+ "last_opened_at": str(entry.get("last_opened_at", "") or ""),
73
+ }
74
+ if not sanitized["display_name"]:
75
+ candidate = sanitized["repo_path"] or sanitized["repo_url"] or sanitized["repo_hash"]
76
+ sanitized["display_name"] = os.path.basename(str(candidate).rstrip("\\/")) or str(candidate)
77
+ return sanitized
78
+
79
+
80
+ def _scrub_sensitive_fields(payload: Any) -> Any:
81
+ if isinstance(payload, dict):
82
+ clean: Dict[str, Any] = {}
83
+ for k, v in payload.items():
84
+ key = str(k or "")
85
+ if _SENSITIVE_RE.search(key):
86
+ continue
87
+ clean[key] = _scrub_sensitive_fields(v)
88
+ return clean
89
+ if isinstance(payload, list):
90
+ return [_scrub_sensitive_fields(v) for v in payload]
91
+ return payload
92
+
93
+
94
+ def save_registry_atomic(data: Dict[str, Any], base_dir: Optional[str] = None) -> Dict[str, Any]:
95
+ root = _cache_root(base_dir)
96
+ os.makedirs(root, exist_ok=True)
97
+ path = _registry_path(base_dir)
98
+
99
+ payload = _default_registry()
100
+ payload.update({
101
+ "version": int(data.get("version", 1) or 1),
102
+ "remember_repos": bool(data.get("remember_repos", False)),
103
+ "updated_at": _now_iso(),
104
+ })
105
+ repos = data.get("repos", [])
106
+ payload["repos"] = [
107
+ _sanitize_repo_entry(r)
108
+ for r in repos
109
+ if isinstance(r, dict) and str(r.get("repo_hash", "") or "").strip()
110
+ ]
111
+
112
+ tmp_path = f"{path}.tmp"
113
+ safe_payload = _scrub_sensitive_fields(payload)
114
+ with open(tmp_path, "w", encoding="utf-8") as f:
115
+ json.dump(safe_payload, f, indent=2)
116
+ os.replace(tmp_path, path)
117
+ return safe_payload
118
+
119
+
120
+ def load_registry(base_dir: Optional[str] = None) -> Dict[str, Any]:
121
+ path = _registry_path(base_dir)
122
+ with _LOCK:
123
+ if not os.path.exists(path):
124
+ return save_registry_atomic(_default_registry(), base_dir=base_dir)
125
+ try:
126
+ with open(path, "r", encoding="utf-8") as f:
127
+ raw = json.load(f)
128
+ except Exception:
129
+ return save_registry_atomic(_default_registry(), base_dir=base_dir)
130
+ if not isinstance(raw, dict):
131
+ return save_registry_atomic(_default_registry(), base_dir=base_dir)
132
+ return save_registry_atomic(raw, base_dir=base_dir)
133
+
134
+
135
+ def set_remember(remember_repos: bool, base_dir: Optional[str] = None) -> Dict[str, Any]:
136
+ with _LOCK:
137
+ reg = load_registry(base_dir=base_dir)
138
+ reg["remember_repos"] = bool(remember_repos)
139
+ return save_registry_atomic(reg, base_dir=base_dir)
140
+
141
+
142
+ def list_repos(base_dir: Optional[str] = None) -> List[Dict[str, Any]]:
143
+ reg = load_registry(base_dir=base_dir)
144
+ repos = reg.get("repos", [])
145
+ if not isinstance(repos, list):
146
+ return []
147
+ return [_sanitize_repo_entry(r) for r in repos if isinstance(r, dict)]
148
+
149
+
150
+ def add_repo(entry: Dict[str, Any], base_dir: Optional[str] = None) -> Dict[str, Any]:
151
+ with _LOCK:
152
+ reg = load_registry(base_dir=base_dir)
153
+ repo = _sanitize_repo_entry(entry)
154
+ repos = reg.get("repos", [])
155
+ if not isinstance(repos, list):
156
+ repos = []
157
+ updated = False
158
+ for idx, item in enumerate(repos):
159
+ if isinstance(item, dict) and str(item.get("repo_hash", "") or "") == repo["repo_hash"]:
160
+ merged = dict(item)
161
+ merged.update(repo)
162
+ repos[idx] = _sanitize_repo_entry(merged)
163
+ updated = True
164
+ break
165
+ if not updated:
166
+ repos.append(repo)
167
+ reg["repos"] = repos
168
+ save_registry_atomic(reg, base_dir=base_dir)
169
+ return repo
170
+
171
+
172
+ def remove_repo(repo_hash: str, base_dir: Optional[str] = None) -> Dict[str, Any]:
173
+ key = str(repo_hash or "").strip()
174
+ with _LOCK:
175
+ reg = load_registry(base_dir=base_dir)
176
+ repos = reg.get("repos", [])
177
+ if not isinstance(repos, list):
178
+ repos = []
179
+ reg["repos"] = [
180
+ r for r in repos
181
+ if not (isinstance(r, dict) and str(r.get("repo_hash", "") or "") == key)
182
+ ]
183
+ return save_registry_atomic(reg, base_dir=base_dir)
184
+
185
+
186
+ def clear_repos(base_dir: Optional[str] = None) -> Dict[str, Any]:
187
+ with _LOCK:
188
+ reg = load_registry(base_dir=base_dir)
189
+ reg["repos"] = []
190
+ return save_registry_atomic(reg, base_dir=base_dir)