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,402 @@
1
+ """Composition boundary for the unrestricted Millforge base preset."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ import hashlib
7
+ import platform
8
+ import re
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Literal
12
+
13
+ from pydantic import (
14
+ BaseModel,
15
+ ConfigDict,
16
+ Field,
17
+ StrictBool,
18
+ StrictInt,
19
+ StrictStr,
20
+ field_validator,
21
+ )
22
+
23
+ from millforge.compiled_plan import CompiledHarnessPlan, CompiledModelProfile
24
+ from millforge.compiler.catalogs import ModelProfileCatalogLookup, ToolCatalogSnapshot
25
+ from millforge.compiler.service import compile_harness_source_in_memory
26
+ from millforge.compiler.source import HarnessSource
27
+ from millforge.contracts import CapabilityEnvelope, CapabilityGrant
28
+ from millforge.model_backend import (
29
+ CapabilitySupport,
30
+ ResolvedModelProfile,
31
+ UnsupportedModelCapabilityError,
32
+ )
33
+ from millforge.protocols import CancellationResolver
34
+ from millforge.tools.execution import CompiledToolBindingExecutor
35
+ from millforge.tools.pi_compat.process import (
36
+ PiCompatShellConfig,
37
+ resolve_pi_compat_shell,
38
+ )
39
+ from millforge.tools.pi_compat_catalog import (
40
+ PI_COMPAT_TOOL_DESCRIPTORS,
41
+ _create_pi_compat_tool_snapshot_for_terminal_results,
42
+ )
43
+ from millforge.tools.pi_compat_runtime import (
44
+ _create_pi_compat_tool_executor_for_terminal_results,
45
+ )
46
+
47
+ from .context import (
48
+ MillforgeBaseContextSnapshot,
49
+ _load_millforge_base_context_resolved,
50
+ )
51
+ from .harness import (
52
+ _CONFIG_ID,
53
+ _HARNESS_ID,
54
+ _STAGE_KIND,
55
+ _TOOL_PACK_ID,
56
+ DEFAULT_BASE_TERMINAL_RESULTS,
57
+ _millforge_base_harness_source_for_terminal_results,
58
+ canonicalize_base_terminal_results,
59
+ )
60
+ from .options import MillforgeBaseOptions
61
+ from .platform import _require_supported_platform
62
+ from .prompt import (
63
+ MillforgeBasePromptSnapshot,
64
+ _build_millforge_base_system_prompt_resolved,
65
+ )
66
+
67
+ __all__ = [
68
+ "MillforgeBaseComponents",
69
+ "MillforgeBaseMetadata",
70
+ "create_millforge_base_components",
71
+ ]
72
+
73
+ _UPSTREAM_PACKAGE = "@earendil-works/pi-coding-agent"
74
+ _UPSTREAM_VERSION = "0.79.6"
75
+ _COMPATIBILITY_CLAIM = (
76
+ "A Python behavioral port of Pi 0.79.6's complete built-in coding tool pack, "
77
+ "adapted to Millforge's compiler and runtime contracts."
78
+ )
79
+ _SECURITY_WARNING = (
80
+ "millforge-base runs with the permissions of the Millforge process. It can read, "
81
+ "write, delete, execute commands, access the network, and access credentials "
82
+ "available to that process. Use only in trusted environments."
83
+ )
84
+ _ENABLED_ALIASES = (
85
+ "read",
86
+ "bash",
87
+ "edit",
88
+ "write",
89
+ "grep",
90
+ "find",
91
+ "ls",
92
+ "submit",
93
+ "block",
94
+ "reject",
95
+ )
96
+ _SHA256_RE = re.compile(r"[0-9a-f]{64}")
97
+
98
+
99
+ class MillforgeBaseMetadata(BaseModel):
100
+ """Sanitized, deterministic metadata for one composed base preset."""
101
+
102
+ model_config = ConfigDict(extra="forbid", frozen=True)
103
+
104
+ schema_version: Literal[1]
105
+ config_id: StrictStr
106
+ harness_id: StrictStr
107
+ tool_pack_id: StrictStr
108
+ upstream_package: StrictStr
109
+ upstream_version: StrictStr
110
+ compatibility_claim: StrictStr
111
+ security_warning: StrictStr
112
+ unrestricted: Literal[True]
113
+ enabled_aliases: tuple[StrictStr, ...]
114
+ model_profile_id: StrictStr
115
+ provider_id: StrictStr
116
+ model_id: StrictStr
117
+ transport_id: StrictStr
118
+ os_name: StrictStr
119
+ shell_name: StrictStr
120
+ cwd_sha256: StrictStr
121
+ descriptor_snapshot_sha256: StrictStr
122
+ compiled_sha256: StrictStr
123
+ effective_prompt_sha256: StrictStr
124
+ context_sha256: StrictStr
125
+ context_file_count: StrictInt = Field(ge=0)
126
+ context_truncated: StrictBool
127
+ prompt_truncated: StrictBool
128
+
129
+ @field_validator(
130
+ "config_id",
131
+ "harness_id",
132
+ "tool_pack_id",
133
+ "upstream_package",
134
+ "upstream_version",
135
+ "compatibility_claim",
136
+ "security_warning",
137
+ "model_profile_id",
138
+ "provider_id",
139
+ "model_id",
140
+ "transport_id",
141
+ "os_name",
142
+ "shell_name",
143
+ )
144
+ @classmethod
145
+ def _strings_are_nonblank(cls, value: str) -> str:
146
+ if not value.strip():
147
+ raise ValueError("metadata strings must be nonblank")
148
+ return value
149
+
150
+ @field_validator("enabled_aliases")
151
+ @classmethod
152
+ def _aliases_are_nonblank(cls, value: tuple[str, ...]) -> tuple[str, ...]:
153
+ if not value or any(not alias.strip() for alias in value):
154
+ raise ValueError("enabled_aliases must contain only nonblank values")
155
+ return value
156
+
157
+ @field_validator(
158
+ "cwd_sha256",
159
+ "descriptor_snapshot_sha256",
160
+ "compiled_sha256",
161
+ "effective_prompt_sha256",
162
+ "context_sha256",
163
+ )
164
+ @classmethod
165
+ def _hashes_are_lowercase_sha256(cls, value: str) -> str:
166
+ if not _SHA256_RE.fullmatch(value):
167
+ raise ValueError("metadata hashes must be lowercase SHA-256 values")
168
+ return value
169
+
170
+
171
+ @dataclass(frozen=True)
172
+ class MillforgeBaseComponents:
173
+ """Fully composed transient components for one base-preset invocation."""
174
+
175
+ legal_terminal_results: tuple[str, ...]
176
+ options: MillforgeBaseOptions
177
+ context: MillforgeBaseContextSnapshot
178
+ prompt: MillforgeBasePromptSnapshot
179
+ harness_source: HarnessSource
180
+ compiled_plan: CompiledHarnessPlan
181
+ model_profile: ResolvedModelProfile
182
+ tool_snapshot: ToolCatalogSnapshot
183
+ tool_executor: CompiledToolBindingExecutor
184
+ capability_envelope: CapabilityEnvelope
185
+ metadata: MillforgeBaseMetadata
186
+
187
+
188
+ @dataclass(frozen=True)
189
+ class _SingleModelProfileCatalog:
190
+ """One admitted logical model profile for a single in-memory compilation."""
191
+
192
+ profile: CompiledModelProfile
193
+ snapshot_id: str
194
+ snapshot_sha256: str
195
+
196
+ def resolve_exact(self, profile_id: str) -> ModelProfileCatalogLookup:
197
+ if profile_id == self.profile.profile_id:
198
+ return ModelProfileCatalogLookup.found(self.profile)
199
+ return ModelProfileCatalogLookup.missing(
200
+ error_code="profile.missing",
201
+ evidence={"profile_id": profile_id},
202
+ )
203
+
204
+
205
+ def _resolved_absolute_path(path: Path, field_name: str) -> Path:
206
+ if not path.is_absolute():
207
+ raise ValueError(f"{field_name} must be absolute")
208
+ return path.resolve()
209
+
210
+
211
+ def _sha256(value: str) -> str:
212
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
213
+
214
+
215
+ def _capability_envelope() -> CapabilityEnvelope:
216
+ required_capabilities = sorted(
217
+ {
218
+ capability_id
219
+ for descriptor in PI_COMPAT_TOOL_DESCRIPTORS
220
+ for capability_id in descriptor.required_capabilities
221
+ }
222
+ )
223
+ return CapabilityEnvelope(
224
+ grants=tuple(
225
+ CapabilityGrant(capability_id=capability_id)
226
+ for capability_id in required_capabilities
227
+ )
228
+ )
229
+
230
+
231
+ def _model_profile_catalog(profile_id: str) -> _SingleModelProfileCatalog:
232
+ snapshot_id = _sha256(f"millforge-base.model-profile:{profile_id}")
233
+ return _SingleModelProfileCatalog(
234
+ profile=CompiledModelProfile(profile_id=profile_id),
235
+ snapshot_id=snapshot_id,
236
+ snapshot_sha256=_sha256(f"millforge-base.model-profile.snapshot:{profile_id}"),
237
+ )
238
+
239
+
240
+ def _metadata(
241
+ *,
242
+ model_profile: ResolvedModelProfile,
243
+ cwd: Path,
244
+ shell_config: PiCompatShellConfig,
245
+ context: MillforgeBaseContextSnapshot,
246
+ prompt: MillforgeBasePromptSnapshot,
247
+ tool_snapshot: ToolCatalogSnapshot,
248
+ compiled_plan: CompiledHarnessPlan,
249
+ ) -> MillforgeBaseMetadata:
250
+ return MillforgeBaseMetadata(
251
+ schema_version=1,
252
+ config_id=_CONFIG_ID,
253
+ harness_id=_HARNESS_ID,
254
+ tool_pack_id=_TOOL_PACK_ID,
255
+ upstream_package=_UPSTREAM_PACKAGE,
256
+ upstream_version=_UPSTREAM_VERSION,
257
+ compatibility_claim=_COMPATIBILITY_CLAIM,
258
+ security_warning=_SECURITY_WARNING,
259
+ unrestricted=True,
260
+ enabled_aliases=_ENABLED_ALIASES,
261
+ model_profile_id=model_profile.profile_id,
262
+ provider_id=model_profile.provider_id,
263
+ model_id=model_profile.model_id,
264
+ transport_id=model_profile.transport_id,
265
+ os_name=platform.system().lower(),
266
+ shell_name=Path(shell_config.executable).name,
267
+ cwd_sha256=_sha256(cwd.as_posix()),
268
+ descriptor_snapshot_sha256=tool_snapshot.snapshot_sha256,
269
+ compiled_sha256=compiled_plan.compiled_sha256,
270
+ effective_prompt_sha256=prompt.effective_prompt_sha256,
271
+ context_sha256=context.context_sha256,
272
+ context_file_count=len(context.files),
273
+ context_truncated=context.truncated,
274
+ prompt_truncated=prompt.truncated,
275
+ )
276
+
277
+
278
+ def create_millforge_base_components(
279
+ *,
280
+ legal_terminal_results: tuple[str, ...] = DEFAULT_BASE_TERMINAL_RESULTS,
281
+ model_profile: ResolvedModelProfile,
282
+ cwd: Path,
283
+ cancellation_resolver: CancellationResolver,
284
+ options: MillforgeBaseOptions | None = None,
285
+ prompt_date: datetime.date | None = None,
286
+ home_directory: Path | None = None,
287
+ ) -> MillforgeBaseComponents:
288
+ """Compose the unrestricted preset without making a provider request."""
289
+
290
+ return _create_millforge_base_components(
291
+ legal_terminal_results=canonicalize_base_terminal_results(
292
+ legal_terminal_results
293
+ ),
294
+ model_profile=model_profile,
295
+ cwd=cwd,
296
+ cancellation_resolver=cancellation_resolver,
297
+ options=options,
298
+ prompt_date=prompt_date,
299
+ home_directory=home_directory,
300
+ )
301
+
302
+
303
+ def _create_millforge_base_components(
304
+ *,
305
+ legal_terminal_results: tuple[str, ...],
306
+ model_profile: ResolvedModelProfile,
307
+ cwd: Path,
308
+ cancellation_resolver: CancellationResolver,
309
+ options: MillforgeBaseOptions | None = None,
310
+ prompt_date: datetime.date | None = None,
311
+ home_directory: Path | None = None,
312
+ ) -> MillforgeBaseComponents:
313
+ """Compose a base preset from already-canonical terminal configuration."""
314
+
315
+ _require_supported_platform()
316
+
317
+ unsupported_model_capabilities = tuple(
318
+ capability
319
+ for capability in ("tool_calls", "system_messages", "tool_result_messages")
320
+ if model_profile.capabilities.state_for(capability)
321
+ is not CapabilitySupport.SUPPORTED
322
+ )
323
+ if unsupported_model_capabilities:
324
+ raise UnsupportedModelCapabilityError(
325
+ "millforge-base requires supported model capabilities: "
326
+ + ", ".join(unsupported_model_capabilities)
327
+ )
328
+
329
+ resolved_cwd = _resolved_absolute_path(cwd, "cwd")
330
+ resolved_home = _resolved_absolute_path(
331
+ Path.home() if home_directory is None else home_directory,
332
+ "home_directory",
333
+ )
334
+ effective_options = options or MillforgeBaseOptions()
335
+ effective_prompt_date = prompt_date or datetime.date.today()
336
+ context = _load_millforge_base_context_resolved(
337
+ cwd=resolved_cwd,
338
+ home_directory=resolved_home,
339
+ enabled=effective_options.load_context_files,
340
+ )
341
+ prompt = _build_millforge_base_system_prompt_resolved(
342
+ options=effective_options,
343
+ context=context,
344
+ cwd=resolved_cwd,
345
+ home_directory=resolved_home,
346
+ prompt_date=effective_prompt_date,
347
+ )
348
+ shell_config = resolve_pi_compat_shell()
349
+ source = _millforge_base_harness_source_for_terminal_results(
350
+ model_profile_id=model_profile.profile_id,
351
+ system_instructions=prompt.system_instructions,
352
+ legal_terminal_results=legal_terminal_results,
353
+ )
354
+ tool_snapshot = _create_pi_compat_tool_snapshot_for_terminal_results(
355
+ legal_terminal_results
356
+ )
357
+ capability_envelope = _capability_envelope()
358
+ compiled_plan = compile_harness_source_in_memory(
359
+ request_id="millforge-base.v1",
360
+ source=source,
361
+ stage_kind_id=_STAGE_KIND,
362
+ legal_terminal_results=legal_terminal_results,
363
+ capability_envelope=capability_envelope,
364
+ tool_catalog=tool_snapshot,
365
+ model_profile_catalog=_model_profile_catalog(model_profile.profile_id),
366
+ )
367
+ if tuple(
368
+ grant.capability_id for grant in capability_envelope.grants
369
+ ) != compiled_plan.required_capabilities or any(
370
+ grant.constraints is not None for grant in capability_envelope.grants
371
+ ):
372
+ raise RuntimeError("millforge-base capability envelope does not match its plan")
373
+
374
+ tool_executor = _create_pi_compat_tool_executor_for_terminal_results(
375
+ compiled_plan,
376
+ cwd=resolved_cwd,
377
+ cancellation_resolver=cancellation_resolver,
378
+ shell_config=shell_config,
379
+ legal_terminal_results=legal_terminal_results,
380
+ )
381
+ metadata = _metadata(
382
+ model_profile=model_profile,
383
+ cwd=resolved_cwd,
384
+ shell_config=shell_config,
385
+ context=context,
386
+ prompt=prompt,
387
+ tool_snapshot=tool_snapshot,
388
+ compiled_plan=compiled_plan,
389
+ )
390
+ return MillforgeBaseComponents(
391
+ legal_terminal_results=legal_terminal_results,
392
+ options=effective_options,
393
+ context=context,
394
+ prompt=prompt,
395
+ harness_source=source,
396
+ compiled_plan=compiled_plan,
397
+ model_profile=model_profile,
398
+ tool_snapshot=tool_snapshot,
399
+ tool_executor=tool_executor,
400
+ capability_envelope=capability_envelope,
401
+ metadata=metadata,
402
+ )
@@ -0,0 +1,285 @@
1
+ """Pi-derived project-context discovery for ``millforge-base``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Literal
11
+
12
+ from pydantic import (
13
+ BaseModel,
14
+ ConfigDict,
15
+ Field,
16
+ StrictBool,
17
+ StrictInt,
18
+ StrictStr,
19
+ field_validator,
20
+ )
21
+
22
+ __all__ = [
23
+ "MillforgeBaseContextFile",
24
+ "MillforgeBaseContextSnapshot",
25
+ "load_millforge_base_context",
26
+ ]
27
+
28
+ _CONTEXT_BYTE_LIMIT = 49_152
29
+ _DIAGNOSTIC_BYTE_LIMIT = 2_048
30
+ _CONTEXT_CANDIDATES = ("AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD")
31
+ _SHA256_RE = re.compile(r"[0-9a-f]{64}")
32
+
33
+
34
+ def _truncate_to_utf8_bytes(value: str, limit: int) -> str:
35
+ """Keep a valid UTF-8 prefix within ``limit`` bytes."""
36
+
37
+ encoded = value.encode("utf-8")
38
+ if len(encoded) <= limit:
39
+ return value
40
+ return encoded[:limit].decode("utf-8", errors="ignore")
41
+
42
+
43
+ def _bounded_diagnostic(value: str) -> str:
44
+ bounded = _truncate_to_utf8_bytes(value, _DIAGNOSTIC_BYTE_LIMIT)
45
+ return bounded or "unreadable file"
46
+
47
+
48
+ def _validate_sha256(value: str, field_name: str) -> str:
49
+ if not _SHA256_RE.fullmatch(value):
50
+ raise ValueError(f"{field_name} must be exactly 64 lowercase hex characters")
51
+ return value
52
+
53
+
54
+ def _validate_diagnostics(value: tuple[str, ...]) -> tuple[str, ...]:
55
+ for diagnostic in value:
56
+ if not diagnostic.strip():
57
+ raise ValueError("diagnostics must be nonblank")
58
+ if len(diagnostic.encode("utf-8")) > _DIAGNOSTIC_BYTE_LIMIT:
59
+ raise ValueError("diagnostics may contain at most 2048 UTF-8 bytes")
60
+ return value
61
+
62
+
63
+ class MillforgeBaseContextFile(BaseModel):
64
+ """One included private project-context record."""
65
+
66
+ model_config = ConfigDict(extra="forbid", frozen=True)
67
+
68
+ scope: Literal["global", "project"]
69
+ path: Path
70
+ content: StrictStr
71
+ raw_sha256: StrictStr
72
+ original_byte_count: StrictInt = Field(ge=0)
73
+ included_byte_count: StrictInt = Field(ge=0)
74
+ truncated: StrictBool
75
+
76
+ @field_validator("raw_sha256")
77
+ @classmethod
78
+ def _raw_sha256_is_valid(cls, value: str) -> str:
79
+ return _validate_sha256(value, "raw_sha256")
80
+
81
+
82
+ class MillforgeBaseContextSnapshot(BaseModel):
83
+ """The bounded, ordered private context available to prompt assembly."""
84
+
85
+ model_config = ConfigDict(extra="forbid", frozen=True)
86
+
87
+ files: tuple[MillforgeBaseContextFile, ...]
88
+ diagnostics: tuple[StrictStr, ...]
89
+ context_sha256: StrictStr
90
+ truncated: StrictBool
91
+
92
+ @field_validator("diagnostics")
93
+ @classmethod
94
+ def _diagnostics_are_valid(cls, value: tuple[str, ...]) -> tuple[str, ...]:
95
+ return _validate_diagnostics(value)
96
+
97
+ @field_validator("context_sha256")
98
+ @classmethod
99
+ def _context_sha256_is_valid(cls, value: str) -> str:
100
+ return _validate_sha256(value, "context_sha256")
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class _DiscoveredContextFile:
105
+ scope: Literal["global", "project"]
106
+ path: Path
107
+ raw: bytes
108
+ content: str
109
+
110
+
111
+ def _resolved_absolute_path(path: Path, field_name: str) -> Path:
112
+ if not path.is_absolute():
113
+ raise ValueError(f"{field_name} must be absolute")
114
+ return path.resolve()
115
+
116
+
117
+ def _context_file_from_directory(
118
+ directory: Path,
119
+ scope: Literal["global", "project"],
120
+ diagnostics: list[str],
121
+ ) -> _DiscoveredContextFile | None:
122
+ attempted_candidates: list[Path] = []
123
+ for filename in _CONTEXT_CANDIDATES:
124
+ candidate = (directory / filename).resolve()
125
+ if not candidate.exists():
126
+ continue
127
+ if any(
128
+ _paths_refer_to_same_file(candidate, attempted)
129
+ for attempted in attempted_candidates
130
+ ):
131
+ continue
132
+ attempted_candidates.append(candidate)
133
+ try:
134
+ raw = candidate.read_bytes()
135
+ except OSError as error:
136
+ diagnostics.append(
137
+ _bounded_diagnostic(f"Could not read {candidate}: {error}")
138
+ )
139
+ continue
140
+ return _DiscoveredContextFile(
141
+ scope=scope,
142
+ path=candidate,
143
+ raw=raw,
144
+ content=raw.decode("utf-8", errors="replace"),
145
+ )
146
+ return None
147
+
148
+
149
+ def _project_directories(cwd: Path) -> tuple[Path, ...]:
150
+ return tuple(reversed(cwd.parents)) + (cwd,)
151
+
152
+
153
+ def _paths_refer_to_same_file(left: Path, right: Path) -> bool:
154
+ """Compare host filesystem identity, falling back across removal races."""
155
+
156
+ if left == right:
157
+ return True
158
+ try:
159
+ return left.samefile(right)
160
+ except OSError:
161
+ return False
162
+
163
+
164
+ def _context_sha256(files: tuple[MillforgeBaseContextFile, ...]) -> str:
165
+ payload = [
166
+ {
167
+ "scope": file.scope,
168
+ "path": file.path.as_posix(),
169
+ "content": file.content,
170
+ "raw_sha256": file.raw_sha256,
171
+ "original_byte_count": file.original_byte_count,
172
+ "included_byte_count": file.included_byte_count,
173
+ "truncated": file.truncated,
174
+ }
175
+ for file in files
176
+ ]
177
+ canonical = json.dumps(
178
+ payload,
179
+ sort_keys=True,
180
+ ensure_ascii=True,
181
+ allow_nan=False,
182
+ separators=(",", ":"),
183
+ ).encode("ascii")
184
+ return hashlib.sha256(canonical).hexdigest()
185
+
186
+
187
+ def _allocate_context(
188
+ discovered_files: tuple[_DiscoveredContextFile, ...],
189
+ ) -> tuple[tuple[MillforgeBaseContextFile, ...], bool]:
190
+ remaining = _CONTEXT_BYTE_LIMIT
191
+ included: list[MillforgeBaseContextFile] = []
192
+
193
+ for index, discovered in enumerate(discovered_files):
194
+ content_bytes = len(discovered.content.encode("utf-8"))
195
+ raw_sha256 = hashlib.sha256(discovered.raw).hexdigest()
196
+ if content_bytes <= remaining:
197
+ included.append(
198
+ MillforgeBaseContextFile(
199
+ scope=discovered.scope,
200
+ path=discovered.path,
201
+ content=discovered.content,
202
+ raw_sha256=raw_sha256,
203
+ original_byte_count=len(discovered.raw),
204
+ included_byte_count=content_bytes,
205
+ truncated=False,
206
+ )
207
+ )
208
+ remaining -= content_bytes
209
+ if remaining == 0 and index < len(discovered_files) - 1:
210
+ return tuple(included), True
211
+ continue
212
+
213
+ content = _truncate_to_utf8_bytes(discovered.content, remaining)
214
+ included.append(
215
+ MillforgeBaseContextFile(
216
+ scope=discovered.scope,
217
+ path=discovered.path,
218
+ content=content,
219
+ raw_sha256=raw_sha256,
220
+ original_byte_count=len(discovered.raw),
221
+ included_byte_count=len(content.encode("utf-8")),
222
+ truncated=True,
223
+ )
224
+ )
225
+ return tuple(included), True
226
+
227
+ return tuple(included), False
228
+
229
+
230
+ def load_millforge_base_context(
231
+ *, cwd: Path, home_directory: Path, enabled: bool
232
+ ) -> MillforgeBaseContextSnapshot:
233
+ """Load Pi-ordered context records under Millforge's finite byte bound."""
234
+
235
+ resolved_cwd = _resolved_absolute_path(cwd, "cwd")
236
+ resolved_home = _resolved_absolute_path(home_directory, "home_directory")
237
+ return _load_millforge_base_context_resolved(
238
+ cwd=resolved_cwd,
239
+ home_directory=resolved_home,
240
+ enabled=enabled,
241
+ )
242
+
243
+
244
+ def _load_millforge_base_context_resolved(
245
+ *, cwd: Path, home_directory: Path, enabled: bool
246
+ ) -> MillforgeBaseContextSnapshot:
247
+ if not enabled:
248
+ empty_files: tuple[MillforgeBaseContextFile, ...] = ()
249
+ return MillforgeBaseContextSnapshot(
250
+ files=empty_files,
251
+ diagnostics=(),
252
+ context_sha256=_context_sha256(empty_files),
253
+ truncated=False,
254
+ )
255
+
256
+ diagnostics: list[str] = []
257
+ discovered: list[_DiscoveredContextFile] = []
258
+ native_global = _context_file_from_directory(
259
+ home_directory / ".millforge", "global", diagnostics
260
+ )
261
+ if native_global is None:
262
+ native_global = _context_file_from_directory(
263
+ home_directory / ".pi" / "agent", "global", diagnostics
264
+ )
265
+ if native_global is not None:
266
+ discovered.append(native_global)
267
+
268
+ seen_paths = [file.path for file in discovered]
269
+ for directory in _project_directories(cwd):
270
+ project_file = _context_file_from_directory(directory, "project", diagnostics)
271
+ if project_file is None or any(
272
+ _paths_refer_to_same_file(project_file.path, seen_path)
273
+ for seen_path in seen_paths
274
+ ):
275
+ continue
276
+ discovered.append(project_file)
277
+ seen_paths.append(project_file.path)
278
+
279
+ files, truncated = _allocate_context(tuple(discovered))
280
+ return MillforgeBaseContextSnapshot(
281
+ files=files,
282
+ diagnostics=tuple(diagnostics),
283
+ context_sha256=_context_sha256(files),
284
+ truncated=truncated,
285
+ )