@ictechgy/context-guard 0.4.4 → 0.4.6

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 (32) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.ko.md +15 -2
  3. package/README.md +15 -4
  4. package/context-guard-kit/README.md +2 -2
  5. package/context-guard-kit/benchmark_runner.py +244 -6
  6. package/context-guard-kit/claude_transcript_cost_audit.py +171 -1
  7. package/context-guard-kit/context_pack.py +82 -35
  8. package/context-guard-kit/cost_guard.py +457 -45
  9. package/docs/benchmark-fixtures/learned-compression-baseline-context-pack.prompt.example.md +19 -0
  10. package/docs/benchmark-fixtures/learned-compression-candidate-digest.prompt.example.md +21 -0
  11. package/docs/benchmark-fixtures/learned-compression.tasks.example.json +5 -1
  12. package/docs/benchmark-fixtures/output-transform-baseline-raw-output.prompt.example.md +20 -0
  13. package/docs/benchmark-fixtures/output-transform-digest-receipt.prompt.example.md +23 -0
  14. package/docs/benchmark-fixtures/output-transform.tasks.example.json +28 -0
  15. package/docs/benchmark-fixtures/output-transform.variants.example.json +10 -0
  16. package/docs/benchmark-fixtures/visual-ocr-cropped-ocr.prompt.example.md +22 -0
  17. package/docs/benchmark-fixtures/visual-ocr-full-visual.prompt.example.md +19 -0
  18. package/docs/benchmark-fixtures/visual-ocr.tasks.example.json +5 -1
  19. package/docs/benchmark-workflow-examples.md +6 -2
  20. package/docs/benchmark-workflows/self-hosted-metrics-ledger.example.jsonl +1 -0
  21. package/docs/experimental-benchmark-fixtures.md +17 -6
  22. package/docs/mac-visibility-feasibility-schema.md +62 -0
  23. package/docs/mac-visibility-feasibility.example.json +130 -0
  24. package/package.json +5 -1
  25. package/packaging/homebrew/context-guard.rb.template +1 -1
  26. package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
  27. package/plugins/context-guard/README.ko.md +1 -1
  28. package/plugins/context-guard/README.md +1 -1
  29. package/plugins/context-guard/bin/context-guard-audit +171 -1
  30. package/plugins/context-guard/bin/context-guard-bench +244 -6
  31. package/plugins/context-guard/bin/context-guard-cost +457 -45
  32. 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 write_private_json_at(dir_fd: int, filename: str, data: dict[str, Any]) -> None:
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("unsafe_artifact_path")
735
- flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
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 = os.open(filename, flags, 0o600, dir_fd=dir_fd)
785
+ fd = -1
786
+ temp_created = False
741
787
  try:
742
- with os.fdopen(fd, "w", encoding="utf-8") as handle:
743
- json.dump(data, handle, ensure_ascii=False, indent=2, sort_keys=True)
744
- handle.write("\n")
745
- except Exception:
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.close(fd)
748
- except OSError:
799
+ os.chmod(filename, mode, dir_fd=dir_fd, follow_symlinks=False)
800
+ except (OSError, TypeError, NotImplementedError):
749
801
  pass
750
- raise
751
- try:
752
- os.chmod(filename, 0o600, dir_fd=dir_fd, follow_symlinks=False)
753
- except (OSError, TypeError, NotImplementedError):
754
- pass
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
- flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
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)