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,184 @@
1
+ """Build mode system ported from /build skill.
2
+
3
+ Determines operating mode from user input and project state.
4
+ """
5
+
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+
11
+ class BuildMode(str, Enum):
12
+ DEBUG = "debug"
13
+ COMPLETE = "complete"
14
+ REVIEW = "review"
15
+ CREATE = "create"
16
+ SPEC = "spec"
17
+ AUTO = "auto"
18
+ SMART = "smart"
19
+
20
+
21
+ class BuildFlags:
22
+ """Parsed flags from user input."""
23
+
24
+ def __init__(
25
+ self,
26
+ budget: float = 100.0,
27
+ commit: bool = False,
28
+ no_verify: bool = False,
29
+ no_suggest: bool = False,
30
+ dry_run: bool = False,
31
+ focus: Optional[str] = None,
32
+ interactive: bool = False,
33
+ parallel: bool = False,
34
+ no_rollback: bool = False,
35
+ allow_dirty: bool = False,
36
+ max_tasks: Optional[int] = None,
37
+ ):
38
+ self.budget = budget
39
+ self.commit = commit
40
+ self.no_verify = no_verify
41
+ self.no_suggest = no_suggest
42
+ self.dry_run = dry_run
43
+ self.focus = focus
44
+ self.interactive = interactive
45
+ self.parallel = parallel
46
+ self.no_rollback = no_rollback
47
+ self.allow_dirty = allow_dirty
48
+ # Per-run ceiling on decomposed tasks (CLI --max-tasks); None = use the
49
+ # config default. Bounds a focused run so a broad spec can't balloon into
50
+ # dozens of tasks and exhaust the budget.
51
+ self.max_tasks = max_tasks
52
+
53
+ def __repr__(self) -> str:
54
+ return (
55
+ f"BuildFlags(budget={self.budget}, commit={self.commit}, "
56
+ f"dry_run={self.dry_run}, focus={self.focus}, interactive={self.interactive})"
57
+ )
58
+
59
+
60
+ def parse_flags(args: list[str]) -> tuple[list[str], BuildFlags]:
61
+ """Extract flags from args list, return (remaining_args, flags)."""
62
+ remaining = []
63
+ flags = BuildFlags()
64
+ i = 0
65
+ while i < len(args):
66
+ arg = args[i]
67
+ if arg == "--budget" and i + 1 < len(args):
68
+ flags.budget = float(args[i + 1])
69
+ i += 2
70
+ elif arg == "--commit":
71
+ flags.commit = True
72
+ i += 1
73
+ elif arg == "--no-verify":
74
+ flags.no_verify = True
75
+ i += 1
76
+ elif arg == "--no-suggest":
77
+ flags.no_suggest = True
78
+ i += 1
79
+ elif arg == "--dry-run":
80
+ flags.dry_run = True
81
+ i += 1
82
+ elif arg == "--interactive" or arg == "-i":
83
+ flags.interactive = True
84
+ i += 1
85
+ elif arg == "--parallel":
86
+ flags.parallel = True
87
+ i += 1
88
+ elif arg == "--no-rollback":
89
+ flags.no_rollback = True
90
+ i += 1
91
+ elif arg == "--allow-dirty":
92
+ flags.allow_dirty = True
93
+ i += 1
94
+ elif arg == "--focus" and i + 1 < len(args):
95
+ flags.focus = args[i + 1]
96
+ i += 2
97
+ elif arg == "--max-tasks" and i + 1 < len(args):
98
+ try:
99
+ flags.max_tasks = int(args[i + 1])
100
+ except ValueError:
101
+ flags.max_tasks = None
102
+ i += 2
103
+ else:
104
+ remaining.append(arg)
105
+ i += 1
106
+ return remaining, flags
107
+
108
+
109
+ def resolve_mode(prompt: str, project_path: Path) -> BuildMode:
110
+ """Determine build mode from prompt content and project state.
111
+
112
+ Resolution order (from /build skill):
113
+ - "debug" -> DEBUG
114
+ - "complete" -> COMPLETE
115
+ - "review" -> REVIEW
116
+ - "new <desc>" -> CREATE
117
+ - path to .md file -> SPEC
118
+ - empty prompt -> AUTO (project exists -> COMPLETE, else CREATE)
119
+ - anything else -> SMART
120
+ """
121
+ stripped = prompt.strip().lower()
122
+
123
+ if stripped == "debug":
124
+ return BuildMode.DEBUG
125
+ if stripped == "complete":
126
+ return BuildMode.COMPLETE
127
+ if stripped == "review":
128
+ return BuildMode.REVIEW
129
+ if stripped.startswith("new "):
130
+ return BuildMode.CREATE
131
+
132
+ # Check if prompt is a path to an existing .md spec file
133
+ if prompt.strip().endswith(".md"):
134
+ spec_path = project_path / prompt.strip()
135
+ if spec_path.exists():
136
+ return BuildMode.SPEC
137
+
138
+ if not stripped:
139
+ # AUTO: detect from project state
140
+ has_code = _project_has_code(project_path)
141
+ return BuildMode.COMPLETE if has_code else BuildMode.CREATE
142
+
143
+ return BuildMode.SMART
144
+
145
+
146
+ def _project_has_code(project_path: Path) -> bool:
147
+ """Check if a project directory contains source code files."""
148
+ code_extensions = {
149
+ ".py",
150
+ ".js",
151
+ ".ts",
152
+ ".rs",
153
+ ".go",
154
+ ".java",
155
+ ".c",
156
+ ".cpp",
157
+ ".rb",
158
+ ".php",
159
+ ".swift",
160
+ ".kt",
161
+ ".scala",
162
+ ".zig",
163
+ }
164
+ for item in project_path.rglob("*"):
165
+ if item.suffix in code_extensions and not _is_venv_or_vendor(item):
166
+ return True
167
+ return False
168
+
169
+
170
+ def _is_venv_or_vendor(path: Path) -> bool:
171
+ """Exclude virtual environments and vendor directories."""
172
+ exclude_dirs = {
173
+ "venv",
174
+ ".venv",
175
+ "env",
176
+ "node_modules",
177
+ "vendor",
178
+ "__pycache__",
179
+ ".git",
180
+ "target",
181
+ "build",
182
+ "dist",
183
+ }
184
+ return any(part in exclude_dirs for part in path.parts)
File without changes
@@ -0,0 +1,89 @@
1
+ """Work recommendation for interactive planning.
2
+
3
+ Given a ProjectAssessment, ask the LLM to propose a short, ranked list of
4
+ concrete work items it would recommend next. Used by the interactive planner
5
+ so the orchestrator composes the plan from the project's live state instead of
6
+ a predefined devplan.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from typing import List
11
+
12
+ from misterdev.core.planning.assessment import ProjectAssessment
13
+ from misterdev.llm.client import BaseLLMClient
14
+ from misterdev.llm.responses import extract_json_array
15
+ from misterdev.logging_setup import setup_logger
16
+
17
+ logger = setup_logger(__name__)
18
+
19
+ VALID_WORK_TYPES = {"debug", "complete", "feature", "refactor", "test", "docs"}
20
+
21
+ RECOMMEND_PROMPT = """You are advising on what to work on next in this project.
22
+
23
+ Project assessment:
24
+ {summary}
25
+
26
+ Incomplete features: {incomplete}
27
+ Broken/stub code: {broken}
28
+ Open TODO/FIXME: {todos}
29
+
30
+ Propose 3-6 concrete, high-value work items, ranked best-first. Favor fixing
31
+ what is broken and completing what is started over new features. Each item must
32
+ be specific enough to act on (name the area or behavior), not a vague theme.
33
+
34
+ Return ONLY a JSON array, each element:
35
+ {{"title": "...", "rationale": "one sentence why it matters now",
36
+ "work_type": "debug|complete|feature|refactor|test|docs"}}
37
+ """
38
+
39
+
40
+ @dataclass
41
+ class Recommendation:
42
+ title: str
43
+ rationale: str
44
+ work_type: str
45
+
46
+
47
+ def recommend_work(
48
+ assessment: ProjectAssessment, llm_client: BaseLLMClient
49
+ ) -> List[Recommendation]:
50
+ """Return a ranked list of recommended work items, or [] on failure."""
51
+ features = assessment.features
52
+ prompt = RECOMMEND_PROMPT.format(
53
+ summary=assessment.summary(),
54
+ incomplete=_names(getattr(features, "incomplete", [])),
55
+ broken=_names(getattr(features, "broken", [])),
56
+ todos=len(getattr(features, "todos", []) or []),
57
+ )
58
+ try:
59
+ response = llm_client.generate_code(
60
+ prompt, "You are a pragmatic engineering lead. Return only JSON."
61
+ )
62
+ except Exception as e:
63
+ logger.error(f"Failed to generate recommendations: {e}")
64
+ return []
65
+ raw = extract_json_array(response)
66
+
67
+ recs: List[Recommendation] = []
68
+ for item in raw:
69
+ if not isinstance(item, dict) or not item.get("title"):
70
+ continue
71
+ wt = str(item.get("work_type", "complete")).lower()
72
+ if wt not in VALID_WORK_TYPES:
73
+ wt = "complete"
74
+ recs.append(
75
+ Recommendation(
76
+ title=str(item["title"]).strip(),
77
+ rationale=str(item.get("rationale", "")).strip(),
78
+ work_type=wt,
79
+ )
80
+ )
81
+ return recs
82
+
83
+
84
+ def _names(items) -> str:
85
+ out = []
86
+ for it in items:
87
+ name = getattr(it, "name", None) or getattr(it, "title", None) or str(it)
88
+ out.append(str(name))
89
+ return ", ".join(out[:12]) if out else "none"
@@ -0,0 +1,135 @@
1
+ """Project assessment models ported from /build skill Phase 1.
2
+
3
+ Structured representations of project analysis results used to drive
4
+ all subsequent build phases.
5
+ """
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+ from typing import Optional
9
+
10
+
11
+ class _AssessmentModel(BaseModel):
12
+ """Base for the assessment models.
13
+
14
+ ``validate_assignment`` makes attribute writes (not just construction)
15
+ type-checked. The pipeline mutates these models field-by-field straight from
16
+ LLM JSON (see the analyzer's merge step), so without this a ``null`` or
17
+ wrong-typed write would be stored silently and only crash much later; with it
18
+ a bad write fails fast, at the assignment site.
19
+ """
20
+
21
+ model_config = ConfigDict(validate_assignment=True)
22
+
23
+
24
+ class HealthCheck(_AssessmentModel):
25
+ """Result of running build, test, and lint commands."""
26
+
27
+ builds: bool = False
28
+ build_output: str = ""
29
+ tests_pass: bool = False
30
+ test_count: int = 0
31
+ test_failures: int = 0
32
+ test_output: str = ""
33
+ lint_clean: bool = False
34
+ lint_warnings: int = 0
35
+ lint_output: str = ""
36
+ missing_dependencies: list[str] = Field(default_factory=list)
37
+
38
+
39
+ class FeatureInfo(_AssessmentModel):
40
+ """A single feature with evidence of its state."""
41
+
42
+ name: str
43
+ description: str = ""
44
+ evidence_files: list[str] = Field(default_factory=list)
45
+ complexity: str = "medium" # trivial, small, medium, large, architectural
46
+
47
+
48
+ class FeatureInventory(_AssessmentModel):
49
+ """Completeness analysis from /build Phase 1b."""
50
+
51
+ existing: list[FeatureInfo] = Field(default_factory=list)
52
+ incomplete: list[FeatureInfo] = Field(default_factory=list)
53
+ missing: list[FeatureInfo] = Field(default_factory=list)
54
+ dead_code: list[str] = Field(default_factory=list)
55
+ stubs: list[str] = Field(default_factory=list)
56
+ broken: list[str] = Field(default_factory=list)
57
+ todos: list[dict] = Field(default_factory=list)
58
+
59
+
60
+ class ProjectStructure(_AssessmentModel):
61
+ """Structural profile from /build Phase 1a."""
62
+
63
+ project_type: str = "unknown" # web-api, web-app, cli, library, etc.
64
+ languages: list[str] = Field(default_factory=list)
65
+ frameworks: list[str] = Field(default_factory=list)
66
+ build_system: Optional[str] = None
67
+ build_command: Optional[str] = None
68
+ test_system: Optional[str] = None
69
+ test_command: Optional[str] = None
70
+ lint_command: Optional[str] = None
71
+ package_manager: Optional[str] = None
72
+ entry_points: list[str] = Field(default_factory=list)
73
+ directory_structure: str = ""
74
+
75
+
76
+ class ProjectContext(_AssessmentModel):
77
+ """Contextual information from /build Phase 1c."""
78
+
79
+ purpose: str = ""
80
+ goals: str = ""
81
+ conventions: str = ""
82
+ constraints: str = ""
83
+ recent_activity: str = ""
84
+ stated_requirements: str = ""
85
+ reference_impl: Optional[str] = None
86
+
87
+
88
+ class TechnicalDebt(_AssessmentModel):
89
+ """Technical debt estimation from /build Phase 1."""
90
+
91
+ score: int = 0 # 0-100
92
+ description: str = ""
93
+ critical_issues: list[str] = Field(default_factory=list)
94
+
95
+
96
+ class RiskAssessment(_AssessmentModel):
97
+ """Risk analysis for the proposed build."""
98
+
99
+ level: str = "low" # low, medium, high, critical
100
+ factors: list[str] = Field(default_factory=list)
101
+ mitigations: list[str] = Field(default_factory=list)
102
+
103
+
104
+ class ProjectAssessment(_AssessmentModel):
105
+ """Merged assessment from all Phase 1 agents.
106
+
107
+ This is the central data structure that drives Phases 2-6.
108
+ """
109
+
110
+ structure: ProjectStructure = Field(default_factory=ProjectStructure)
111
+ health: HealthCheck = Field(default_factory=HealthCheck)
112
+ features: FeatureInventory = Field(default_factory=FeatureInventory)
113
+ context: ProjectContext = Field(default_factory=ProjectContext)
114
+ tech_debt: TechnicalDebt = Field(default_factory=TechnicalDebt)
115
+ risk: RiskAssessment = Field(default_factory=RiskAssessment)
116
+
117
+ def summary(self) -> str:
118
+ """One-line summary for logging."""
119
+ s = self.structure
120
+ h = self.health
121
+ lang = ", ".join(s.languages) if s.languages else "unknown"
122
+ build_status = "OK" if h.builds else "FAIL"
123
+ # Clamp the passing count: some runner parsers fill test_count and
124
+ # test_failures from independent regexes, so a malformed log can yield
125
+ # failures > count, which would otherwise render a negative "passed".
126
+ test_status = (
127
+ f"{max(0, h.test_count - h.test_failures)}/{h.test_count}"
128
+ if h.test_count
129
+ else "none"
130
+ )
131
+ return (
132
+ f"[{s.project_type}] {lang} | "
133
+ f"build={build_status} tests={test_status} "
134
+ f"lint_warnings={h.lint_warnings}"
135
+ )