@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.
- package/CHANGELOG.md +15 -0
- package/README.ko.md +15 -2
- package/README.md +15 -4
- package/context-guard-kit/README.md +2 -2
- package/context-guard-kit/benchmark_runner.py +244 -6
- package/context-guard-kit/claude_transcript_cost_audit.py +171 -1
- package/context-guard-kit/context_pack.py +82 -35
- package/context-guard-kit/cost_guard.py +457 -45
- package/docs/benchmark-fixtures/learned-compression-baseline-context-pack.prompt.example.md +19 -0
- package/docs/benchmark-fixtures/learned-compression-candidate-digest.prompt.example.md +21 -0
- package/docs/benchmark-fixtures/learned-compression.tasks.example.json +5 -1
- package/docs/benchmark-fixtures/output-transform-baseline-raw-output.prompt.example.md +20 -0
- package/docs/benchmark-fixtures/output-transform-digest-receipt.prompt.example.md +23 -0
- package/docs/benchmark-fixtures/output-transform.tasks.example.json +28 -0
- package/docs/benchmark-fixtures/output-transform.variants.example.json +10 -0
- package/docs/benchmark-fixtures/visual-ocr-cropped-ocr.prompt.example.md +22 -0
- package/docs/benchmark-fixtures/visual-ocr-full-visual.prompt.example.md +19 -0
- package/docs/benchmark-fixtures/visual-ocr.tasks.example.json +5 -1
- package/docs/benchmark-workflow-examples.md +6 -2
- package/docs/benchmark-workflows/self-hosted-metrics-ledger.example.jsonl +1 -0
- package/docs/experimental-benchmark-fixtures.md +17 -6
- package/docs/mac-visibility-feasibility-schema.md +62 -0
- package/docs/mac-visibility-feasibility.example.json +130 -0
- package/package.json +5 -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 +1 -1
- package/plugins/context-guard/bin/context-guard-audit +171 -1
- package/plugins/context-guard/bin/context-guard-bench +244 -6
- package/plugins/context-guard/bin/context-guard-cost +457 -45
- package/plugins/context-guard/bin/context-guard-pack +82 -35
|
@@ -46,7 +46,8 @@ COST_KEYS = ("total_cost_usd", "cost_usd", "costUSD")
|
|
|
46
46
|
MODEL_KEYS = ("model", "model_id", "modelId")
|
|
47
47
|
QUERY_SOURCE_KEYS = ("query_source", "querySource")
|
|
48
48
|
TIMESTAMP_KEYS = ("timestamp", "created_at", "createdAt", "time", "ts")
|
|
49
|
-
FEASIBILITY_SCHEMA_VERSION = "contextguard.metric-feasibility.v1.
|
|
49
|
+
FEASIBILITY_SCHEMA_VERSION = "contextguard.metric-feasibility.v1.3"
|
|
50
|
+
MAC_VISIBILITY_SCHEMA_VERSION = "contextguard.mac-visibility.v1"
|
|
50
51
|
FEASIBILITY_PRODUCER = "context-guard-audit"
|
|
51
52
|
CACHE_DIAGNOSTICS_SCHEMA_VERSION = "contextguard.cache-diagnostics.v1"
|
|
52
53
|
CACHE_LAYOUT_ADVICE_SCHEMA_VERSION = "contextguard.cache-layout-advice.v1"
|
|
@@ -1635,6 +1636,168 @@ def build_metric_caveats(summary: UsageSummary) -> list[str]:
|
|
|
1635
1636
|
return caveats
|
|
1636
1637
|
|
|
1637
1638
|
|
|
1639
|
+
def _mac_card(
|
|
1640
|
+
card_id: str,
|
|
1641
|
+
title: str,
|
|
1642
|
+
status: str,
|
|
1643
|
+
binding_paths: list[str],
|
|
1644
|
+
*,
|
|
1645
|
+
required_observation: str | None = None,
|
|
1646
|
+
) -> dict[str, Any]:
|
|
1647
|
+
card: dict[str, Any] = {
|
|
1648
|
+
"id": card_id,
|
|
1649
|
+
"title": title,
|
|
1650
|
+
"status": status,
|
|
1651
|
+
"binding_paths": binding_paths,
|
|
1652
|
+
}
|
|
1653
|
+
if required_observation:
|
|
1654
|
+
card["required_observation"] = required_observation
|
|
1655
|
+
return card
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
def build_mac_visibility_contract(
|
|
1659
|
+
*,
|
|
1660
|
+
availability: dict[str, Any],
|
|
1661
|
+
integrity: dict[str, Any],
|
|
1662
|
+
cache_layout_advice: dict[str, Any],
|
|
1663
|
+
) -> dict[str, Any]:
|
|
1664
|
+
"""Build the pre-GUI macOS visibility binding contract.
|
|
1665
|
+
|
|
1666
|
+
This is intentionally a thin index over already-emitted stable feasibility
|
|
1667
|
+
fields. It does not recompute metrics, read diagnostic summary data, or infer
|
|
1668
|
+
live context/headroom from historical transcript totals.
|
|
1669
|
+
"""
|
|
1670
|
+
token_status = str((availability.get("tokens") or {}).get("status", "missing"))
|
|
1671
|
+
scan_status = str(integrity.get("status", "partial"))
|
|
1672
|
+
if token_status == "available" and scan_status == "complete":
|
|
1673
|
+
readiness_status = "ready"
|
|
1674
|
+
readiness_reason = "Transcript token totals are available and the scan completed within configured limits."
|
|
1675
|
+
elif token_status in {"available", "partial"}:
|
|
1676
|
+
readiness_status = "partial"
|
|
1677
|
+
readiness_reason = "Some stable fields can be shown, but scan integrity or metric availability is partial."
|
|
1678
|
+
else:
|
|
1679
|
+
readiness_status = "missing"
|
|
1680
|
+
readiness_reason = "Token totals are missing from the transcript scan; show setup or unavailable state."
|
|
1681
|
+
|
|
1682
|
+
context_status = str((availability.get("context") or {}).get("status", "missing"))
|
|
1683
|
+
headroom_status = str((availability.get("headroom") or {}).get("status", "missing"))
|
|
1684
|
+
cache_status = str((availability.get("cache") or {}).get("status", "missing"))
|
|
1685
|
+
cost_status = str((availability.get("cost") or {}).get("status", "missing"))
|
|
1686
|
+
advice_status = str(cache_layout_advice.get("status", "missing"))
|
|
1687
|
+
|
|
1688
|
+
missing_live_observations: list[dict[str, Any]] = []
|
|
1689
|
+
if context_status == "missing":
|
|
1690
|
+
missing_live_observations.append({
|
|
1691
|
+
"id": "live_context_window",
|
|
1692
|
+
"required_observation": "live_statusline_snapshot",
|
|
1693
|
+
"affects": ["context_availability", "metric_availability.context"],
|
|
1694
|
+
"reason": "Historical transcript scans do not include live Claude Code context_window data.",
|
|
1695
|
+
})
|
|
1696
|
+
if headroom_status == "missing":
|
|
1697
|
+
missing_live_observations.append({
|
|
1698
|
+
"id": "live_headroom",
|
|
1699
|
+
"required_observation": "live_statusline_snapshot",
|
|
1700
|
+
"affects": ["headroom_availability", "cache_diagnostics.headroom_diagnostics"],
|
|
1701
|
+
"reason": "Historical transcript totals are not remaining-token or live headroom observations.",
|
|
1702
|
+
})
|
|
1703
|
+
|
|
1704
|
+
return {
|
|
1705
|
+
"schema_version": MAC_VISIBILITY_SCHEMA_VERSION,
|
|
1706
|
+
"surface_kind": "local_macos_visibility_contract",
|
|
1707
|
+
"readiness": {
|
|
1708
|
+
"status": readiness_status,
|
|
1709
|
+
"reason": readiness_reason,
|
|
1710
|
+
},
|
|
1711
|
+
"bind_to_top_level_fields": [
|
|
1712
|
+
"source_kind",
|
|
1713
|
+
"source_freshness",
|
|
1714
|
+
"scan_integrity",
|
|
1715
|
+
"metric_availability",
|
|
1716
|
+
"metric_caveats",
|
|
1717
|
+
"redaction_mode",
|
|
1718
|
+
"context_availability",
|
|
1719
|
+
"headroom_availability",
|
|
1720
|
+
"cache_friendliness",
|
|
1721
|
+
"cache_diagnostics",
|
|
1722
|
+
"cache_layout_advice",
|
|
1723
|
+
"totals",
|
|
1724
|
+
],
|
|
1725
|
+
"diagnostic_only_fields": ["summary"],
|
|
1726
|
+
"primary_cards": [
|
|
1727
|
+
_mac_card(
|
|
1728
|
+
"source_freshness",
|
|
1729
|
+
"Source freshness",
|
|
1730
|
+
"available",
|
|
1731
|
+
["source_kind", "source_freshness.status", "source_freshness.generated_at"],
|
|
1732
|
+
),
|
|
1733
|
+
_mac_card(
|
|
1734
|
+
"scan_integrity",
|
|
1735
|
+
"Scan integrity",
|
|
1736
|
+
scan_status,
|
|
1737
|
+
[
|
|
1738
|
+
"scan_integrity.status",
|
|
1739
|
+
"scan_integrity.files_scanned",
|
|
1740
|
+
"scan_integrity.records_scanned",
|
|
1741
|
+
"scan_integrity.skipped_files",
|
|
1742
|
+
"scan_integrity.skipped_records",
|
|
1743
|
+
],
|
|
1744
|
+
),
|
|
1745
|
+
_mac_card(
|
|
1746
|
+
"token_totals",
|
|
1747
|
+
"Token totals",
|
|
1748
|
+
token_status,
|
|
1749
|
+
[
|
|
1750
|
+
"totals.total_tokens",
|
|
1751
|
+
"totals.tokens.input",
|
|
1752
|
+
"totals.tokens.output",
|
|
1753
|
+
"totals.tokens.cache_read",
|
|
1754
|
+
"totals.tokens.cache_creation",
|
|
1755
|
+
],
|
|
1756
|
+
),
|
|
1757
|
+
_mac_card(
|
|
1758
|
+
"cache_reuse",
|
|
1759
|
+
"Cache-read share and reuse ratio",
|
|
1760
|
+
cache_status,
|
|
1761
|
+
["totals.cache_read_share", "totals.cache_reuse_ratio", "metric_availability.cache"],
|
|
1762
|
+
),
|
|
1763
|
+
_mac_card(
|
|
1764
|
+
"observed_cost",
|
|
1765
|
+
"Observed transcript cost",
|
|
1766
|
+
cost_status,
|
|
1767
|
+
["totals.cost_usd_observed", "metric_availability.cost"],
|
|
1768
|
+
),
|
|
1769
|
+
_mac_card(
|
|
1770
|
+
"context_availability",
|
|
1771
|
+
"Context availability",
|
|
1772
|
+
context_status,
|
|
1773
|
+
["context_availability", "metric_availability.context"],
|
|
1774
|
+
required_observation="live_statusline_snapshot" if context_status == "missing" else None,
|
|
1775
|
+
),
|
|
1776
|
+
_mac_card(
|
|
1777
|
+
"headroom_availability",
|
|
1778
|
+
"Headroom availability",
|
|
1779
|
+
headroom_status,
|
|
1780
|
+
["headroom_availability", "cache_diagnostics.headroom_diagnostics"],
|
|
1781
|
+
required_observation="live_statusline_snapshot" if headroom_status == "missing" else None,
|
|
1782
|
+
),
|
|
1783
|
+
_mac_card(
|
|
1784
|
+
"cache_layout_advice",
|
|
1785
|
+
"Cache layout advice",
|
|
1786
|
+
advice_status,
|
|
1787
|
+
["cache_layout_advice", "cache_friendliness", "cache_diagnostics.dynamic_prefix_breakers"],
|
|
1788
|
+
),
|
|
1789
|
+
],
|
|
1790
|
+
"missing_live_observations": missing_live_observations,
|
|
1791
|
+
"claim_boundaries": [
|
|
1792
|
+
"Local transcript observations are not invoice-grade billing records.",
|
|
1793
|
+
"Provider cache fields are telemetry, not ContextGuard-caused token reduction and do not prove provider cache hits.",
|
|
1794
|
+
"Historical transcript totals do not infer live context headroom or remaining tokens.",
|
|
1795
|
+
"This contract does not guarantee token or cost savings.",
|
|
1796
|
+
],
|
|
1797
|
+
"redaction_required": True,
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
|
|
1638
1801
|
def feasibility_json(
|
|
1639
1802
|
summary: UsageSummary,
|
|
1640
1803
|
top: int = 15,
|
|
@@ -1652,6 +1815,11 @@ def feasibility_json(
|
|
|
1652
1815
|
cache_friendliness = cache_friendliness_for_summary(summary)
|
|
1653
1816
|
cache_diagnostics = cache_diagnostics_for_summary(summary)
|
|
1654
1817
|
cache_layout_advice = cache_layout_advice_for_summary(summary)
|
|
1818
|
+
mac_visibility = build_mac_visibility_contract(
|
|
1819
|
+
availability=availability,
|
|
1820
|
+
integrity=integrity,
|
|
1821
|
+
cache_layout_advice=cache_layout_advice,
|
|
1822
|
+
)
|
|
1655
1823
|
return {
|
|
1656
1824
|
"schema_version": FEASIBILITY_SCHEMA_VERSION,
|
|
1657
1825
|
"producer": FEASIBILITY_PRODUCER,
|
|
@@ -1672,6 +1840,7 @@ def feasibility_json(
|
|
|
1672
1840
|
"cache_friendliness",
|
|
1673
1841
|
"cache_diagnostics",
|
|
1674
1842
|
"cache_layout_advice",
|
|
1843
|
+
"mac_visibility",
|
|
1675
1844
|
"totals",
|
|
1676
1845
|
],
|
|
1677
1846
|
"diagnostic_fields": ["summary"],
|
|
@@ -1701,6 +1870,7 @@ def feasibility_json(
|
|
|
1701
1870
|
"cache_friendliness": cache_friendliness,
|
|
1702
1871
|
"cache_diagnostics": cache_diagnostics,
|
|
1703
1872
|
"cache_layout_advice": cache_layout_advice,
|
|
1873
|
+
"mac_visibility": mac_visibility,
|
|
1704
1874
|
"totals": {
|
|
1705
1875
|
"total_tokens": stable_total_tokens,
|
|
1706
1876
|
"tokens": stable_tokens,
|
|
@@ -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)
|