misterdev 0.2.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 (136) hide show
  1. misterdev/__init__.py +3 -0
  2. misterdev/agent.py +2166 -0
  3. misterdev/agent_helpers.py +194 -0
  4. misterdev/analyzers/__init__.py +0 -0
  5. misterdev/analyzers/project_analyzer/__init__.py +246 -0
  6. misterdev/analyzers/project_analyzer/detection.py +146 -0
  7. misterdev/analyzers/project_analyzer/merge.py +137 -0
  8. misterdev/analyzers/project_analyzer/overview.py +312 -0
  9. misterdev/analyzers/project_analyzer/prompts.py +83 -0
  10. misterdev/cli.py +370 -0
  11. misterdev/config.py +521 -0
  12. misterdev/core/__init__.py +0 -0
  13. misterdev/core/audit.py +88 -0
  14. misterdev/core/config.py +10 -0
  15. misterdev/core/context/__init__.py +0 -0
  16. misterdev/core/context/change_tracker.py +218 -0
  17. misterdev/core/context/contracts/__init__.py +63 -0
  18. misterdev/core/context/contracts/_log.py +9 -0
  19. misterdev/core/context/contracts/_text.py +12 -0
  20. misterdev/core/context/contracts/extraction.py +30 -0
  21. misterdev/core/context/contracts/python_generic.py +42 -0
  22. misterdev/core/context/contracts/registry.py +127 -0
  23. misterdev/core/context/contracts/rust_line.py +225 -0
  24. misterdev/core/context/contracts/rust_tree_sitter.py +141 -0
  25. misterdev/core/context/lsp.py +174 -0
  26. misterdev/core/context/scratchpad.py +111 -0
  27. misterdev/core/context/topography/__init__.py +43 -0
  28. misterdev/core/context/topography/_log.py +10 -0
  29. misterdev/core/context/topography/cache.py +91 -0
  30. misterdev/core/context/topography/engine.py +172 -0
  31. misterdev/core/context/topography/graph.py +675 -0
  32. misterdev/core/context/topography/nodes.py +55 -0
  33. misterdev/core/context/topography/parsers.py +95 -0
  34. misterdev/core/context/topography/syntax.py +54 -0
  35. misterdev/core/economics/__init__.py +0 -0
  36. misterdev/core/economics/context_budget.py +175 -0
  37. misterdev/core/economics/embeddings.py +232 -0
  38. misterdev/core/economics/free_models.py +108 -0
  39. misterdev/core/economics/llm_cache.py +105 -0
  40. misterdev/core/economics/model_catalog.py +79 -0
  41. misterdev/core/economics/model_ledger.py +331 -0
  42. misterdev/core/economics/model_selector.py +281 -0
  43. misterdev/core/execution/__init__.py +0 -0
  44. misterdev/core/execution/bounded.py +50 -0
  45. misterdev/core/execution/container.py +221 -0
  46. misterdev/core/execution/error_classifier.py +366 -0
  47. misterdev/core/execution/error_resolver.py +201 -0
  48. misterdev/core/execution/governance.py +283 -0
  49. misterdev/core/execution/outcomes.py +50 -0
  50. misterdev/core/execution/progress.py +120 -0
  51. misterdev/core/execution/project.py +231 -0
  52. misterdev/core/execution/registry.py +97 -0
  53. misterdev/core/execution/runtime.py +279 -0
  54. misterdev/core/gitcmd.py +39 -0
  55. misterdev/core/integration/__init__.py +0 -0
  56. misterdev/core/integration/mcp.py +368 -0
  57. misterdev/core/integration/mcp_gather.py +186 -0
  58. misterdev/core/models.py +35 -0
  59. misterdev/core/modes.py +184 -0
  60. misterdev/core/planning/__init__.py +0 -0
  61. misterdev/core/planning/advisor.py +89 -0
  62. misterdev/core/planning/assessment.py +135 -0
  63. misterdev/core/planning/decomposer.py +387 -0
  64. misterdev/core/planning/metacognition.py +103 -0
  65. misterdev/core/planning/sovereign.py +308 -0
  66. misterdev/core/planning/targets.py +201 -0
  67. misterdev/core/reporting/__init__.py +0 -0
  68. misterdev/core/reporting/report.py +377 -0
  69. misterdev/core/reporting/report_view.py +151 -0
  70. misterdev/core/task.py +163 -0
  71. misterdev/core/verification/__init__.py +0 -0
  72. misterdev/core/verification/claim_verifier.py +210 -0
  73. misterdev/core/verification/critic.py +324 -0
  74. misterdev/core/verification/gatekeeper/__init__.py +631 -0
  75. misterdev/core/verification/gatekeeper/constants.py +138 -0
  76. misterdev/core/verification/gatekeeper/helpers.py +28 -0
  77. misterdev/core/verification/goal_check.py +219 -0
  78. misterdev/core/verification/independent.py +68 -0
  79. misterdev/core/verification/mutation_gate.py +221 -0
  80. misterdev/core/verification/preflight.py +95 -0
  81. misterdev/core/verification/spec_tests.py +175 -0
  82. misterdev/core/verification/validator.py +495 -0
  83. misterdev/core/verification/vision_verify.py +185 -0
  84. misterdev/core/verification/web_verify.py +408 -0
  85. misterdev/environments/__init__.py +0 -0
  86. misterdev/environments/base_env.py +18 -0
  87. misterdev/environments/container_env.py +87 -0
  88. misterdev/environments/venv_env.py +42 -0
  89. misterdev/llm/__init__.py +0 -0
  90. misterdev/llm/client/__init__.py +152 -0
  91. misterdev/llm/client/base.py +382 -0
  92. misterdev/llm/client/edits.py +70 -0
  93. misterdev/llm/client/embeddings.py +121 -0
  94. misterdev/llm/client/errors.py +134 -0
  95. misterdev/llm/client/providers.py +535 -0
  96. misterdev/llm/client/response.py +24 -0
  97. misterdev/llm/prompt_manager.py +82 -0
  98. misterdev/llm/responses/__init__.py +34 -0
  99. misterdev/llm/responses/apply.py +131 -0
  100. misterdev/llm/responses/json_extract.py +80 -0
  101. misterdev/llm/responses/models.py +43 -0
  102. misterdev/llm/responses/parsing.py +494 -0
  103. misterdev/logging_setup.py +20 -0
  104. misterdev/mcp_server.py +208 -0
  105. misterdev/nl_cli.py +149 -0
  106. misterdev/plugins.py +115 -0
  107. misterdev/py.typed +0 -0
  108. misterdev/task_executors/__init__.py +0 -0
  109. misterdev/task_executors/base_executor.py +10 -0
  110. misterdev/task_executors/markdown_plan_executor/__init__.py +90 -0
  111. misterdev/task_executors/markdown_plan_executor/commands_mixin.py +82 -0
  112. misterdev/task_executors/markdown_plan_executor/context_mixin.py +221 -0
  113. misterdev/task_executors/markdown_plan_executor/critic_spec_mixin.py +174 -0
  114. misterdev/task_executors/markdown_plan_executor/edits_mixin.py +251 -0
  115. misterdev/task_executors/markdown_plan_executor/execute_mixin.py +727 -0
  116. misterdev/task_executors/markdown_plan_executor/gates_mixin.py +203 -0
  117. misterdev/task_executors/markdown_plan_executor/git_mixin.py +219 -0
  118. misterdev/task_executors/markdown_plan_executor/helpers.py +521 -0
  119. misterdev/task_executors/markdown_plan_executor/llm_mixin.py +238 -0
  120. misterdev/task_executors/markdown_plan_executor/results_mixin.py +23 -0
  121. misterdev/tools/__init__.py +19 -0
  122. misterdev/tools/base_tool.py +14 -0
  123. misterdev/tools/command.py +75 -0
  124. misterdev/tools/file_io.py +69 -0
  125. misterdev/tools/formatter.py +26 -0
  126. misterdev/tools/git_tool.py +90 -0
  127. misterdev/utils/__init__.py +0 -0
  128. misterdev/utils/file_utils.py +169 -0
  129. misterdev/utils/process.py +23 -0
  130. misterdev-0.2.0.dist-info/METADATA +326 -0
  131. misterdev-0.2.0.dist-info/RECORD +136 -0
  132. misterdev-0.2.0.dist-info/WHEEL +5 -0
  133. misterdev-0.2.0.dist-info/entry_points.txt +3 -0
  134. misterdev-0.2.0.dist-info/licenses/COMMERCIAL_LICENSE.md +34 -0
  135. misterdev-0.2.0.dist-info/licenses/LICENSE +661 -0
  136. misterdev-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,10 @@
1
+ """Shared package logger.
2
+
3
+ Named for the package (not this submodule) so log output keeps the pre-split
4
+ logger name ``misterdev.core.context.topography`` after the
5
+ god-module was broken into sections.
6
+ """
7
+
8
+ from misterdev.logging_setup import setup_logger
9
+
10
+ logger = setup_logger(__name__.rsplit(".", 1)[0])
@@ -0,0 +1,91 @@
1
+ """Disk-backed per-file symbol cache, keyed by content hash + lang + format."""
2
+
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Dict, List, Optional, Set, Any
7
+
8
+ from misterdev.utils.file_utils import atomic_write_json
9
+
10
+ from ._log import logger
11
+ from .nodes import SymbolNode, _symbol_to_dict, _symbol_from_dict
12
+
13
+ # Bump when the parse output shape or any _traverse_* logic changes, so a stale
14
+ # on-disk cache from an older grammar/format is discarded rather than served.
15
+ _CACHE_FORMAT_VERSION = 1
16
+
17
+
18
+ class _TopographyCache:
19
+ """Disk-backed per-file symbol cache, keyed by content hash + lang + format.
20
+
21
+ Maps a project-relative path to ``{"key": <hash>, "symbols": [...]}`` where
22
+ the key folds the file's content sha256, its language, and the cache-format
23
+ version. An entry is only reused when the recomputed key matches, so an edit
24
+ (content change), a language change, or a format bump all invalidate it; mtime
25
+ is never consulted. Every read/write is best-effort: a corrupt or unreadable
26
+ cache degrades to an empty in-memory map and a full parse, never a raise and
27
+ never stale symbols.
28
+ """
29
+
30
+ def __init__(self, path: Path):
31
+ self.path = Path(path)
32
+ self.entries: Dict[str, Dict[str, Any]] = {}
33
+
34
+ @staticmethod
35
+ def make_key(content_bytes: bytes, lang: str) -> str:
36
+ h = hashlib.sha256()
37
+ h.update(f"{_CACHE_FORMAT_VERSION}\x00{lang}\x00".encode("utf-8"))
38
+ h.update(content_bytes)
39
+ return h.hexdigest()
40
+
41
+ def load(self) -> None:
42
+ if not self.path.exists():
43
+ return
44
+ try:
45
+ data = json.loads(self.path.read_text(encoding="utf-8"))
46
+ except (OSError, json.JSONDecodeError, ValueError) as e:
47
+ logger.debug(f"Topography cache unreadable, starting fresh: {e}")
48
+ self.entries = {}
49
+ return
50
+ if (
51
+ not isinstance(data, dict)
52
+ or data.get("format") != _CACHE_FORMAT_VERSION
53
+ or not isinstance(data.get("files"), dict)
54
+ ):
55
+ logger.debug("Topography cache format mismatch; starting fresh")
56
+ self.entries = {}
57
+ return
58
+ self.entries = data["files"]
59
+
60
+ def get(self, rel_path: str, key: str) -> Optional[List["SymbolNode"]]:
61
+ """Cached symbols for ``rel_path`` iff its key matches, else None."""
62
+ entry = self.entries.get(rel_path)
63
+ if not isinstance(entry, dict) or entry.get("key") != key:
64
+ return None
65
+ raw = entry.get("symbols")
66
+ if not isinstance(raw, list):
67
+ return None
68
+ try:
69
+ return [_symbol_from_dict(d) for d in raw]
70
+ except (KeyError, TypeError) as e:
71
+ logger.debug(f"Topography cache entry for {rel_path} malformed: {e}")
72
+ return None
73
+
74
+ def put(self, rel_path: str, key: str, symbols: List["SymbolNode"]) -> None:
75
+ self.entries[rel_path] = {
76
+ "key": key,
77
+ "symbols": [_symbol_to_dict(s) for s in symbols],
78
+ }
79
+
80
+ def prune(self, live_paths: Set[str]) -> None:
81
+ """Drop entries for files no longer present in the source tree."""
82
+ for stale in [p for p in self.entries if p not in live_paths]:
83
+ del self.entries[stale]
84
+
85
+ def save(self) -> None:
86
+ try:
87
+ atomic_write_json(
88
+ self.path, {"format": _CACHE_FORMAT_VERSION, "files": self.entries}
89
+ )
90
+ except OSError as e:
91
+ logger.debug(f"Topography cache write failed (non-fatal): {e}")
@@ -0,0 +1,172 @@
1
+ """The Topography Engine: a lazy-loaded facade over the symbol graph."""
2
+
3
+ from pathlib import Path
4
+ from typing import Dict, List, Optional, Set, Any
5
+
6
+ from ._log import logger
7
+ from .nodes import SymbolNode
8
+ from .graph import SymbolGraph
9
+
10
+
11
+ class TopographyEngine:
12
+ """Topography Engine with Vector Persistence and Lazy Loading."""
13
+
14
+ def __init__(self, project_path: Path, llm_client: Any, golden_paths=None):
15
+ self.project_path = project_path
16
+ self.llm = llm_client
17
+ self.graph = SymbolGraph(project_path, golden_paths=golden_paths)
18
+ self._initialized = False
19
+
20
+ def initialize(self, force: bool = False):
21
+ if self._initialized and not force:
22
+ return
23
+
24
+ logger.info("Initializing Topography Engine...")
25
+ self.graph.build()
26
+ logger.info(f"Symbol graph: {len(self.graph.symbols)} symbols indexed")
27
+ self._initialized = True
28
+
29
+ def invalidate(self) -> None:
30
+ """Mark the symbol graph stale so the next access rebuilds it.
31
+
32
+ The graph is built once and then never reflected the edits tasks made, so
33
+ later tasks saw a stale map — missing new symbols, listing deleted ones —
34
+ which weakens reference discovery and the dangling-reference gate. Calling
35
+ this after a task changes the tree makes the next task rebuild from the
36
+ current on-disk state. Rebuild is cheap: the per-file symbol cache is
37
+ content-hashed, so only files that actually changed are re-parsed.
38
+ """
39
+ self._initialized = False
40
+
41
+ def get_file_outline(self, file_path: str) -> str:
42
+ self.initialize()
43
+ return self.graph.file_outline(file_path)
44
+
45
+ def get_file_symbols(self, file_path: str):
46
+ self.initialize()
47
+ return self.graph.file_symbols(file_path)
48
+
49
+ def get_project_outline(self) -> str:
50
+ self.initialize()
51
+ return self.graph.project_outline()
52
+
53
+ def get_context_for_task(
54
+ self,
55
+ query: str,
56
+ related_files: List[str],
57
+ max_symbols: int = 30,
58
+ ranker=None,
59
+ exclude_files: Optional[Set[str]] = None,
60
+ ) -> str:
61
+ """Retrieves functional neighborhood and semantic context. Triggers lazy init.
62
+
63
+ When more candidate symbols are found than fit (``max_symbols``) and a
64
+ semantic ``ranker`` is supplied, the kept symbols are the ones most
65
+ relevant to ``query`` rather than an arbitrary slice.
66
+
67
+ ``exclude_files`` names files already sent verbatim IN FULL by another
68
+ context section (small target files in code_context). Their own symbols
69
+ are dropped here so the same code isn't duplicated across two sections;
70
+ their cross-file call-neighbors are still surfaced. Files shown only as a
71
+ windowed excerpt must NOT be excluded — their out-of-window symbols are
72
+ complementary, not duplicate.
73
+ """
74
+ self.initialize()
75
+ excluded = exclude_files or set()
76
+
77
+ context_symbols: Set[str] = set()
78
+ for file in related_files:
79
+ for key, sym in self.graph.symbols.items():
80
+ if sym.file_path == file:
81
+ if file not in excluded:
82
+ context_symbols.add(key)
83
+ context_symbols.update(sym.outgoing_calls)
84
+ context_symbols.update(sym.incoming_calls)
85
+
86
+ if excluded:
87
+ context_symbols = {
88
+ key
89
+ for key in context_symbols
90
+ if key not in self.graph.symbols
91
+ or self.graph.symbols[key].file_path not in excluded
92
+ }
93
+
94
+ if not context_symbols:
95
+ return ""
96
+
97
+ # Cap to avoid blowing LLM context, keeping the most task-relevant
98
+ # symbols when a semantic ranker is available.
99
+ if ranker is not None and len(context_symbols) > max_symbols:
100
+ candidates = {
101
+ key: self.graph.symbols[key].content
102
+ for key in context_symbols
103
+ if key in self.graph.symbols
104
+ }
105
+ symbol_list = ranker.top_k(query, candidates, max_symbols)
106
+ else:
107
+ symbol_list = list(context_symbols)[:max_symbols]
108
+
109
+ output = "## Topological Context\n"
110
+ by_file: Dict[str, List[SymbolNode]] = {}
111
+ for key in symbol_list:
112
+ if key in self.graph.symbols:
113
+ sym = self.graph.symbols[key]
114
+ by_file.setdefault(sym.file_path, []).append(sym)
115
+
116
+ for file_path, syms in by_file.items():
117
+ output += f"\n### Symbols in {file_path}\n"
118
+ for sym in syms:
119
+ output += f"\n# {sym.kind.upper()}: {sym.name}\n{sym.content}\n"
120
+
121
+ if len(context_symbols) > max_symbols:
122
+ output += (
123
+ f"\n(... {len(context_symbols) - max_symbols} more symbols omitted)\n"
124
+ )
125
+
126
+ return output
127
+
128
+ def reference_sites(self, target_files: List[str], max_refs: int = 80) -> str:
129
+ """Every EXTERNAL call site of the symbols defined in ``target_files``.
130
+
131
+ A delete/rename/refactor task must update every reference to the symbol
132
+ it changes, but the referencing files often sit outside the task's
133
+ declared scope — so the model discovers them one build-error at a time
134
+ and runs out of attempts (whack-a-mole). Listing all of them up front,
135
+ with exact file:line, lets one attempt update them completely. Only
136
+ references OUTSIDE the target files are listed (in-file uses are already
137
+ visible in code_context). Returns '' when there are none.
138
+ """
139
+ self.initialize()
140
+ targets = set(target_files)
141
+ blocks: List[str] = []
142
+ shown = 0
143
+ for sym in self.graph.symbols.values():
144
+ if sym.file_path not in targets or not sym.incoming_calls:
145
+ continue
146
+ sites = set()
147
+ for caller_key in sym.incoming_calls:
148
+ caller = self.graph.symbols.get(caller_key)
149
+ if caller is not None and caller.file_path not in targets:
150
+ sites.add((caller.file_path, caller.start_line, caller.name))
151
+ if not sites:
152
+ continue
153
+ lines = [f"- `{sym.name}` ({sym.kind}) is referenced by:"]
154
+ for fp, ln, nm in sorted(sites):
155
+ if shown >= max_refs:
156
+ lines.append(" - (... more references omitted)")
157
+ break
158
+ lines.append(f" - {fp}:{ln} (in {nm})")
159
+ shown += 1
160
+ blocks.append("\n".join(lines))
161
+ if shown >= max_refs:
162
+ break
163
+ if not blocks:
164
+ return ""
165
+ return (
166
+ "## Complete External Reference Sites\n"
167
+ "Every reference to a symbol defined in the files you are editing "
168
+ "that lives OUTSIDE those files. If this task removes, renames, or "
169
+ "changes the signature of such a symbol, you MUST update ALL of these "
170
+ "sites in this attempt — the build fails on any you miss:\n"
171
+ + "\n".join(blocks)
172
+ )