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,155 @@
1
+ """Contained logical path policy for built-in runtime tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import tempfile
8
+ from pathlib import Path, PurePosixPath
9
+
10
+ _WINDOWS_DRIVE_RE = re.compile(r"^[a-zA-Z]:")
11
+ _WINDOWS_DEVICE_PREFIXES = ("\\\\?\\", "\\\\.\\", "//?/", "//./")
12
+ _UNC_PREFIXES = ("\\\\", "//")
13
+ _WINDOWS_RESERVED_DEVICE_NAMES = frozenset(
14
+ {
15
+ "aux",
16
+ "con",
17
+ "conin$",
18
+ "conout$",
19
+ "nul",
20
+ "prn",
21
+ *{f"com{i}" for i in range(1, 10)},
22
+ *{f"lpt{i}" for i in range(1, 10)},
23
+ }
24
+ )
25
+
26
+
27
+ class PathPolicyError(ValueError):
28
+ """Raised when a model-provided logical path is outside policy."""
29
+
30
+
31
+ def validate_logical_path(value: str, *, allow_dot: bool = False) -> PurePosixPath:
32
+ """Validate a model-visible logical path before host resolution."""
33
+ if not isinstance(value, str) or not value.strip():
34
+ raise PathPolicyError("path must be a non-empty logical relative path")
35
+ if "\x00" in value:
36
+ raise PathPolicyError("path contains a NUL byte")
37
+ if "\\" in value:
38
+ raise PathPolicyError("path must use POSIX separators")
39
+ if ":" in value:
40
+ raise PathPolicyError("path must not contain Windows drive or ADS syntax")
41
+ if value.startswith(_WINDOWS_DEVICE_PREFIXES) or value.startswith(_UNC_PREFIXES):
42
+ raise PathPolicyError("path must not use Windows device or UNC syntax")
43
+ if _WINDOWS_DRIVE_RE.match(value):
44
+ raise PathPolicyError("path must not use a Windows drive prefix")
45
+ path = PurePosixPath(value)
46
+ if path.is_absolute() or value.startswith("/"):
47
+ raise PathPolicyError("path must not be absolute")
48
+ if value.startswith("~"):
49
+ raise PathPolicyError("path must not use home-directory syntax")
50
+ parts = path.parts
51
+ if any(part in ("", "..") for part in parts):
52
+ raise PathPolicyError("path must not contain traversal components")
53
+ if not allow_dot and any(part == "." for part in parts):
54
+ raise PathPolicyError("path must not contain current-directory components")
55
+ if parts == (".",) and not allow_dot:
56
+ raise PathPolicyError("path must not be current directory")
57
+ _reject_windows_reserved_device_names(parts)
58
+ return path
59
+
60
+
61
+ def resolve_existing_contained(
62
+ root: Path, logical: str, *, allow_dot: bool = False
63
+ ) -> Path:
64
+ """Resolve an existing logical path under a trusted root."""
65
+ path = validate_logical_path(logical, allow_dot=allow_dot)
66
+ root_resolved = root.resolve(strict=True)
67
+ candidate = (root_resolved / Path(*path.parts)).resolve(strict=True)
68
+ _ensure_contained(root_resolved, candidate)
69
+ return candidate
70
+
71
+
72
+ def resolve_write_contained(root: Path, logical: str) -> Path:
73
+ """Resolve a writable logical file path under a trusted root."""
74
+ path = validate_logical_path(logical)
75
+ root_resolved = root.resolve(strict=True)
76
+ candidate = root_resolved / Path(*path.parts)
77
+ parent = candidate.parent.resolve(strict=True)
78
+ _ensure_contained(root_resolved, parent)
79
+ if candidate.exists() or candidate.is_symlink():
80
+ resolved = candidate.resolve(strict=True)
81
+ _ensure_contained(root_resolved, resolved)
82
+ if resolved.is_dir():
83
+ raise PathPolicyError("path resolves to a directory")
84
+ return resolved
85
+ return parent / candidate.name
86
+
87
+
88
+ def atomic_write_contained(root: Path, logical: str, content: bytes) -> str:
89
+ """Atomically write bytes to a contained logical path and return sha256."""
90
+ target = resolve_write_contained(root, logical)
91
+ digest = canonical_sha256_bytes(content)
92
+ fd: int | None = None
93
+ tmp_path: str | None = None
94
+ try:
95
+ fd, tmp_path = tempfile.mkstemp(
96
+ dir=str(target.parent),
97
+ prefix=f".{target.name}.tmp.",
98
+ )
99
+ with os.fdopen(fd, "wb") as handle:
100
+ fd = None
101
+ handle.write(content)
102
+ handle.flush()
103
+ os.fsync(handle.fileno())
104
+ os.replace(tmp_path, target)
105
+ tmp_path = None
106
+ finally:
107
+ if fd is not None:
108
+ try:
109
+ os.close(fd)
110
+ except OSError:
111
+ pass
112
+ if tmp_path is not None:
113
+ try:
114
+ os.unlink(tmp_path)
115
+ except OSError:
116
+ pass
117
+ return digest
118
+
119
+
120
+ def canonical_sha256_bytes(content: bytes) -> str:
121
+ """Hash raw bytes using the same lowercase SHA-256 format as JSON results."""
122
+ import hashlib
123
+
124
+ return hashlib.sha256(content).hexdigest()
125
+
126
+
127
+ def logical_from_resolved(root: Path, target: Path) -> str:
128
+ """Return a POSIX logical path after containment has already been enforced."""
129
+ root_resolved = root.resolve(strict=True)
130
+ resolved = target.resolve(strict=True)
131
+ _ensure_contained(root_resolved, resolved)
132
+ return resolved.relative_to(root_resolved).as_posix()
133
+
134
+
135
+ def _ensure_contained(root: Path, candidate: Path) -> None:
136
+ try:
137
+ candidate.relative_to(root)
138
+ except ValueError as exc:
139
+ raise PathPolicyError("path resolves outside the trusted root") from exc
140
+
141
+
142
+ def _reject_windows_reserved_device_names(parts: tuple[str, ...]) -> None:
143
+ for part in parts:
144
+ if part in (".", ".."):
145
+ continue
146
+ if _is_windows_reserved_device_name(part):
147
+ raise PathPolicyError("path must not use a reserved Windows device name")
148
+
149
+
150
+ def _is_windows_reserved_device_name(part: str) -> bool:
151
+ trimmed = part.rstrip(" .")
152
+ if not trimmed:
153
+ return False
154
+ base = trimmed.split(".", 1)[0].rstrip(" .").casefold()
155
+ return base in _WINDOWS_RESERVED_DEVICE_NAMES
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ {
2
+ "schema_version": 1,
3
+ "upstream": {
4
+ "repository": "https://github.com/earendil-works/pi",
5
+ "package": "@earendil-works/pi-coding-agent",
6
+ "version": "0.79.6",
7
+ "license": "MIT",
8
+ "copyright": "Copyright (c) 2025 Mario Zechner"
9
+ },
10
+ "source_snapshot": "reference/pi/",
11
+ "source_history": "The approved reference copy is history-free. No upstream commit hash is recorded or inferred.",
12
+ "adaptations": [
13
+ "Python filesystem and process APIs replace Node APIs.",
14
+ "`pathspec` and the exact Section 7 ignore rules replace `fd`/`rg` discovery.",
15
+ "Python regex/glob, binary detection, decoding, traversal, and error rules are the exact Section 7 search adaptation.",
16
+ "supported image reads return the exact successful text-only result in Spec 11 Section 6 instead of multimodal image content; non-image bytes retain Pi's UTF-8 replacement-decoding behavior.",
17
+ "the poll/wait cancellation protocol replaces Pi's abort signal.",
18
+ "Windows uses COMSPEC/cmd.exe while POSIX preserves Pi's Bash-first order.",
19
+ "the inherited environment omits Pi's private bin-directory PATH mutation.",
20
+ "the 11A-owned async timer enforces the caller-supplied strictly positive finite timeout; expiry after spawn performs the specified process-tree cleanup and returns `process_timeout` with `completion_unknown`."
21
+ ],
22
+ "pinned_paths": [
23
+ {"path": "LICENSE", "sha256": "0457f5bcec3b3b211605dfb5d1a49042fd638f3686a410fe099c24a25af13c48", "classification": "ported"},
24
+ {"path": "packages/coding-agent/package.json", "sha256": "76919365cc55f94e79a27583ec2f36c917c0312e529f6b83f71c7817b6774f55", "classification": "excluded"},
25
+ {"path": "packages/coding-agent/src/core/tools/index.ts", "sha256": "afeb406e0f5edde143aaac51b9c928cb497bfb8216c04ac9242bfd0f9241ffe1", "classification": "adapted"},
26
+ {"path": "packages/coding-agent/src/core/tools/read.ts", "sha256": "954419870e12b9e82e45a37bd3f9e6dda5bd80aa44ea56fbcdac0f976676d03e", "classification": "adapted"},
27
+ {"path": "packages/coding-agent/src/core/tools/ls.ts", "sha256": "0cd36018b68f08c98e615c761d6c3af19842537cd500d7be9912f932cd2b1b22", "classification": "ported"},
28
+ {"path": "packages/coding-agent/src/core/tools/grep.ts", "sha256": "9737dcfd27ae56a33fd934c0b336b9f1cb64eaab6b6378a91270dde4b505ba47", "classification": "adapted"},
29
+ {"path": "packages/coding-agent/src/core/tools/find.ts", "sha256": "f6f2d780df007af22d67101dc17ffae7c9d670893be168628eeebb01345b97df", "classification": "adapted"},
30
+ {"path": "packages/coding-agent/src/core/tools/write.ts", "sha256": "7f7f1425e2a7cdffe48b190033189fd03dffc5756708b12775dddb0acfe90c6c", "classification": "ported"},
31
+ {"path": "packages/coding-agent/src/core/tools/edit.ts", "sha256": "373083e70b8f58dc49b213a4046e4095e1acc5fb0bea1925185e09e0d3c00618", "classification": "ported"},
32
+ {"path": "packages/coding-agent/src/core/tools/edit-diff.ts", "sha256": "b76fab8a2868b1e65970286928e996e76918ba8b8dbfdefe9ab0fe34420f9c90", "classification": "ported"},
33
+ {"path": "packages/coding-agent/src/core/tools/bash.ts", "sha256": "9d932837466dee1f8fd84cae3b180363aa523118ff8c84a992eeab18dcd3be29", "classification": "adapted"},
34
+ {"path": "packages/coding-agent/src/core/tools/truncate.ts", "sha256": "104e61ea602c7f7894097b2771dad8c68de75c5a10ea6222e615c4ad144451c7", "classification": "ported"},
35
+ {"path": "packages/coding-agent/src/core/tools/output-accumulator.ts", "sha256": "c601ddd8e10934be6f3db30696eacba7b9544f2dd1648a5ec3c9935f0d4625c3", "classification": "adapted"},
36
+ {"path": "packages/coding-agent/src/core/tools/path-utils.ts", "sha256": "ab6f420d2388a41366113c17a25e356867820b87b02a169d044cfcf83e1997d3", "classification": "ported"},
37
+ {"path": "packages/coding-agent/src/core/tools/file-mutation-queue.ts", "sha256": "33cb06ac9bcdf32c8b84d9d12e33be44c503a7670f668e003a3262cd34294d11", "classification": "adapted"},
38
+ {"path": "packages/coding-agent/src/core/tools/render-utils.ts", "sha256": "38913e15575b7c314687c1a7d53b2763eecb8e7a86b0b58d7b5532c900503cf3", "classification": "excluded"},
39
+ {"path": "packages/coding-agent/src/utils/shell.ts", "sha256": "7a74bb1f3809fa62771827a9c814636b9742760f6deddc517a84ae237dc4fbef", "classification": "adapted"},
40
+ {"path": "packages/coding-agent/src/utils/child-process.ts", "sha256": "cd324f5b9a1abadd85ab5a331750d475c8bd51ec65cae091ad9b416245ec85d4", "classification": "adapted"},
41
+ {"path": "packages/coding-agent/src/utils/mime.ts", "sha256": "b562303ba7bbf27432bd310891fe656a8473995a4a1f8ba2e00a79b56400605c", "classification": "ported"},
42
+ {"path": "packages/coding-agent/src/utils/paths.ts", "sha256": "c433bd9f98744aa9a373282fd50ab3657b6e2a366feecbfb177fe648746d1944", "classification": "ported"},
43
+ {"path": "packages/coding-agent/test/tools.test.ts", "sha256": "0360d773cfef2e08f23490b9c83d4bb5b88ce070bf79c57d227c5fd2fcd121e9", "classification": "test-derived"},
44
+ {"path": "packages/coding-agent/test/path-utils.test.ts", "sha256": "08bb88574596a43e2364ee3b2655e071593822417ef8937693744174e9615f3e", "classification": "test-derived"},
45
+ {"path": "packages/coding-agent/test/bash-close-hang-windows.test.ts", "sha256": "1d2230b25129a365cbae417269b65c1724772104328f584ee521cb5c255b49fb", "classification": "test-derived"},
46
+ {"path": "packages/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts", "sha256": "d8816a5ed1f3033439e7866dc9c7b87b7e7a265caf186bae3817443b597e1d02", "classification": "test-derived"},
47
+ {"path": "packages/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts", "sha256": "bcc7517366d11bf9cc3b8dade9d54bfbe6cd482a23deb9d6d966d1b2e09c6982", "classification": "test-derived"},
48
+ {"path": "packages/coding-agent/test/suite/regressions/5208-late-bash-output.test.ts", "sha256": "9d6fc0d457e6d7470246d821deec9937d428e86fdad6858d05e7d5c956802f8c", "classification": "test-derived"},
49
+ {"path": "packages/coding-agent/test/suite/regressions/5303-bash-output-truncation.test.ts", "sha256": "d690fdad5de2bd82f016892972395d49d3b83570d5223f4f473626d34af10136", "classification": "test-derived"},
50
+ {"path": "packages/coding-agent/src/core/system-prompt.ts", "sha256": "49cd7166a7f1eb8d088b7d8b8f2c38642de827ac2ff7a327ec4b7549b543d8d1", "classification": "adapted"},
51
+ {"path": "packages/coding-agent/src/core/resource-loader.ts", "sha256": "9e339467f3c5997ec363ad96ff18e1850cff71e2ab37dfeca7b4e8782926a2ff", "classification": "adapted"},
52
+ {"path": "packages/coding-agent/test/system-prompt.test.ts", "sha256": "6443dbc77ee1c39ca14c89652079e0907f47a4efae2bf9ce08323442983e2981", "classification": "test-derived"},
53
+ {"path": "packages/coding-agent/test/resource-loader.test.ts", "sha256": "98b343646e389f87743ef548f9c23e329ccf1eb0bb6945a66016884ec515b45e", "classification": "test-derived"}
54
+ ]
55
+ }
@@ -0,0 +1,36 @@
1
+ # Pi Compatibility Update Policy
2
+
3
+ This source-attributed behavioral port may be updated only as an isolated,
4
+ reviewable change against a new approved Pi snapshot.
5
+
6
+ 1. Record the upstream package name, version, license, repository URL, and
7
+ approved snapshot location. Do not invent a commit hash when the snapshot
8
+ has no history.
9
+ 2. Recompute SHA-256 values for every path in PROVENANCE.json before copying
10
+ or translating behavior. Stop the update when a pinned source differs until
11
+ the source version and manifest are explicitly reconciled.
12
+ 3. Review Pi's tool sources, utility sources, and source-derived tests together.
13
+ Preserve model-visible text unless a documented Spec 11 adaptation requires
14
+ a divergence.
15
+ 4. Reassess the complete adaptation list, including Python filesystem/process
16
+ APIs, text-only image results, search implementation, mutation locking, and
17
+ Windows shell behavior. Document any newly required divergence in the
18
+ accepted specification, provenance, tests, and public documentation.
19
+ 5. Copy the upstream MIT notice unmodified, update every path hash and
20
+ classification, and keep the package-data rules in sync.
21
+ 6. Run the source-derived focused tests, the full relevant Millforge tests,
22
+ Ruff, MyPy, dependency checks, and package-content verification before
23
+ accepting the update.
24
+
25
+ No Pi runtime, Node package, downloaded search binary, or unreviewed source
26
+ snapshot is an acceptable substitute for this procedure.
27
+
28
+ ## Current Platform Validation
29
+
30
+ The POSIX shell path, timeout, cancellation, process-group cleanup, output
31
+ ordering, and persistence behavior are covered by the bundled Python 3.12
32
+ tests. The Windows `COMSPEC`/`cmd.exe` and `taskkill /F /T` implementation is
33
+ source-reviewed and has platform-gated contract tests, but native Windows
34
+ process-tree stress behavior was not locally verified for the 0.79.6 port.
35
+ Run those gated tests in a controlled Windows CI environment before claiming
36
+ native Windows process-tree parity.
@@ -0,0 +1,34 @@
1
+ """Source-attributed Pi 0.79.6 coding-tool compatibility operations."""
2
+
3
+ from .contracts import (
4
+ PiCompatErrorKind,
5
+ PiCompatOperationResult,
6
+ PiCompatSideEffectState,
7
+ )
8
+ from .editing import execute_edit
9
+ from .operations import execute_ls, execute_read, execute_write
10
+ from .process import (
11
+ PiCompatCancellation,
12
+ PiCompatShellConfig,
13
+ PiCompatShellResolutionError,
14
+ execute_bash,
15
+ resolve_pi_compat_shell,
16
+ )
17
+ from .search import execute_find, execute_grep
18
+
19
+ __all__ = [
20
+ "PiCompatCancellation",
21
+ "PiCompatErrorKind",
22
+ "PiCompatOperationResult",
23
+ "PiCompatShellConfig",
24
+ "PiCompatShellResolutionError",
25
+ "PiCompatSideEffectState",
26
+ "execute_bash",
27
+ "execute_edit",
28
+ "execute_find",
29
+ "execute_grep",
30
+ "execute_ls",
31
+ "execute_read",
32
+ "execute_write",
33
+ "resolve_pi_compat_shell",
34
+ ]
@@ -0,0 +1,49 @@
1
+ """Shared contracts for the Pi 0.79.6 compatibility operation pack."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from pathlib import Path
8
+
9
+ __all__ = [
10
+ "PiCompatErrorKind",
11
+ "PiCompatOperationResult",
12
+ "PiCompatSideEffectState",
13
+ ]
14
+
15
+
16
+ class PiCompatErrorKind(str, Enum):
17
+ """Closed operation errors translated by the unrestricted tool adapter."""
18
+
19
+ INVALID_ARGUMENTS = "invalid_arguments"
20
+ NOT_FOUND = "not_found"
21
+ PERMISSION_DENIED = "permission_denied"
22
+ CONFLICT = "conflict"
23
+ IO_ERROR = "io_error"
24
+ PROCESS_EXIT_NONZERO = "process_exit_nonzero"
25
+ PROCESS_TIMEOUT = "process_timeout"
26
+ CANCELLED = "cancelled"
27
+ PROCESS_LAUNCH_ERROR = "process_launch_error"
28
+
29
+
30
+ class PiCompatSideEffectState(str, Enum):
31
+ """The operation's explicit side-effect certainty."""
32
+
33
+ NOT_ATTEMPTED = "not_attempted"
34
+ CONFIRMED_ABSENT = "confirmed_absent"
35
+ CONFIRMED_COMPLETE = "confirmed_complete"
36
+ ROLLED_BACK = "rolled_back"
37
+ COMPLETION_UNKNOWN = "completion_unknown"
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class PiCompatOperationResult:
42
+ """The complete model-visible result of one Pi-compatible operation."""
43
+
44
+ model_text: str
45
+ truncated: bool
46
+ error_kind: PiCompatErrorKind | None
47
+ exit_code: int | None
48
+ changed_path: Path | None
49
+ side_effect_state: PiCompatSideEffectState