mycode-coding-agent 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.
- mycode/__init__.py +0 -0
- mycode/adapters/__init__.py +21 -0
- mycode/adapters/jsonl.py +692 -0
- mycode/agent/__init__.py +25 -0
- mycode/agent/events.py +111 -0
- mycode/agent/outcome.py +103 -0
- mycode/agent/progress.py +373 -0
- mycode/agent/runner.py +1481 -0
- mycode/application/__init__.py +38 -0
- mycode/application/agent_session.py +367 -0
- mycode/application/events.py +59 -0
- mycode/application/runtime.py +211 -0
- mycode/application/sessions.py +180 -0
- mycode/cli.py +840 -0
- mycode/config.py +355 -0
- mycode/context/__init__.py +1 -0
- mycode/context/artifacts.py +672 -0
- mycode/context/budget.py +752 -0
- mycode/context/builder.py +112 -0
- mycode/context/compact.py +795 -0
- mycode/context/tool_result_format.py +199 -0
- mycode/context/tool_result_retention.py +261 -0
- mycode/conversation.py +78 -0
- mycode/error_handling.py +481 -0
- mycode/event_format.py +147 -0
- mycode/instructions.py +285 -0
- mycode/llm.py +771 -0
- mycode/mcp/__init__.py +41 -0
- mycode/mcp/client.py +44 -0
- mycode/mcp/config.py +207 -0
- mycode/mcp/errors.py +302 -0
- mycode/mcp/manager.py +339 -0
- mycode/mcp/models.py +20 -0
- mycode/mcp/result_adapter.py +58 -0
- mycode/mcp/tool_adapter.py +145 -0
- mycode/mcp/trust.py +313 -0
- mycode/memory.py +570 -0
- mycode/memory_context.py +245 -0
- mycode/messages.py +63 -0
- mycode/observability.py +28 -0
- mycode/permissions.py +262 -0
- mycode/persistence/__init__.py +1 -0
- mycode/persistence/filesystem.py +291 -0
- mycode/persistence/project_storage.py +208 -0
- mycode/persistence/session_lock.py +138 -0
- mycode/persistence/session_store.py +503 -0
- mycode/presentation/__init__.py +1 -0
- mycode/presentation/cli/__init__.py +14 -0
- mycode/presentation/cli/confirmer.py +116 -0
- mycode/presentation/cli/mcp_trust.py +61 -0
- mycode/presentation/cli/presenter.py +320 -0
- mycode/presentation/cli/session_menu.py +146 -0
- mycode/presentation/cli/subagent_observer.py +124 -0
- mycode/presentation/command_format.py +90 -0
- mycode/presentation/commands.py +95 -0
- mycode/presentation/tui/__init__.py +6 -0
- mycode/presentation/tui/app.py +1351 -0
- mycode/presentation/tui/interactions.py +253 -0
- mycode/presentation/tui/presenter.py +266 -0
- mycode/presentation/tui/screens.py +305 -0
- mycode/presentation/tui/widgets.py +214 -0
- mycode/project.py +22 -0
- mycode/prompts.py +181 -0
- mycode/reasoning.py +40 -0
- mycode/session.py +86 -0
- mycode/skills/__init__.py +27 -0
- mycode/skills/builtin/database-recovery/SKILL.md +138 -0
- mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
- mycode/skills/registry.py +295 -0
- mycode/skills/state.py +68 -0
- mycode/subagents/__init__.py +1 -0
- mycode/subagents/audit.py +212 -0
- mycode/subagents/concurrency.py +124 -0
- mycode/subagents/contracts.py +421 -0
- mycode/subagents/delegate.py +80 -0
- mycode/subagents/delegation.py +128 -0
- mycode/subagents/lifecycle.py +86 -0
- mycode/subagents/limits.py +7 -0
- mycode/subagents/observability.py +150 -0
- mycode/subagents/persistence.py +152 -0
- mycode/subagents/profiles.py +184 -0
- mycode/subagents/prompts.py +67 -0
- mycode/subagents/results.py +178 -0
- mycode/subagents/runtime.py +528 -0
- mycode/subagents/snapshots.py +211 -0
- mycode/subagents/tool_batch.py +260 -0
- mycode/tools/__init__.py +81 -0
- mycode/tools/base.py +222 -0
- mycode/tools/bounds.py +14 -0
- mycode/tools/command_executor.py +167 -0
- mycode/tools/command_output.py +166 -0
- mycode/tools/command_risk.py +596 -0
- mycode/tools/defaults.py +59 -0
- mycode/tools/edit_file.py +524 -0
- mycode/tools/file_mutation.py +30 -0
- mycode/tools/glob.py +247 -0
- mycode/tools/grep.py +324 -0
- mycode/tools/ignore.py +122 -0
- mycode/tools/inspect_changes.py +269 -0
- mycode/tools/load_skill.py +92 -0
- mycode/tools/memory.py +264 -0
- mycode/tools/path_permissions.py +78 -0
- mycode/tools/patterns.py +48 -0
- mycode/tools/permission_metadata.py +27 -0
- mycode/tools/process_tree.py +166 -0
- mycode/tools/read_file.py +242 -0
- mycode/tools/read_skill_resource.py +93 -0
- mycode/tools/registry.py +279 -0
- mycode/tools/run_command.py +237 -0
- mycode/tools/run_skill_script.py +206 -0
- mycode/tools/run_validation.py +107 -0
- mycode/tools/submit_result.py +93 -0
- mycode/tools/text.py +15 -0
- mycode/tools/validation_command.py +377 -0
- mycode/tools/workspace.py +33 -0
- mycode/tools/write_file.py +169 -0
- mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
- mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
- mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- mycode_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
mycode/instructions.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import hashlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
NATIVE_INSTRUCTION_FILE = "MYCODE.md"
|
|
8
|
+
SHARED_INSTRUCTION_FILE = "AGENTS.md"
|
|
9
|
+
COMPATIBLE_INSTRUCTION_FILE = "CLAUDE.md"
|
|
10
|
+
INSTRUCTION_FILE_PRIORITY = (
|
|
11
|
+
NATIVE_INSTRUCTION_FILE,
|
|
12
|
+
SHARED_INSTRUCTION_FILE,
|
|
13
|
+
COMPATIBLE_INSTRUCTION_FILE,
|
|
14
|
+
)
|
|
15
|
+
DEFAULT_MAX_INSTRUCTION_FILE_BYTES = 32 * 1024
|
|
16
|
+
DEFAULT_MAX_INSTRUCTION_CHARS = 64 * 1024
|
|
17
|
+
|
|
18
|
+
InstructionScope = Literal["user", "project", "directory"]
|
|
19
|
+
InstructionIssueCode = Literal[
|
|
20
|
+
"outside_scope",
|
|
21
|
+
"broken_symlink",
|
|
22
|
+
"not_a_file",
|
|
23
|
+
"file_too_large",
|
|
24
|
+
"invalid_utf8",
|
|
25
|
+
"total_too_large",
|
|
26
|
+
"read_error",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class InstructionPathError(ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class InstructionLimits:
|
|
36
|
+
max_file_bytes: int = DEFAULT_MAX_INSTRUCTION_FILE_BYTES
|
|
37
|
+
max_total_chars: int = DEFAULT_MAX_INSTRUCTION_CHARS
|
|
38
|
+
|
|
39
|
+
def __post_init__(self) -> None:
|
|
40
|
+
if self.max_file_bytes < 1:
|
|
41
|
+
raise ValueError("max_file_bytes must be at least 1.")
|
|
42
|
+
if self.max_total_chars < 1:
|
|
43
|
+
raise ValueError("max_total_chars must be at least 1.")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class InstructionSource:
|
|
48
|
+
path: Path
|
|
49
|
+
scope: InstructionScope
|
|
50
|
+
content: str
|
|
51
|
+
content_bytes: int = 0
|
|
52
|
+
sha256: str = ""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def label(self) -> str:
|
|
56
|
+
return f"{self.scope}: {self.path}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class InstructionIssue:
|
|
61
|
+
path: Path
|
|
62
|
+
code: InstructionIssueCode
|
|
63
|
+
message: str
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def display(self) -> str:
|
|
67
|
+
return f"{self.code}: {self.path}: {self.message}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class InstructionBundle:
|
|
72
|
+
sources: tuple[InstructionSource, ...] = ()
|
|
73
|
+
issues: tuple[InstructionIssue, ...] = ()
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def total_chars(self) -> int:
|
|
77
|
+
return sum(len(source.content) for source in self.sources)
|
|
78
|
+
|
|
79
|
+
def to_prompt_text(self) -> str:
|
|
80
|
+
sections: list[str] = []
|
|
81
|
+
for source in self.sources:
|
|
82
|
+
sections.append(
|
|
83
|
+
f"### {source.label}\n\n{source.content.rstrip()}"
|
|
84
|
+
)
|
|
85
|
+
return "\n\n".join(sections)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def default_user_instruction_directory() -> Path:
|
|
89
|
+
return Path.home() / ".mycode"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def load_instruction_bundle(
|
|
93
|
+
workspace_root: str | Path,
|
|
94
|
+
*,
|
|
95
|
+
working_directory: str | Path | None = None,
|
|
96
|
+
user_instruction_directory: str | Path | None = None,
|
|
97
|
+
limits: InstructionLimits | None = None,
|
|
98
|
+
) -> InstructionBundle:
|
|
99
|
+
resolved_workspace = _require_directory(workspace_root, label="workspace_root")
|
|
100
|
+
resolved_working_directory = _require_directory(
|
|
101
|
+
resolved_workspace if working_directory is None else working_directory,
|
|
102
|
+
label="working_directory",
|
|
103
|
+
)
|
|
104
|
+
if not resolved_working_directory.is_relative_to(resolved_workspace):
|
|
105
|
+
raise InstructionPathError(
|
|
106
|
+
"working_directory must be inside workspace_root: "
|
|
107
|
+
f"{resolved_working_directory}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
resolved_user_directory = Path(
|
|
111
|
+
default_user_instruction_directory()
|
|
112
|
+
if user_instruction_directory is None
|
|
113
|
+
else user_instruction_directory
|
|
114
|
+
).resolve(strict=False)
|
|
115
|
+
effective_limits = InstructionLimits() if limits is None else limits
|
|
116
|
+
|
|
117
|
+
candidates: list[tuple[Path, InstructionScope, Path]] = []
|
|
118
|
+
user_candidate = _select_instruction_file(resolved_user_directory)
|
|
119
|
+
if user_candidate is not None:
|
|
120
|
+
candidates.append(
|
|
121
|
+
(user_candidate, "user", resolved_user_directory.parent)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
directories = _instruction_directories(
|
|
125
|
+
resolved_workspace,
|
|
126
|
+
resolved_working_directory,
|
|
127
|
+
)
|
|
128
|
+
for index, directory in enumerate(directories):
|
|
129
|
+
candidate = _select_instruction_file(directory)
|
|
130
|
+
if candidate is None:
|
|
131
|
+
continue
|
|
132
|
+
scope: InstructionScope = "project" if index == 0 else "directory"
|
|
133
|
+
candidates.append((candidate, scope, resolved_workspace))
|
|
134
|
+
|
|
135
|
+
sources: list[InstructionSource] = []
|
|
136
|
+
issues: list[InstructionIssue] = []
|
|
137
|
+
seen_paths: set[Path] = set()
|
|
138
|
+
total_chars = 0
|
|
139
|
+
|
|
140
|
+
for candidate, scope, allowed_root in candidates:
|
|
141
|
+
source, issue = _read_instruction_source(
|
|
142
|
+
candidate,
|
|
143
|
+
scope=scope,
|
|
144
|
+
allowed_root=allowed_root,
|
|
145
|
+
limits=effective_limits,
|
|
146
|
+
)
|
|
147
|
+
if issue is not None:
|
|
148
|
+
issues.append(issue)
|
|
149
|
+
continue
|
|
150
|
+
if source is None or source.path in seen_paths:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
next_total = total_chars + len(source.content)
|
|
154
|
+
if next_total > effective_limits.max_total_chars:
|
|
155
|
+
issues.append(
|
|
156
|
+
InstructionIssue(
|
|
157
|
+
path=source.path,
|
|
158
|
+
code="total_too_large",
|
|
159
|
+
message=(
|
|
160
|
+
"instruction total would exceed "
|
|
161
|
+
f"{effective_limits.max_total_chars} characters"
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
seen_paths.add(source.path)
|
|
168
|
+
sources.append(source)
|
|
169
|
+
total_chars = next_total
|
|
170
|
+
|
|
171
|
+
return InstructionBundle(
|
|
172
|
+
sources=tuple(sources),
|
|
173
|
+
issues=tuple(issues),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _require_directory(path: str | Path, *, label: str) -> Path:
|
|
178
|
+
resolved = Path(path).resolve(strict=False)
|
|
179
|
+
if not resolved.exists():
|
|
180
|
+
raise InstructionPathError(f"{label} does not exist: {resolved}")
|
|
181
|
+
if not resolved.is_dir():
|
|
182
|
+
raise InstructionPathError(f"{label} is not a directory: {resolved}")
|
|
183
|
+
return resolved
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _select_instruction_file(directory: Path) -> Path | None:
|
|
187
|
+
for file_name in INSTRUCTION_FILE_PRIORITY:
|
|
188
|
+
candidate = directory / file_name
|
|
189
|
+
if candidate.exists() or candidate.is_symlink():
|
|
190
|
+
return candidate
|
|
191
|
+
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _instruction_directories(workspace_root: Path, working_directory: Path) -> list[Path]:
|
|
196
|
+
directories = [workspace_root]
|
|
197
|
+
current = workspace_root
|
|
198
|
+
relative = working_directory.relative_to(workspace_root)
|
|
199
|
+
for part in relative.parts:
|
|
200
|
+
current = current / part
|
|
201
|
+
directories.append(current)
|
|
202
|
+
return directories
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _read_instruction_source(
|
|
206
|
+
path: Path,
|
|
207
|
+
*,
|
|
208
|
+
scope: InstructionScope,
|
|
209
|
+
allowed_root: Path,
|
|
210
|
+
limits: InstructionLimits,
|
|
211
|
+
) -> tuple[InstructionSource | None, InstructionIssue | None]:
|
|
212
|
+
if path.is_symlink() and not path.exists():
|
|
213
|
+
return None, InstructionIssue(
|
|
214
|
+
path=path,
|
|
215
|
+
code="broken_symlink",
|
|
216
|
+
message="instruction symlink target does not exist",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
resolved = path.resolve(strict=True)
|
|
221
|
+
except OSError as error:
|
|
222
|
+
return None, InstructionIssue(
|
|
223
|
+
path=path,
|
|
224
|
+
code="read_error",
|
|
225
|
+
message=str(error),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if not resolved.is_relative_to(allowed_root.resolve(strict=False)):
|
|
229
|
+
return None, InstructionIssue(
|
|
230
|
+
path=path,
|
|
231
|
+
code="outside_scope",
|
|
232
|
+
message=f"resolved path is outside {allowed_root}",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if not resolved.is_file():
|
|
236
|
+
return None, InstructionIssue(
|
|
237
|
+
path=resolved,
|
|
238
|
+
code="not_a_file",
|
|
239
|
+
message="instruction path is not a regular file",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
file_size = resolved.stat().st_size
|
|
244
|
+
if file_size > limits.max_file_bytes:
|
|
245
|
+
return None, InstructionIssue(
|
|
246
|
+
path=resolved,
|
|
247
|
+
code="file_too_large",
|
|
248
|
+
message=(
|
|
249
|
+
f"file is {file_size} bytes; limit is "
|
|
250
|
+
f"{limits.max_file_bytes} bytes"
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
raw_content = resolved.read_bytes()
|
|
254
|
+
if len(raw_content) > limits.max_file_bytes:
|
|
255
|
+
return None, InstructionIssue(
|
|
256
|
+
path=resolved,
|
|
257
|
+
code="file_too_large",
|
|
258
|
+
message=(
|
|
259
|
+
f"file is {len(raw_content)} bytes; limit is "
|
|
260
|
+
f"{limits.max_file_bytes} bytes"
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
except OSError as error:
|
|
264
|
+
return None, InstructionIssue(
|
|
265
|
+
path=resolved,
|
|
266
|
+
code="read_error",
|
|
267
|
+
message=str(error),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
content = raw_content.decode("utf-8-sig")
|
|
272
|
+
except UnicodeDecodeError:
|
|
273
|
+
return None, InstructionIssue(
|
|
274
|
+
path=resolved,
|
|
275
|
+
code="invalid_utf8",
|
|
276
|
+
message="instruction file must be valid UTF-8",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return InstructionSource(
|
|
280
|
+
path=resolved,
|
|
281
|
+
scope=scope,
|
|
282
|
+
content=content,
|
|
283
|
+
content_bytes=len(raw_content),
|
|
284
|
+
sha256=hashlib.sha256(raw_content).hexdigest(),
|
|
285
|
+
), None
|