@ictechgy/context-guard 0.4.5 → 0.4.7
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.
- package/CHANGELOG.md +15 -0
- package/README.ko.md +32 -4
- package/README.md +36 -5
- package/context-guard-kit/README.md +2 -0
- package/context-guard-kit/context_escrow.py +22 -6
- package/context-guard-kit/context_guard_cli.py +1 -0
- package/context-guard-kit/context_pack.py +82 -35
- package/context-guard-kit/cost_guard.py +457 -45
- package/context-guard-kit/experimental_registry.py +2038 -0
- package/docs/benchmark-workflow-examples.md +1 -1
- package/docs/experimental-benchmark-fixtures.md +1 -1
- package/package.json +2 -1
- package/packaging/homebrew/context-guard.rb.template +1 -1
- package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
- package/plugins/context-guard/README.ko.md +1 -1
- package/plugins/context-guard/README.md +19 -1
- package/plugins/context-guard/bin/context-guard +1 -0
- package/plugins/context-guard/bin/context-guard-artifact +22 -6
- package/plugins/context-guard/bin/context-guard-cost +457 -45
- package/plugins/context-guard/bin/context-guard-experiments +2038 -0
- package/plugins/context-guard/bin/context-guard-pack +82 -35
|
@@ -729,29 +729,95 @@ def ensure_private_pack_dir(root: Path) -> tuple[Path | None, int | None, str |
|
|
|
729
729
|
pass
|
|
730
730
|
|
|
731
731
|
|
|
732
|
-
def
|
|
732
|
+
def atomic_write_ops_supported() -> bool:
|
|
733
|
+
return (
|
|
734
|
+
os.open in os.supports_dir_fd
|
|
735
|
+
and os.rename in os.supports_dir_fd
|
|
736
|
+
and os.unlink in os.supports_dir_fd
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def fsync_dir_fd(dir_fd: int) -> None:
|
|
741
|
+
os.fsync(dir_fd)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def validate_existing_output_target_at(dir_fd: int, filename: str, option_name: str) -> None:
|
|
745
|
+
flags = os.O_WRONLY
|
|
746
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
747
|
+
flags |= os.O_NOFOLLOW
|
|
748
|
+
if hasattr(os, "O_CLOEXEC"):
|
|
749
|
+
flags |= os.O_CLOEXEC
|
|
750
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
751
|
+
flags |= os.O_NONBLOCK
|
|
752
|
+
file_fd = -1
|
|
753
|
+
try:
|
|
754
|
+
file_fd = os.open(filename, flags, dir_fd=dir_fd)
|
|
755
|
+
st = os.fstat(file_fd)
|
|
756
|
+
if not stat.S_ISREG(st.st_mode):
|
|
757
|
+
raise PackError(f"invalid {option_name}: unsafe_path")
|
|
758
|
+
except FileNotFoundError:
|
|
759
|
+
return
|
|
760
|
+
except IsADirectoryError as exc:
|
|
761
|
+
raise PackError(f"invalid {option_name}: unsafe_path") from exc
|
|
762
|
+
except OSError as exc:
|
|
763
|
+
raise PackError(f"invalid {option_name}: {exc.strerror or exc.__class__.__name__}") from exc
|
|
764
|
+
finally:
|
|
765
|
+
if file_fd >= 0:
|
|
766
|
+
try:
|
|
767
|
+
os.close(file_fd)
|
|
768
|
+
except OSError:
|
|
769
|
+
pass
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def write_text_atomic_at(dir_fd: int, filename: str, content: str, *, mode: int, option_name: str) -> None:
|
|
733
773
|
if "/" in filename or filename in {"", ".", ".."}:
|
|
734
|
-
raise PackError("
|
|
735
|
-
|
|
774
|
+
raise PackError(f"invalid {option_name}: unsafe_path")
|
|
775
|
+
if not atomic_write_ops_supported():
|
|
776
|
+
raise PackError(f"invalid {option_name}: atomic_write_unsupported")
|
|
777
|
+
validate_existing_output_target_at(dir_fd, filename, option_name)
|
|
778
|
+
digest = hashlib.sha256(f"{filename}:{os.getpid()}:{time.time_ns()}".encode("utf-8", "replace")).hexdigest()[:16]
|
|
779
|
+
temp_name = f".context-guard-pack-{digest}.tmp"
|
|
780
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
736
781
|
if hasattr(os, "O_NOFOLLOW"):
|
|
737
782
|
flags |= os.O_NOFOLLOW
|
|
738
783
|
if hasattr(os, "O_CLOEXEC"):
|
|
739
784
|
flags |= os.O_CLOEXEC
|
|
740
|
-
fd =
|
|
785
|
+
fd = -1
|
|
786
|
+
temp_created = False
|
|
741
787
|
try:
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
788
|
+
fd = os.open(temp_name, flags, mode, dir_fd=dir_fd)
|
|
789
|
+
temp_created = True
|
|
790
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="") as handle:
|
|
791
|
+
fd = -1
|
|
792
|
+
handle.write(content)
|
|
793
|
+
handle.flush()
|
|
794
|
+
os.fsync(handle.fileno())
|
|
795
|
+
fsync_dir_fd(dir_fd)
|
|
796
|
+
os.rename(temp_name, filename, src_dir_fd=dir_fd, dst_dir_fd=dir_fd)
|
|
797
|
+
temp_created = False
|
|
746
798
|
try:
|
|
747
|
-
os.
|
|
748
|
-
except OSError:
|
|
799
|
+
os.chmod(filename, mode, dir_fd=dir_fd, follow_symlinks=False)
|
|
800
|
+
except (OSError, TypeError, NotImplementedError):
|
|
749
801
|
pass
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
802
|
+
fsync_dir_fd(dir_fd)
|
|
803
|
+
finally:
|
|
804
|
+
if fd >= 0:
|
|
805
|
+
try:
|
|
806
|
+
os.close(fd)
|
|
807
|
+
except OSError:
|
|
808
|
+
pass
|
|
809
|
+
if temp_created:
|
|
810
|
+
try:
|
|
811
|
+
os.unlink(temp_name, dir_fd=dir_fd)
|
|
812
|
+
except OSError:
|
|
813
|
+
pass
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def write_private_json_at(dir_fd: int, filename: str, data: dict[str, Any]) -> None:
|
|
817
|
+
if "/" in filename or filename in {"", ".", ".."}:
|
|
818
|
+
raise PackError("unsafe_artifact_path")
|
|
819
|
+
content = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
|
|
820
|
+
write_text_atomic_at(dir_fd, filename, content, mode=0o600, option_name="artifact receipt")
|
|
755
821
|
|
|
756
822
|
|
|
757
823
|
def finalize_receipt_size(receipt: dict[str, Any]) -> int:
|
|
@@ -1453,27 +1519,13 @@ def write_text_under_root(root: Path, raw_path: str, content: str, option_name:
|
|
|
1453
1519
|
parent_parts = rel.parts[:-1]
|
|
1454
1520
|
filename = rel.parts[-1]
|
|
1455
1521
|
current_fd: int | None = None
|
|
1456
|
-
file_fd = -1
|
|
1457
1522
|
try:
|
|
1458
1523
|
current_fd = open_dir_no_follow(root)
|
|
1459
1524
|
for part in parent_parts:
|
|
1460
1525
|
next_fd = open_dir_no_follow(part, dir_fd=current_fd)
|
|
1461
1526
|
os.close(current_fd)
|
|
1462
1527
|
current_fd = next_fd
|
|
1463
|
-
|
|
1464
|
-
if hasattr(os, "O_NOFOLLOW"):
|
|
1465
|
-
flags |= os.O_NOFOLLOW
|
|
1466
|
-
if hasattr(os, "O_CLOEXEC"):
|
|
1467
|
-
flags |= os.O_CLOEXEC
|
|
1468
|
-
if hasattr(os, "O_NONBLOCK"):
|
|
1469
|
-
flags |= os.O_NONBLOCK
|
|
1470
|
-
file_fd = os.open(filename, flags, 0o600, dir_fd=current_fd)
|
|
1471
|
-
st = os.fstat(file_fd)
|
|
1472
|
-
if not stat.S_ISREG(st.st_mode):
|
|
1473
|
-
raise PackError(f"invalid {option_name}: unsafe_path")
|
|
1474
|
-
with os.fdopen(file_fd, "w", encoding="utf-8") as handle:
|
|
1475
|
-
file_fd = -1
|
|
1476
|
-
handle.write(content)
|
|
1528
|
+
write_text_atomic_at(current_fd, filename, content, mode=0o600, option_name=option_name)
|
|
1477
1529
|
except PackError:
|
|
1478
1530
|
raise
|
|
1479
1531
|
except FileNotFoundError as exc:
|
|
@@ -1481,11 +1533,6 @@ def write_text_under_root(root: Path, raw_path: str, content: str, option_name:
|
|
|
1481
1533
|
except OSError as exc:
|
|
1482
1534
|
raise PackError(f"invalid {option_name}: {exc.strerror or exc.__class__.__name__}") from exc
|
|
1483
1535
|
finally:
|
|
1484
|
-
if file_fd >= 0:
|
|
1485
|
-
try:
|
|
1486
|
-
os.close(file_fd)
|
|
1487
|
-
except OSError:
|
|
1488
|
-
pass
|
|
1489
1536
|
if current_fd is not None:
|
|
1490
1537
|
try:
|
|
1491
1538
|
os.close(current_fd)
|