@ictechgy/context-guard 0.4.9 → 0.4.11

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 (64) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.ko.md +59 -31
  3. package/README.md +85 -36
  4. package/docs/benchmark-fixtures/token-savings-12task-baseline.prompt.example.md +7 -0
  5. package/docs/benchmark-fixtures/token-savings-12task-contextguard.prompt.example.md +7 -0
  6. package/docs/benchmark-fixtures/token-savings-12task.evidence.example.jsonl +24 -0
  7. package/docs/benchmark-fixtures/token-savings-12task.tasks.example.json +182 -0
  8. package/docs/benchmark-fixtures/token-savings-12task.variants.example.json +10 -0
  9. package/docs/benchmark-workflow-examples.md +3 -0
  10. package/docs/benchmark-workflows/context-pack-byte-proxy.example.json +278 -137
  11. package/docs/benchmark-workflows/measured-token-workflow.example.json +279 -138
  12. package/docs/benchmark-workflows/provider-cache-telemetry.example.json +279 -138
  13. package/docs/distribution.md +10 -7
  14. package/docs/experimental-benchmark-fixtures.md +30 -6
  15. package/package.json +4 -6
  16. package/packaging/homebrew/context-guard.rb.template +1 -1
  17. package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
  18. package/plugins/context-guard/README.ko.md +20 -14
  19. package/plugins/context-guard/README.md +26 -17
  20. package/plugins/context-guard/bin/context-guard +147 -25
  21. package/plugins/context-guard/bin/context-guard-artifact +884 -79
  22. package/plugins/context-guard/bin/context-guard-audit +33 -2
  23. package/plugins/context-guard/bin/context-guard-bench +1542 -31
  24. package/plugins/context-guard/bin/context-guard-cache-score +665 -0
  25. package/plugins/context-guard/bin/context-guard-compress +146 -1
  26. package/plugins/context-guard/bin/context-guard-cost +790 -6
  27. package/plugins/context-guard/bin/context-guard-experiments +463 -26
  28. package/plugins/context-guard/bin/context-guard-failed-nudge +9 -2
  29. package/plugins/context-guard/bin/context-guard-filter +163 -7
  30. package/plugins/context-guard/bin/context-guard-guard-read +3 -0
  31. package/plugins/context-guard/bin/context-guard-pack +892 -49
  32. package/plugins/context-guard/bin/context-guard-rewrite-bash +3 -0
  33. package/plugins/context-guard/bin/context-guard-sanitize-output +76 -12
  34. package/plugins/context-guard/bin/context-guard-setup +165 -31
  35. package/plugins/context-guard/bin/context-guard-statusline +490 -283
  36. package/plugins/context-guard/bin/context-guard-statusline-merged +5 -0
  37. package/plugins/context-guard/bin/context-guard-tool-prune +480 -53
  38. package/plugins/context-guard/bin/context-guard-trim-output +288 -41
  39. package/plugins/context-guard/brief/README.md +5 -5
  40. package/plugins/context-guard/lib/context_guard_commands.py +230 -0
  41. package/plugins/context-guard/skills/setup/SKILL.md +1 -0
  42. package/context-guard-kit/README.md +0 -91
  43. package/context-guard-kit/benchmark_runner.py +0 -2401
  44. package/context-guard-kit/claude_transcript_cost_audit.py +0 -2346
  45. package/context-guard-kit/context_compress.py +0 -695
  46. package/context-guard-kit/context_escrow.py +0 -935
  47. package/context-guard-kit/context_filter.py +0 -637
  48. package/context-guard-kit/context_guard_cli.py +0 -325
  49. package/context-guard-kit/context_guard_diet.py +0 -1711
  50. package/context-guard-kit/context_pack.py +0 -2713
  51. package/context-guard-kit/cost_guard.py +0 -2349
  52. package/context-guard-kit/experimental_registry.py +0 -4348
  53. package/context-guard-kit/failed_attempt_nudge.py +0 -567
  54. package/context-guard-kit/guard_large_read.py +0 -690
  55. package/context-guard-kit/hook_secret_patterns.py +0 -43
  56. package/context-guard-kit/read_symbol.py +0 -483
  57. package/context-guard-kit/rewrite_bash_for_token_budget.py +0 -501
  58. package/context-guard-kit/sanitize_output.py +0 -725
  59. package/context-guard-kit/settings.example.json +0 -67
  60. package/context-guard-kit/setup_wizard.py +0 -2515
  61. package/context-guard-kit/statusline.sh +0 -362
  62. package/context-guard-kit/statusline_merged.sh +0 -157
  63. package/context-guard-kit/tool_schema_pruner.py +0 -837
  64. package/context-guard-kit/trim_command_output.py +0 -1449
@@ -56,8 +56,10 @@ JSON_PARSE_RECURSION_LIMIT = 10_000
56
56
  READ_CHUNK_BYTES = 64 * 1024
57
57
  DEFAULT_MAX_FILE_BYTES = 50 * 1024 * 1024
58
58
  DEFAULT_MAX_LINE_BYTES = 2 * 1024 * 1024
59
+ DEFAULT_MAX_SCAN_FILES = 100_000
59
60
  MAX_FILE_BYTES_LIMIT = 2 * 1024 * 1024 * 1024
60
61
  MAX_LINE_BYTES_LIMIT = 128 * 1024 * 1024
62
+ MAX_SCAN_FILES_LIMIT = 1_000_000
61
63
  SECRET_VALUE_RE = re.compile(
62
64
  r"(?i)(gh[pousr]_[A-Za-z0-9_]{8,}|github_pat_[A-Za-z0-9_]{20,}|"
63
65
  r"xox[abprs]-[A-Za-z0-9-]{8,}|(?:AKIA|ASIA)[0-9A-Z]{8,}|"
@@ -169,6 +171,8 @@ class UsageSummary:
169
171
  files: int = 0
170
172
  records: int = 0
171
173
  skipped_files: int = 0
174
+ unscanned_files_lower_bound: int = 0
175
+ scan_truncated: bool = False
172
176
  skipped_records: int = 0
173
177
  parse_errors: list[str] = field(default_factory=list)
174
178
  tokens: Counter[str] = field(default_factory=Counter)
@@ -618,6 +622,7 @@ def os_error_summary(exc: OSError) -> str:
618
622
  class ScanLimits:
619
623
  max_file_bytes: int = DEFAULT_MAX_FILE_BYTES
620
624
  max_line_bytes: int = DEFAULT_MAX_LINE_BYTES
625
+ max_files: int = DEFAULT_MAX_SCAN_FILES
621
626
 
622
627
 
623
628
  def open_regular_no_symlink(file: Path):
@@ -809,6 +814,15 @@ def scan(
809
814
  limits = limits or ScanLimits()
810
815
  summary = UsageSummary()
811
816
  for file in iter_jsonl_files(paths):
817
+ if summary.files >= limits.max_files:
818
+ summary.skipped_files += 1
819
+ summary.unscanned_files_lower_bound += 1
820
+ summary.scan_truncated = True
821
+ summary.note_error(
822
+ f"transcript scan file limit reached ({limits.max_files}); "
823
+ "rerun with narrower paths or --max-files if more evidence is required"
824
+ )
825
+ break
812
826
  summary.files += 1
813
827
  try:
814
828
  with open_regular_no_symlink(file) as handle:
@@ -925,6 +939,8 @@ def scan_integrity(summary: UsageSummary) -> dict[str, Any]:
925
939
  "files_scanned": summary.files,
926
940
  "records_scanned": summary.records,
927
941
  "skipped_files": summary.skipped_files,
942
+ "unscanned_files_lower_bound": summary.unscanned_files_lower_bound,
943
+ "scan_truncated": summary.scan_truncated,
928
944
  "skipped_records": summary.skipped_records,
929
945
  "parse_error_count": len(summary.parse_errors),
930
946
  "complete": complete,
@@ -2151,11 +2167,14 @@ def summary_json(
2151
2167
  "files": summary.files,
2152
2168
  "records": summary.records,
2153
2169
  "skipped_files": summary.skipped_files,
2170
+ "unscanned_files_lower_bound": summary.unscanned_files_lower_bound,
2171
+ "scan_truncated": summary.scan_truncated,
2154
2172
  "skipped_records": summary.skipped_records,
2155
2173
  "parse_errors": summary.parse_errors,
2156
2174
  "scan_limits": {
2157
2175
  "max_file_bytes": limits.max_file_bytes,
2158
2176
  "max_line_bytes": limits.max_line_bytes,
2177
+ "max_files": limits.max_files,
2159
2178
  },
2160
2179
  "total_tokens": summary.total_tokens,
2161
2180
  "tokens": dict(summary.tokens),
@@ -2221,10 +2240,17 @@ def main() -> int:
2221
2240
  default=DEFAULT_MAX_LINE_BYTES,
2222
2241
  help="skip individual JSONL records larger than this many bytes (default: 2 MiB)",
2223
2242
  )
2243
+ parser.add_argument(
2244
+ "--max-files",
2245
+ type=int,
2246
+ default=DEFAULT_MAX_SCAN_FILES,
2247
+ help=f"stop after this many transcript files (default: {DEFAULT_MAX_SCAN_FILES})",
2248
+ )
2224
2249
  args = parser.parse_args()
2225
2250
  limits = ScanLimits(
2226
2251
  max_file_bytes=require_scan_limit(parser, "--max-file-bytes", args.max_file_bytes, MAX_FILE_BYTES_LIMIT),
2227
2252
  max_line_bytes=require_scan_limit(parser, "--max-line-bytes", args.max_line_bytes, MAX_LINE_BYTES_LIMIT),
2253
+ max_files=require_scan_limit(parser, "--max-files", args.max_files, MAX_SCAN_FILES_LIMIT),
2228
2254
  )
2229
2255
 
2230
2256
  summary = scan(args.paths, show_paths=args.show_paths, show_commands=args.show_commands, limits=limits)
@@ -2248,9 +2274,14 @@ def main() -> int:
2248
2274
  print("Claude Code transcript usage audit")
2249
2275
  print(
2250
2276
  f"files_scanned={summary.files} records={summary.records} "
2251
- f"skipped_files={summary.skipped_files} skipped_records={summary.skipped_records}"
2277
+ f"skipped_files={summary.skipped_files} skipped_records={summary.skipped_records} "
2278
+ f"scan_truncated={str(summary.scan_truncated).lower()} "
2279
+ f"unscanned_files_lower_bound={summary.unscanned_files_lower_bound}"
2280
+ )
2281
+ print(
2282
+ f"scan_limits=max_file_bytes:{limits.max_file_bytes} "
2283
+ f"max_line_bytes:{limits.max_line_bytes} max_files:{limits.max_files}"
2252
2284
  )
2253
- print(f"scan_limits=max_file_bytes:{limits.max_file_bytes} max_line_bytes:{limits.max_line_bytes}")
2254
2285
  print(f"observed_total_tokens={summary.total_tokens}")
2255
2286
  if summary.cost_usd:
2256
2287
  print(f"observed_cost_usd={summary.cost_usd:.4f}")