agents-md-compiler 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.
- agents_md_compiler/__init__.py +127 -0
- agents_md_compiler/__main__.py +6 -0
- agents_md_compiler/_version.py +28 -0
- agents_md_compiler/atomic.py +135 -0
- agents_md_compiler/cli.py +1220 -0
- agents_md_compiler/codex.py +505 -0
- agents_md_compiler/errors.py +626 -0
- agents_md_compiler/hashing.py +45 -0
- agents_md_compiler/installation.py +942 -0
- agents_md_compiler/lockfile.py +428 -0
- agents_md_compiler/locking.py +148 -0
- agents_md_compiler/manifest.py +379 -0
- agents_md_compiler/models.py +529 -0
- agents_md_compiler/paths.py +158 -0
- agents_md_compiler/py.typed +0 -0
- agents_md_compiler/receipts.py +825 -0
- agents_md_compiler/rendering.py +276 -0
- agents_md_compiler/schemas/lock-v1.schema.json +77 -0
- agents_md_compiler/schemas/manifest-v1.schema.json +66 -0
- agents_md_compiler/schemas/receipt-v1.schema.json +382 -0
- agents_md_compiler/sources.py +249 -0
- agents_md_compiler/state.py +319 -0
- agents_md_compiler-0.1.0.dist-info/METADATA +312 -0
- agents_md_compiler-0.1.0.dist-info/RECORD +27 -0
- agents_md_compiler-0.1.0.dist-info/WHEEL +4 -0
- agents_md_compiler-0.1.0.dist-info/entry_points.txt +3 -0
- agents_md_compiler-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Deterministic compiler for a single global ``AGENTS.md`` policy bundle.
|
|
2
|
+
|
|
3
|
+
The public surface is intentionally small. Pure computation (manifest parsing,
|
|
4
|
+
source validation, locking, rendering, state comparison) lives here; every
|
|
5
|
+
filesystem mutation lives in :mod:`agents_md_compiler.installation` so a library
|
|
6
|
+
consumer cannot confuse rendering with installing.
|
|
7
|
+
|
|
8
|
+
Typical read-only use::
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from agents_md_compiler import compile_bundle, load_manifest
|
|
12
|
+
|
|
13
|
+
manifest = load_manifest(Path("global-agents.toml").absolute())
|
|
14
|
+
compiled = compile_bundle(manifest)
|
|
15
|
+
rendered_bytes = compiled.rendered.data
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from agents_md_compiler._version import distribution_version
|
|
19
|
+
from agents_md_compiler.codex import (
|
|
20
|
+
CodexCapability,
|
|
21
|
+
RuntimeVerification,
|
|
22
|
+
detect_capability,
|
|
23
|
+
verify_rendered_visibility,
|
|
24
|
+
)
|
|
25
|
+
from agents_md_compiler.errors import (
|
|
26
|
+
CodexVerificationError,
|
|
27
|
+
CompilerError,
|
|
28
|
+
ConcurrentChangeError,
|
|
29
|
+
LockError,
|
|
30
|
+
LockMissingError,
|
|
31
|
+
LockStaleError,
|
|
32
|
+
ManifestError,
|
|
33
|
+
MutationError,
|
|
34
|
+
OutputExistsError,
|
|
35
|
+
ReceiptError,
|
|
36
|
+
RenderError,
|
|
37
|
+
ShadowedError,
|
|
38
|
+
SourceError,
|
|
39
|
+
TargetError,
|
|
40
|
+
UnmanagedTargetError,
|
|
41
|
+
UsageError,
|
|
42
|
+
)
|
|
43
|
+
from agents_md_compiler.lockfile import build_lock, load_lock, serialize_lock
|
|
44
|
+
from agents_md_compiler.manifest import load_manifest
|
|
45
|
+
from agents_md_compiler.models import (
|
|
46
|
+
LOCK_FORMAT_VERSION,
|
|
47
|
+
MANIFEST_SCHEMA_VERSION,
|
|
48
|
+
RECEIPT_SCHEMA_VERSION,
|
|
49
|
+
RENDER_FORMAT_VERSION,
|
|
50
|
+
BundleLimits,
|
|
51
|
+
BundleLock,
|
|
52
|
+
BundleManifest,
|
|
53
|
+
BundleState,
|
|
54
|
+
BundleStatus,
|
|
55
|
+
CompiledBundle,
|
|
56
|
+
LockedModule,
|
|
57
|
+
ModuleSpec,
|
|
58
|
+
OverrideInspection,
|
|
59
|
+
RenderedBundle,
|
|
60
|
+
SourceSnapshot,
|
|
61
|
+
TargetInspection,
|
|
62
|
+
TargetKind,
|
|
63
|
+
)
|
|
64
|
+
from agents_md_compiler.rendering import render_bundle, validate_rendered
|
|
65
|
+
from agents_md_compiler.sources import read_source, read_sources
|
|
66
|
+
from agents_md_compiler.state import (
|
|
67
|
+
compile_bundle,
|
|
68
|
+
evaluate,
|
|
69
|
+
inspect_override,
|
|
70
|
+
inspect_target,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"LOCK_FORMAT_VERSION",
|
|
75
|
+
"MANIFEST_SCHEMA_VERSION",
|
|
76
|
+
"RECEIPT_SCHEMA_VERSION",
|
|
77
|
+
"RENDER_FORMAT_VERSION",
|
|
78
|
+
"BundleLimits",
|
|
79
|
+
"BundleLock",
|
|
80
|
+
"BundleManifest",
|
|
81
|
+
"BundleState",
|
|
82
|
+
"BundleStatus",
|
|
83
|
+
"CodexCapability",
|
|
84
|
+
"CodexVerificationError",
|
|
85
|
+
"CompiledBundle",
|
|
86
|
+
"CompilerError",
|
|
87
|
+
"ConcurrentChangeError",
|
|
88
|
+
"LockError",
|
|
89
|
+
"LockMissingError",
|
|
90
|
+
"LockStaleError",
|
|
91
|
+
"LockedModule",
|
|
92
|
+
"ManifestError",
|
|
93
|
+
"ModuleSpec",
|
|
94
|
+
"MutationError",
|
|
95
|
+
"OutputExistsError",
|
|
96
|
+
"OverrideInspection",
|
|
97
|
+
"ReceiptError",
|
|
98
|
+
"RenderError",
|
|
99
|
+
"RenderedBundle",
|
|
100
|
+
"RuntimeVerification",
|
|
101
|
+
"ShadowedError",
|
|
102
|
+
"SourceError",
|
|
103
|
+
"SourceSnapshot",
|
|
104
|
+
"TargetError",
|
|
105
|
+
"TargetInspection",
|
|
106
|
+
"TargetKind",
|
|
107
|
+
"UnmanagedTargetError",
|
|
108
|
+
"UsageError",
|
|
109
|
+
"__version__",
|
|
110
|
+
"build_lock",
|
|
111
|
+
"compile_bundle",
|
|
112
|
+
"detect_capability",
|
|
113
|
+
"distribution_version",
|
|
114
|
+
"evaluate",
|
|
115
|
+
"inspect_override",
|
|
116
|
+
"inspect_target",
|
|
117
|
+
"load_lock",
|
|
118
|
+
"load_manifest",
|
|
119
|
+
"read_source",
|
|
120
|
+
"read_sources",
|
|
121
|
+
"render_bundle",
|
|
122
|
+
"serialize_lock",
|
|
123
|
+
"validate_rendered",
|
|
124
|
+
"verify_rendered_visibility",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
__version__ = distribution_version()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Installed distribution version discovery.
|
|
2
|
+
|
|
3
|
+
The compiler reports its own version from installed package metadata rather than
|
|
4
|
+
from a hard-coded string, so a wheel and its metadata can never disagree. The
|
|
5
|
+
version deliberately never enters rendered output: a tool upgrade must not change
|
|
6
|
+
policy bytes when the format, manifest, lock, and modules are identical.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
DISTRIBUTION_NAME = "agents-md-compiler"
|
|
12
|
+
"""Distribution name used for metadata lookup."""
|
|
13
|
+
|
|
14
|
+
UNKNOWN_VERSION = "0.0.0+unknown"
|
|
15
|
+
"""Reported when the package is not installed, as in a bare source checkout."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def distribution_version() -> str:
|
|
19
|
+
"""Report the installed distribution version.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The version from installed package metadata, or ``0.0.0+unknown`` when no
|
|
23
|
+
installed distribution provides it.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
return version(DISTRIBUTION_NAME)
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
return UNKNOWN_VERSION
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Atomic same-filesystem file replacement.
|
|
2
|
+
|
|
3
|
+
An interrupted write must leave either the complete old bytes or the complete new
|
|
4
|
+
bytes, never a partial file. The sequence is: create a temporary file in the
|
|
5
|
+
target's own directory, write it, flush it, ``fsync`` it, apply the intended mode,
|
|
6
|
+
atomically replace the target, then ``fsync`` the containing directory where the
|
|
7
|
+
platform supports it.
|
|
8
|
+
|
|
9
|
+
The temporary file must share the target's directory because ``os.replace`` is
|
|
10
|
+
atomic only within one filesystem. A temporary directory elsewhere would silently
|
|
11
|
+
degrade to a copy.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import tempfile
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from agents_md_compiler.errors import MutationError, MutationProblem
|
|
19
|
+
|
|
20
|
+
TEMP_PREFIX = ".agents-md-compiler-"
|
|
21
|
+
"""Prefix for the same-directory temporary file, so a leftover is identifiable."""
|
|
22
|
+
|
|
23
|
+
TEMP_SUFFIX = ".tmp"
|
|
24
|
+
"""Suffix for the same-directory temporary file."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def sync_directory(directory: Path) -> bool:
|
|
28
|
+
"""Flush a directory entry to stable storage where supported.
|
|
29
|
+
|
|
30
|
+
Windows cannot open a directory as a file descriptor, so this is a documented
|
|
31
|
+
best effort rather than a guarantee. The replacement itself is still atomic;
|
|
32
|
+
only the durability of the rename across a crash is platform-dependent.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
directory: Directory whose entries were changed.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
``True`` when the directory was synced, ``False`` when the platform does not
|
|
39
|
+
support it.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
descriptor = os.open(directory, os.O_RDONLY)
|
|
43
|
+
except OSError:
|
|
44
|
+
return False
|
|
45
|
+
try:
|
|
46
|
+
os.fsync(descriptor)
|
|
47
|
+
except OSError:
|
|
48
|
+
return False
|
|
49
|
+
finally:
|
|
50
|
+
os.close(descriptor)
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def atomic_write(target: Path, data: bytes, *, mode: int) -> bool:
|
|
55
|
+
"""Replace a file's contents atomically.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
target: Destination path. Its parent directory must already exist.
|
|
59
|
+
data: Exact bytes to write.
|
|
60
|
+
mode: Permission bits to apply to the new file.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
``True`` when the containing directory was also synced.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
MutationError: Any step failed. The temporary file is removed first, so a
|
|
67
|
+
failure never leaves a partial file behind and never damages the target.
|
|
68
|
+
"""
|
|
69
|
+
directory = target.parent
|
|
70
|
+
try:
|
|
71
|
+
descriptor, temp_name = tempfile.mkstemp(
|
|
72
|
+
dir=directory, prefix=TEMP_PREFIX, suffix=TEMP_SUFFIX
|
|
73
|
+
)
|
|
74
|
+
except OSError as error:
|
|
75
|
+
raise MutationError(
|
|
76
|
+
MutationProblem.WRITE_FAILED,
|
|
77
|
+
path=target,
|
|
78
|
+
detail=error.strerror or type(error).__name__,
|
|
79
|
+
) from error
|
|
80
|
+
temporary = Path(temp_name)
|
|
81
|
+
try:
|
|
82
|
+
try:
|
|
83
|
+
with os.fdopen(descriptor, "wb") as stream:
|
|
84
|
+
stream.write(data)
|
|
85
|
+
stream.flush()
|
|
86
|
+
os.fsync(stream.fileno())
|
|
87
|
+
except OSError as error:
|
|
88
|
+
raise MutationError(
|
|
89
|
+
MutationProblem.SYNC_FAILED,
|
|
90
|
+
path=target,
|
|
91
|
+
detail=error.strerror or type(error).__name__,
|
|
92
|
+
) from error
|
|
93
|
+
try:
|
|
94
|
+
temporary.chmod(mode)
|
|
95
|
+
except OSError as error:
|
|
96
|
+
raise MutationError(
|
|
97
|
+
MutationProblem.PERMISSION_FAILED,
|
|
98
|
+
path=target,
|
|
99
|
+
detail=error.strerror or type(error).__name__,
|
|
100
|
+
) from error
|
|
101
|
+
try:
|
|
102
|
+
temporary.replace(target)
|
|
103
|
+
except OSError as error:
|
|
104
|
+
raise MutationError(
|
|
105
|
+
MutationProblem.REPLACE_FAILED,
|
|
106
|
+
path=target,
|
|
107
|
+
detail=error.strerror or type(error).__name__,
|
|
108
|
+
) from error
|
|
109
|
+
except MutationError:
|
|
110
|
+
temporary.unlink(missing_ok=True)
|
|
111
|
+
raise
|
|
112
|
+
return sync_directory(directory)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def create_state_directory(directory: Path, *, mode: int) -> None:
|
|
116
|
+
"""Create a state directory tree with owner-only access.
|
|
117
|
+
|
|
118
|
+
An existing directory's mode is left alone, so a more restrictive operator
|
|
119
|
+
choice is never broadened.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
directory: Directory to create, including parents.
|
|
123
|
+
mode: Mode applied to directories this call creates.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
MutationError: The directory could not be created.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
directory.mkdir(parents=True, exist_ok=True, mode=mode)
|
|
130
|
+
except OSError as error:
|
|
131
|
+
raise MutationError(
|
|
132
|
+
MutationProblem.STATE_ROOT_FAILED,
|
|
133
|
+
path=directory,
|
|
134
|
+
detail=error.strerror or type(error).__name__,
|
|
135
|
+
) from error
|