millforge 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 (116) hide show
  1. millforge/__init__.py +1174 -0
  2. millforge/_forge/LICENSE +21 -0
  3. millforge/_forge/PROVENANCE.json +295 -0
  4. millforge/_forge/UPDATE_POLICY.md +24 -0
  5. millforge/_forge/__init__.py +14 -0
  6. millforge/_forge/adapter.py +2232 -0
  7. millforge/_forge/base_runner.py +121 -0
  8. millforge/_forge/clients/__init__.py +10 -0
  9. millforge/_forge/clients/base.py +200 -0
  10. millforge/_forge/context/__init__.py +23 -0
  11. millforge/_forge/context/manager.py +178 -0
  12. millforge/_forge/context/strategies.py +335 -0
  13. millforge/_forge/core/__init__.py +16 -0
  14. millforge/_forge/core/inference.py +433 -0
  15. millforge/_forge/core/messages.py +119 -0
  16. millforge/_forge/core/runner.py +479 -0
  17. millforge/_forge/core/steps.py +108 -0
  18. millforge/_forge/core/workflow.py +400 -0
  19. millforge/_forge/errors.py +222 -0
  20. millforge/_forge/guardrails/__init__.py +21 -0
  21. millforge/_forge/guardrails/error_tracker.py +71 -0
  22. millforge/_forge/guardrails/guardrails.py +194 -0
  23. millforge/_forge/guardrails/nudge.py +47 -0
  24. millforge/_forge/guardrails/response_validator.py +119 -0
  25. millforge/_forge/guardrails/step_enforcer.py +183 -0
  26. millforge/_forge/prompts/__init__.py +16 -0
  27. millforge/_forge/prompts/nudges.py +95 -0
  28. millforge/_forge/prompts/templates.py +285 -0
  29. millforge/_version.py +3 -0
  30. millforge/artifacts.py +570 -0
  31. millforge/base/__init__.py +97 -0
  32. millforge/base/composition.py +402 -0
  33. millforge/base/context.py +285 -0
  34. millforge/base/harness.py +138 -0
  35. millforge/base/identity.py +465 -0
  36. millforge/base/options.py +34 -0
  37. millforge/base/platform.py +17 -0
  38. millforge/base/prompt.py +317 -0
  39. millforge/base/runner.py +546 -0
  40. millforge/compiled_plan.py +970 -0
  41. millforge/compiler/__init__.py +231 -0
  42. millforge/compiler/artifact_validation.py +257 -0
  43. millforge/compiler/canonicalization.py +169 -0
  44. millforge/compiler/capabilities.py +66 -0
  45. millforge/compiler/catalogs.py +500 -0
  46. millforge/compiler/diagnostics.py +491 -0
  47. millforge/compiler/graph.py +678 -0
  48. millforge/compiler/lowering.py +198 -0
  49. millforge/compiler/output.py +692 -0
  50. millforge/compiler/parsing.py +1424 -0
  51. millforge/compiler/requests.py +1180 -0
  52. millforge/compiler/schema_validation.py +272 -0
  53. millforge/compiler/semantic.py +490 -0
  54. millforge/compiler/service.py +448 -0
  55. millforge/compiler/source.py +375 -0
  56. millforge/compiler/validators.py +184 -0
  57. millforge/connectors/__init__.py +95 -0
  58. millforge/connectors/admission.py +801 -0
  59. millforge/connectors/broker.py +202 -0
  60. millforge/connectors/contracts.py +1159 -0
  61. millforge/connectors/diagnostics.py +189 -0
  62. millforge/connectors/fake.py +66 -0
  63. millforge/connectors/runtime.py +236 -0
  64. millforge/contracts.py +2860 -0
  65. millforge/custom_tools/__init__.py +67 -0
  66. millforge/custom_tools/compiler.py +724 -0
  67. millforge/custom_tools/contracts.py +1093 -0
  68. millforge/custom_tools/diagnostics.py +205 -0
  69. millforge/eval_artifacts.py +952 -0
  70. millforge/eval_boundary.py +2435 -0
  71. millforge/eval_fixtures/__init__.py +1 -0
  72. millforge/eval_fixtures/default_pack/__init__.py +1 -0
  73. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.bug_diagnosis.traceback.v1.json +52 -0
  74. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.direct_edit.import_sort.v1.json +52 -0
  75. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.evidence_discipline.no_source_change.v1.json +51 -0
  76. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.false_closure.visible_green.v1.json +52 -0
  77. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.multi_file.api_contract.v1.json +54 -0
  78. millforge/eval_fixtures/default_pack/fixtures/fixture.08a.recovery.malformed_artifact.v1.json +54 -0
  79. millforge/eval_fixtures/default_pack/manifest.json +12 -0
  80. millforge/eval_modes.py +1282 -0
  81. millforge/eval_presets.py +1398 -0
  82. millforge/eval_reports.py +2517 -0
  83. millforge/eval_suite.py +2429 -0
  84. millforge/eval_trials.py +2632 -0
  85. millforge/eval_workflow.py +794 -0
  86. millforge/exceptions.py +122 -0
  87. millforge/model_backend.py +2098 -0
  88. millforge/protocols.py +340 -0
  89. millforge/py.typed +0 -0
  90. millforge/runtime.py +1791 -0
  91. millforge/testing/__init__.py +1089 -0
  92. millforge/tools/__init__.py +83 -0
  93. millforge/tools/builtin_runtime.py +1339 -0
  94. millforge/tools/builtins.py +773 -0
  95. millforge/tools/execution.py +1545 -0
  96. millforge/tools/path_policy.py +155 -0
  97. millforge/tools/pi_compat/PI_LICENSE +21 -0
  98. millforge/tools/pi_compat/PROVENANCE.json +55 -0
  99. millforge/tools/pi_compat/UPDATE_POLICY.md +36 -0
  100. millforge/tools/pi_compat/__init__.py +34 -0
  101. millforge/tools/pi_compat/contracts.py +49 -0
  102. millforge/tools/pi_compat/editing.py +390 -0
  103. millforge/tools/pi_compat/mutations.py +57 -0
  104. millforge/tools/pi_compat/operations.py +401 -0
  105. millforge/tools/pi_compat/paths.py +155 -0
  106. millforge/tools/pi_compat/process.py +1375 -0
  107. millforge/tools/pi_compat/search.py +738 -0
  108. millforge/tools/pi_compat/truncation.py +267 -0
  109. millforge/tools/pi_compat_catalog.py +396 -0
  110. millforge/tools/pi_compat_runtime.py +460 -0
  111. millforge/tools/registry.py +553 -0
  112. millforge/tools/results.py +533 -0
  113. millforge-0.1.0.dist-info/METADATA +844 -0
  114. millforge-0.1.0.dist-info/RECORD +116 -0
  115. millforge-0.1.0.dist-info/WHEEL +4 -0
  116. millforge-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,317 @@
1
+ """Pi-derived system-prompt assembly for ``millforge-base``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ import hashlib
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from pydantic import (
11
+ BaseModel,
12
+ ConfigDict,
13
+ Field,
14
+ StrictBool,
15
+ StrictInt,
16
+ StrictStr,
17
+ field_validator,
18
+ )
19
+
20
+ from millforge.base.context import (
21
+ MillforgeBaseContextSnapshot,
22
+ _bounded_diagnostic,
23
+ _resolved_absolute_path,
24
+ _truncate_to_utf8_bytes,
25
+ _validate_diagnostics,
26
+ _validate_sha256,
27
+ )
28
+ from millforge.base.options import MillforgeBaseOptions
29
+
30
+ __all__ = [
31
+ "MillforgeBasePromptBudgetError",
32
+ "MillforgeBasePromptSnapshot",
33
+ "build_millforge_base_system_prompt",
34
+ ]
35
+
36
+ _PROMPT_BYTE_LIMIT = 65_536
37
+ _APPEND_PREFIX = "\n\n"
38
+ _CONTEXT_PREFIX = (
39
+ "\n\n<project_context>\n\nProject-specific instructions and guidelines:\n\n"
40
+ )
41
+ _CONTEXT_SUFFIX = "</project_context>\n"
42
+
43
+ _DEFAULT_SYSTEM_PROMPT = (
44
+ "You are an expert coding assistant operating inside Millforge, a coding agent "
45
+ "harness. You help users by reading files, executing commands, editing code, "
46
+ "and writing new files.\n\n"
47
+ "Available tools:\n"
48
+ "- read: Read file contents\n"
49
+ "- bash: Execute commands in the resolved host shell\n"
50
+ "- edit: Make precise file edits, including multiple disjoint edits in one call\n"
51
+ "- write: Create or overwrite files\n"
52
+ "- grep: Search file contents for patterns (respects .gitignore)\n"
53
+ "- find: Find files by glob pattern (respects .gitignore)\n"
54
+ "- ls: List directory contents\n"
55
+ "- submit: Complete the current run\n"
56
+ "- block: Report that the current run is blocked\n"
57
+ "- reject: Reject the current run\n\n"
58
+ "Guidelines:\n"
59
+ "- Use write only for new files or complete rewrites.\n"
60
+ "- Use edit for precise changes; every edits[].oldText must uniquely match the "
61
+ "original file.\n"
62
+ "- Put multiple disjoint changes to one file in one edit call and never overlap "
63
+ "edit regions.\n"
64
+ "- Be concise in your responses.\n"
65
+ "- Show file paths clearly when working with files.\n"
66
+ "- Filesystem and shell tools run with the permissions of the Millforge process "
67
+ "and are not sandboxed.\n"
68
+ "- Finish by calling submit, block, or reject with the corresponding fixed "
69
+ "terminal_result and a concise summary."
70
+ )
71
+
72
+
73
+ class MillforgeBasePromptBudgetError(ValueError):
74
+ """Raised when the immutable prompt footer cannot fit its byte budget."""
75
+
76
+
77
+ class MillforgeBasePromptSnapshot(BaseModel):
78
+ """The final bounded system prompt and its deterministic accounting."""
79
+
80
+ model_config = ConfigDict(extra="forbid", frozen=True)
81
+
82
+ system_instructions: StrictStr
83
+ effective_prompt_sha256: StrictStr
84
+ byte_count: StrictInt = Field(ge=0)
85
+ prompt_date: datetime.date
86
+ truncated: StrictBool
87
+ diagnostics: tuple[StrictStr, ...]
88
+
89
+ @field_validator("effective_prompt_sha256")
90
+ @classmethod
91
+ def _effective_prompt_sha256_is_valid(cls, value: str) -> str:
92
+ return _validate_sha256(value, "effective_prompt_sha256")
93
+
94
+ @field_validator("diagnostics")
95
+ @classmethod
96
+ def _diagnostics_are_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
97
+ return _validate_diagnostics(value)
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class _PromptFile:
102
+ content: str
103
+ raw_sha256: str
104
+
105
+
106
+ def _prompt_candidates(
107
+ cwd: Path, home_directory: Path, filename: str
108
+ ) -> tuple[Path, ...]:
109
+ return (
110
+ cwd / ".millforge" / filename,
111
+ cwd / ".pi" / filename,
112
+ home_directory / ".millforge" / filename,
113
+ home_directory / ".pi" / "agent" / filename,
114
+ )
115
+
116
+
117
+ def _discover_prompt_file(
118
+ *, cwd: Path, home_directory: Path, filename: str, diagnostics: list[str]
119
+ ) -> _PromptFile | None:
120
+ for candidate in _prompt_candidates(cwd, home_directory, filename):
121
+ resolved = candidate.resolve()
122
+ if not resolved.exists():
123
+ continue
124
+ try:
125
+ raw = resolved.read_bytes()
126
+ except OSError as error:
127
+ diagnostics.append(
128
+ _bounded_diagnostic(f"Could not read {resolved}: {error}")
129
+ )
130
+ continue
131
+ return _PromptFile(
132
+ content=_truncate_to_utf8_bytes(
133
+ raw.decode("utf-8", errors="replace"), _PROMPT_BYTE_LIMIT
134
+ ),
135
+ raw_sha256=hashlib.sha256(raw).hexdigest(),
136
+ )
137
+ return None
138
+
139
+
140
+ def _footer(*, prompt_date: datetime.date, cwd: Path) -> str:
141
+ return (
142
+ f"\nCurrent date: {prompt_date.isoformat()}"
143
+ f"\nCurrent working directory: {cwd.as_posix()}"
144
+ )
145
+
146
+
147
+ def _record_prefix(path: Path) -> str:
148
+ return f'<project_instructions path="{path.as_posix()}">\n'
149
+
150
+
151
+ def _snapshot(
152
+ *,
153
+ prompt: str,
154
+ prompt_date: datetime.date,
155
+ truncated: bool,
156
+ diagnostics: tuple[str, ...],
157
+ ) -> MillforgeBasePromptSnapshot:
158
+ encoded = prompt.encode("utf-8")
159
+ return MillforgeBasePromptSnapshot(
160
+ system_instructions=prompt,
161
+ effective_prompt_sha256=hashlib.sha256(encoded).hexdigest(),
162
+ byte_count=len(encoded),
163
+ prompt_date=prompt_date,
164
+ truncated=truncated,
165
+ diagnostics=diagnostics,
166
+ )
167
+
168
+
169
+ def build_millforge_base_system_prompt(
170
+ *,
171
+ options: MillforgeBaseOptions,
172
+ context: MillforgeBaseContextSnapshot,
173
+ cwd: Path,
174
+ home_directory: Path,
175
+ prompt_date: datetime.date,
176
+ ) -> MillforgeBasePromptSnapshot:
177
+ """Assemble the exact finite Pi-derived prompt order and framing."""
178
+
179
+ resolved_cwd = _resolved_absolute_path(cwd, "cwd")
180
+ resolved_home = _resolved_absolute_path(home_directory, "home_directory")
181
+ return _build_millforge_base_system_prompt_resolved(
182
+ options=options,
183
+ context=context,
184
+ cwd=resolved_cwd,
185
+ home_directory=resolved_home,
186
+ prompt_date=prompt_date,
187
+ )
188
+
189
+
190
+ def _build_millforge_base_system_prompt_resolved(
191
+ *,
192
+ options: MillforgeBaseOptions,
193
+ context: MillforgeBaseContextSnapshot,
194
+ cwd: Path,
195
+ home_directory: Path,
196
+ prompt_date: datetime.date,
197
+ ) -> MillforgeBasePromptSnapshot:
198
+ diagnostics = list(context.diagnostics)
199
+
200
+ if options.system_prompt is None:
201
+ discovered_body = _discover_prompt_file(
202
+ cwd=cwd,
203
+ home_directory=home_directory,
204
+ filename="SYSTEM.md",
205
+ diagnostics=diagnostics,
206
+ )
207
+ body = (
208
+ _DEFAULT_SYSTEM_PROMPT
209
+ if discovered_body is None
210
+ else discovered_body.content
211
+ )
212
+ else:
213
+ body = options.system_prompt
214
+
215
+ if options.append_system_prompt is None:
216
+ discovered_append = _discover_prompt_file(
217
+ cwd=cwd,
218
+ home_directory=home_directory,
219
+ filename="APPEND_SYSTEM.md",
220
+ diagnostics=diagnostics,
221
+ )
222
+ append = None if discovered_append is None else discovered_append.content
223
+ else:
224
+ append = options.append_system_prompt
225
+
226
+ footer = _footer(prompt_date=prompt_date, cwd=cwd)
227
+ footer_bytes = len(footer.encode("utf-8"))
228
+ if footer_bytes > _PROMPT_BYTE_LIMIT:
229
+ raise MillforgeBasePromptBudgetError(
230
+ "millforge-base prompt footer exceeds 65536 UTF-8 bytes"
231
+ )
232
+
233
+ body_budget = _PROMPT_BYTE_LIMIT - footer_bytes
234
+ bounded_body = _truncate_to_utf8_bytes(body, body_budget)
235
+ diagnostics_tuple = tuple(diagnostics)
236
+ if bounded_body != body:
237
+ return _snapshot(
238
+ prompt=bounded_body + footer,
239
+ prompt_date=prompt_date,
240
+ truncated=True,
241
+ diagnostics=diagnostics_tuple,
242
+ )
243
+
244
+ prompt = bounded_body
245
+ if append:
246
+ if len((prompt + _APPEND_PREFIX + footer).encode("utf-8")) > _PROMPT_BYTE_LIMIT:
247
+ return _snapshot(
248
+ prompt=prompt + footer,
249
+ prompt_date=prompt_date,
250
+ truncated=True,
251
+ diagnostics=diagnostics_tuple,
252
+ )
253
+ append_budget = _PROMPT_BYTE_LIMIT - len(
254
+ (prompt + _APPEND_PREFIX + footer).encode("utf-8")
255
+ )
256
+ bounded_append = _truncate_to_utf8_bytes(append, append_budget)
257
+ prompt += _APPEND_PREFIX + bounded_append
258
+ if bounded_append != append:
259
+ return _snapshot(
260
+ prompt=prompt + footer,
261
+ prompt_date=prompt_date,
262
+ truncated=True,
263
+ diagnostics=diagnostics_tuple,
264
+ )
265
+
266
+ if not context.files:
267
+ return _snapshot(
268
+ prompt=prompt + footer,
269
+ prompt_date=prompt_date,
270
+ truncated=False,
271
+ diagnostics=diagnostics_tuple,
272
+ )
273
+
274
+ if (
275
+ len((prompt + _CONTEXT_PREFIX + _CONTEXT_SUFFIX + footer).encode("utf-8"))
276
+ > _PROMPT_BYTE_LIMIT
277
+ ):
278
+ return _snapshot(
279
+ prompt=prompt + footer,
280
+ prompt_date=prompt_date,
281
+ truncated=True,
282
+ diagnostics=diagnostics_tuple,
283
+ )
284
+
285
+ prompt += _CONTEXT_PREFIX
286
+ for context_file in context.files:
287
+ prefix = _record_prefix(context_file.path)
288
+ suffix = "\n</project_instructions>\n\n"
289
+ if (
290
+ len((prompt + prefix + suffix + _CONTEXT_SUFFIX + footer).encode("utf-8"))
291
+ > _PROMPT_BYTE_LIMIT
292
+ ):
293
+ return _snapshot(
294
+ prompt=prompt + _CONTEXT_SUFFIX + footer,
295
+ prompt_date=prompt_date,
296
+ truncated=True,
297
+ diagnostics=diagnostics_tuple,
298
+ )
299
+ content_budget = _PROMPT_BYTE_LIMIT - len(
300
+ (prompt + prefix + suffix + _CONTEXT_SUFFIX + footer).encode("utf-8")
301
+ )
302
+ bounded_content = _truncate_to_utf8_bytes(context_file.content, content_budget)
303
+ prompt += prefix + bounded_content + suffix
304
+ if bounded_content != context_file.content:
305
+ return _snapshot(
306
+ prompt=prompt + _CONTEXT_SUFFIX + footer,
307
+ prompt_date=prompt_date,
308
+ truncated=True,
309
+ diagnostics=diagnostics_tuple,
310
+ )
311
+
312
+ return _snapshot(
313
+ prompt=prompt + _CONTEXT_SUFFIX + footer,
314
+ prompt_date=prompt_date,
315
+ truncated=False,
316
+ diagnostics=diagnostics_tuple,
317
+ )