@ictechgy/context-guard 0.4.10 → 0.4.12

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 +17 -1
  2. package/README.ko.md +46 -28
  3. package/README.md +42 -33
  4. package/docs/benchmark-fixtures/token-savings-12task.evidence.example.jsonl +24 -0
  5. package/docs/benchmark-workflow-examples.md +3 -0
  6. package/docs/benchmark-workflows/context-pack-byte-proxy.example.json +278 -137
  7. package/docs/benchmark-workflows/measured-token-workflow.example.json +279 -138
  8. package/docs/benchmark-workflows/provider-cache-telemetry.example.json +279 -138
  9. package/docs/experimental-benchmark-fixtures.md +24 -7
  10. package/package.json +2 -1
  11. package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
  12. package/plugins/context-guard/README.ko.md +14 -11
  13. package/plugins/context-guard/README.md +15 -14
  14. package/plugins/context-guard/bin/context-guard +48 -17
  15. package/plugins/context-guard/bin/context-guard-artifact +342 -33
  16. package/plugins/context-guard/bin/context-guard-audit +36 -5
  17. package/plugins/context-guard/bin/context-guard-bench +1675 -44
  18. package/plugins/context-guard/bin/context-guard-cache-score +347 -35
  19. package/plugins/context-guard/bin/context-guard-compress +89 -27
  20. package/plugins/context-guard/bin/context-guard-cost +7 -2
  21. package/plugins/context-guard/bin/context-guard-experiments +364 -8
  22. package/plugins/context-guard/bin/context-guard-failed-nudge +6 -2
  23. package/plugins/context-guard/bin/context-guard-filter +88 -18
  24. package/plugins/context-guard/bin/context-guard-pack +329 -19
  25. package/plugins/context-guard/bin/context-guard-read-symbol +27 -0
  26. package/plugins/context-guard/bin/context-guard-sanitize-output +245 -18
  27. package/plugins/context-guard/bin/context-guard-setup +21 -5
  28. package/plugins/context-guard/bin/context-guard-tool-prune +287 -62
  29. package/plugins/context-guard/bin/context-guard-trim-output +394 -90
  30. package/plugins/context-guard/brief/README.md +5 -5
  31. package/plugins/context-guard/lib/context_guard_command_manifest_loader.py +123 -0
  32. package/plugins/context-guard/lib/context_guard_commands.py +217 -190
@@ -1,13 +1,13 @@
1
1
  # ContextGuard brief mode (advisory)
2
2
 
3
- Brief mode is a set of **agent-neutral, advisory** rule snippets that ask a coding/tool
4
- agent to cut filler from its responses while preserving the technical evidence a reviewer
5
- needs. It is guidance text, not an enforcement mechanism.
3
+ Brief mode is a set of **agent-neutral, advisory** rule snippets that ask a coding or
4
+ tool-using agent to cut filler from its responses while preserving the technical evidence a
5
+ reviewer needs. It is guidance text, not an enforcement mechanism.
6
6
 
7
7
  - **Advisory / best-effort.** Compatible agents may follow these rules fully, partially, or
8
8
  ignore them. Brief mode does not intercept, rewrite, or block model output.
9
9
  - **No guaranteed savings.** Brief mode does **not** promise any token or cost reduction.
10
- Verbosity behavior varies by agent and model. Measure real before/after results for your
10
+ Verbosity behavior varies by agent and model. Measure real before-and-after results for your
11
11
  own tasks with `context-guard-bench` before making any savings claim.
12
12
  - **Evidence first.** Every level keeps the same mandatory evidence floor (see below). Brief
13
13
  mode trims wording, never correctness-critical content.
@@ -56,7 +56,7 @@ context-guard setup --agent codex --scope project --brief-mode standard --yes
56
56
  context-guard setup --agent codex --scope project --brief-mode off --yes
57
57
  ```
58
58
 
59
- Per the project safety rules it stays dry-run first, writes only local files, backs up
59
+ Per the project safety rules, it stays dry-run first, writes only local files, backs up
60
60
  existing rule files before changing anything, and applies only with explicit approval.
61
61
 
62
62
  Each block is wrapped in stable markers:
@@ -0,0 +1,123 @@
1
+ """Trusted literal loader for the ContextGuard command manifest.
2
+
3
+ The command manifest is intentionally a literal-only Python data file so release
4
+ gates and runtime dispatchers can inspect it without executing manifest code.
5
+ This helper centralizes the bounded no-follow read and AST-literal parsing logic
6
+ used by the runtime dispatcher, release gates, and tests.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import os
12
+ from pathlib import Path
13
+ import stat
14
+ from typing import Any, Iterable, Mapping
15
+
16
+ MAX_COMMAND_MANIFEST_BYTES = 128 * 1024
17
+
18
+ COMMAND_MANIFEST_LITERAL_NAMES = frozenset(
19
+ {
20
+ "IMPLEMENTATION_PAIRS",
21
+ "HELPER_PAIRS",
22
+ "NPM_BINS",
23
+ "NPM_BIN_PATHS",
24
+ "DISPATCHER_SUBCOMMANDS",
25
+ "LEGACY_WRAPPERS",
26
+ "ENTRYPOINT_SMOKE_CASES",
27
+ "PLUGIN_ENTRYPOINTS",
28
+ "DISPATCHER_SMOKE_CASES",
29
+ "EXPECTED_COMMAND_PACK_FILES",
30
+ }
31
+ )
32
+
33
+
34
+ def manifest_open_flags() -> int | None:
35
+ if not hasattr(os, "O_NOFOLLOW"):
36
+ return None
37
+ flags = os.O_RDONLY | os.O_NOFOLLOW
38
+ if hasattr(os, "O_CLOEXEC"):
39
+ flags |= os.O_CLOEXEC
40
+ if hasattr(os, "O_NONBLOCK"):
41
+ flags |= os.O_NONBLOCK
42
+ if hasattr(os, "O_NOCTTY"):
43
+ flags |= os.O_NOCTTY
44
+ return flags
45
+
46
+
47
+ def read_manifest_source(path: Path, *, max_bytes: int = MAX_COMMAND_MANIFEST_BYTES) -> str | None:
48
+ flags = manifest_open_flags()
49
+ if flags is None:
50
+ return None
51
+ fd = -1
52
+ try:
53
+ fd = os.open(path, flags)
54
+ st = os.fstat(fd)
55
+ if not stat.S_ISREG(st.st_mode) or st.st_size > max_bytes:
56
+ return None
57
+ chunks: list[bytes] = []
58
+ total = 0
59
+ while True:
60
+ chunk = os.read(fd, min(64 * 1024, max_bytes + 1 - total))
61
+ if not chunk:
62
+ break
63
+ chunks.append(chunk)
64
+ total += len(chunk)
65
+ if total > max_bytes:
66
+ return None
67
+ return b"".join(chunks).decode("utf-8")
68
+ except (OSError, UnicodeDecodeError):
69
+ return None
70
+ finally:
71
+ if fd >= 0:
72
+ try:
73
+ os.close(fd)
74
+ except OSError:
75
+ pass
76
+
77
+
78
+ def literal_command_manifest_from_source(
79
+ source: str,
80
+ *,
81
+ allowed_names: Iterable[str] = COMMAND_MANIFEST_LITERAL_NAMES,
82
+ ) -> dict[str, Any]:
83
+ try:
84
+ tree = ast.parse(source)
85
+ except SyntaxError as exc:
86
+ raise ValueError(f"invalid Python manifest syntax: line {exc.lineno}: {exc.msg}") from exc
87
+ allowed = set(allowed_names)
88
+ values: dict[str, Any] = {}
89
+ for node in tree.body:
90
+ if isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
91
+ continue
92
+ target: str | None = None
93
+ value: ast.expr | None = None
94
+ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
95
+ target = node.target.id
96
+ value = node.value
97
+ elif isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
98
+ target = node.targets[0].id
99
+ value = node.value
100
+ if target is None:
101
+ raise ValueError(f"unsupported executable manifest statement: {type(node).__name__}")
102
+ if target not in allowed or value is None:
103
+ raise ValueError(f"unsupported manifest assignment: {target}")
104
+ try:
105
+ values[target] = ast.literal_eval(value)
106
+ except (SyntaxError, ValueError) as exc:
107
+ raise ValueError(f"manifest assignment must be a literal: {target}") from exc
108
+ return values
109
+
110
+
111
+ def command_manifest_namespace(values: Mapping[str, Any], *, required: Iterable[str] = ()) -> type:
112
+ missing = sorted(set(required) - set(values))
113
+ if missing:
114
+ raise ValueError(f"trusted command manifest missing required literals: {', '.join(missing)}")
115
+ return type("CommandManifest", (), dict(values))
116
+
117
+
118
+ def load_command_manifest(path: Path, *, required: Iterable[str] = ()) -> type:
119
+ source = read_manifest_source(path)
120
+ if source is None:
121
+ raise ValueError(f"could not load trusted command manifest source: {path}")
122
+ values = literal_command_manifest_from_source(source)
123
+ return command_manifest_namespace(values, required=required)
@@ -1,206 +1,233 @@
1
- #!/usr/bin/env python3
2
1
  """Canonical ContextGuard command/package manifest.
3
2
 
4
3
  This module is intentionally side-effect free. It centralizes command, copy,
5
4
  package, and smoke-test inventories that were previously repeated across the
6
5
  runtime dispatcher, release gates, sync tooling, smoke tests, and unit tests.
7
6
  """
8
- from __future__ import annotations
9
7
 
10
- from typing import Any
8
+ IMPLEMENTATION_PAIRS = (('context_guard_cli.py', 'context-guard'),
9
+ ('cost_guard.py', 'context-guard-cost'),
10
+ ('cache_score.py', 'context-guard-cache-score'),
11
+ ('benchmark_runner.py', 'context-guard-bench'),
12
+ ('context_escrow.py', 'context-guard-artifact'),
13
+ ('context_compress.py', 'context-guard-compress'),
14
+ ('context_pack.py', 'context-guard-pack'),
15
+ ('context_filter.py', 'context-guard-filter'),
16
+ ('tool_schema_pruner.py', 'context-guard-tool-prune'),
17
+ ('claude_transcript_cost_audit.py', 'context-guard-audit'),
18
+ ('context_guard_diet.py', 'context-guard-diet'),
19
+ ('experimental_registry.py', 'context-guard-experiments'),
20
+ ('failed_attempt_nudge.py', 'context-guard-failed-nudge'),
21
+ ('guard_large_read.py', 'context-guard-guard-read'),
22
+ ('read_symbol.py', 'context-guard-read-symbol'),
23
+ ('rewrite_bash_for_token_budget.py', 'context-guard-rewrite-bash'),
24
+ ('sanitize_output.py', 'context-guard-sanitize-output'),
25
+ ('setup_wizard.py', 'context-guard-setup'),
26
+ ('statusline.sh', 'context-guard-statusline'),
27
+ ('statusline_merged.sh', 'context-guard-statusline-merged'),
28
+ ('trim_command_output.py', 'context-guard-trim-output'))
11
29
 
12
- IMPLEMENTATION_PAIRS: tuple[tuple[str, str], ...] = (
13
- ("context_guard_cli.py", "context-guard"),
14
- ("cost_guard.py", "context-guard-cost"),
15
- ("cache_score.py", "context-guard-cache-score"),
16
- ("benchmark_runner.py", "context-guard-bench"),
17
- ("context_escrow.py", "context-guard-artifact"),
18
- ("context_compress.py", "context-guard-compress"),
19
- ("context_pack.py", "context-guard-pack"),
20
- ("context_filter.py", "context-guard-filter"),
21
- ("tool_schema_pruner.py", "context-guard-tool-prune"),
22
- ("claude_transcript_cost_audit.py", "context-guard-audit"),
23
- ("context_guard_diet.py", "context-guard-diet"),
24
- ("experimental_registry.py", "context-guard-experiments"),
25
- ("failed_attempt_nudge.py", "context-guard-failed-nudge"),
26
- ("guard_large_read.py", "context-guard-guard-read"),
27
- ("read_symbol.py", "context-guard-read-symbol"),
28
- ("rewrite_bash_for_token_budget.py", "context-guard-rewrite-bash"),
29
- ("sanitize_output.py", "context-guard-sanitize-output"),
30
- ("setup_wizard.py", "context-guard-setup"),
31
- ("statusline.sh", "context-guard-statusline"),
32
- ("statusline_merged.sh", "context-guard-statusline-merged"),
33
- ("trim_command_output.py", "context-guard-trim-output"),
34
- )
30
+ HELPER_PAIRS = (('hook_secret_patterns.py', 'lib/hook_secret_patterns.py'),
31
+ ('context_guard_commands.py', 'lib/context_guard_commands.py'),
32
+ ('context_guard_command_manifest_loader.py',
33
+ 'lib/context_guard_command_manifest_loader.py'))
35
34
 
36
- HELPER_PAIRS: tuple[tuple[str, str], ...] = (
37
- ("hook_secret_patterns.py", "lib/hook_secret_patterns.py"),
38
- ("context_guard_commands.py", "lib/context_guard_commands.py"),
39
- )
35
+ NPM_BINS = ('context-guard',
36
+ 'context-guard-cost',
37
+ 'context-guard-cache-score',
38
+ 'context-guard-bench',
39
+ 'context-guard-artifact',
40
+ 'context-guard-compress',
41
+ 'context-guard-pack',
42
+ 'context-guard-filter',
43
+ 'context-guard-tool-prune',
44
+ 'context-guard-audit',
45
+ 'context-guard-diet',
46
+ 'context-guard-experiments',
47
+ 'context-guard-failed-nudge',
48
+ 'context-guard-guard-read',
49
+ 'context-guard-read-symbol',
50
+ 'context-guard-rewrite-bash',
51
+ 'context-guard-sanitize-output',
52
+ 'context-guard-setup',
53
+ 'context-guard-statusline',
54
+ 'context-guard-statusline-merged',
55
+ 'context-guard-trim-output')
40
56
 
41
- NPM_BINS: tuple[str, ...] = (
42
- "context-guard",
43
- "context-guard-cost",
44
- "context-guard-cache-score",
45
- "context-guard-bench",
46
- "context-guard-artifact",
47
- "context-guard-compress",
48
- "context-guard-pack",
49
- "context-guard-filter",
50
- "context-guard-tool-prune",
51
- "context-guard-audit",
52
- "context-guard-diet",
53
- "context-guard-experiments",
54
- "context-guard-failed-nudge",
55
- "context-guard-guard-read",
56
- "context-guard-read-symbol",
57
- "context-guard-rewrite-bash",
58
- "context-guard-sanitize-output",
59
- "context-guard-setup",
60
- "context-guard-statusline",
61
- "context-guard-statusline-merged",
62
- "context-guard-trim-output",
63
- )
64
- NPM_BIN_PATHS: dict[str, str] = {
65
- name: f"plugins/context-guard/bin/{name}" for name in NPM_BINS
66
- }
57
+ NPM_BIN_PATHS = {'context-guard': 'plugins/context-guard/bin/context-guard',
58
+ 'context-guard-cost': 'plugins/context-guard/bin/context-guard-cost',
59
+ 'context-guard-cache-score': 'plugins/context-guard/bin/context-guard-cache-score',
60
+ 'context-guard-bench': 'plugins/context-guard/bin/context-guard-bench',
61
+ 'context-guard-artifact': 'plugins/context-guard/bin/context-guard-artifact',
62
+ 'context-guard-compress': 'plugins/context-guard/bin/context-guard-compress',
63
+ 'context-guard-pack': 'plugins/context-guard/bin/context-guard-pack',
64
+ 'context-guard-filter': 'plugins/context-guard/bin/context-guard-filter',
65
+ 'context-guard-tool-prune': 'plugins/context-guard/bin/context-guard-tool-prune',
66
+ 'context-guard-audit': 'plugins/context-guard/bin/context-guard-audit',
67
+ 'context-guard-diet': 'plugins/context-guard/bin/context-guard-diet',
68
+ 'context-guard-experiments': 'plugins/context-guard/bin/context-guard-experiments',
69
+ 'context-guard-failed-nudge': 'plugins/context-guard/bin/context-guard-failed-nudge',
70
+ 'context-guard-guard-read': 'plugins/context-guard/bin/context-guard-guard-read',
71
+ 'context-guard-read-symbol': 'plugins/context-guard/bin/context-guard-read-symbol',
72
+ 'context-guard-rewrite-bash': 'plugins/context-guard/bin/context-guard-rewrite-bash',
73
+ 'context-guard-sanitize-output': 'plugins/context-guard/bin/context-guard-sanitize-output',
74
+ 'context-guard-setup': 'plugins/context-guard/bin/context-guard-setup',
75
+ 'context-guard-statusline': 'plugins/context-guard/bin/context-guard-statusline',
76
+ 'context-guard-statusline-merged': 'plugins/context-guard/bin/context-guard-statusline-merged',
77
+ 'context-guard-trim-output': 'plugins/context-guard/bin/context-guard-trim-output'}
67
78
 
68
- DISPATCHER_SUBCOMMANDS: dict[str, tuple[str, ...]] = {
69
- "setup": ("context-guard-setup",),
70
- "doctor": ("context-guard-setup", "--verify"),
71
- "audit": ("context-guard-audit",),
72
- "diet": ("context-guard-diet",),
73
- "experiments": ("context-guard-experiments",),
74
- "scan": ("context-guard-diet", "scan"),
75
- "trim-output": ("context-guard-trim-output",),
76
- "trim": ("context-guard-trim-output",),
77
- "sanitize-output": ("context-guard-sanitize-output",),
78
- "sanitize": ("context-guard-sanitize-output",),
79
- "filter": ("context-guard-filter",),
80
- "artifact": ("context-guard-artifact",),
81
- "pack": ("context-guard-pack",),
82
- "tool-prune": ("context-guard-tool-prune",),
83
- "compress": ("context-guard-compress",),
84
- "cost": ("context-guard-cost",),
85
- "route-advisor": ("context-guard-cost", "route-advisor"),
86
- "route": ("context-guard-cost", "route-advisor"),
87
- "cache-score": ("context-guard-cache-score",),
88
- "bench": ("context-guard-bench",),
89
- "read-symbol": ("context-guard-read-symbol",),
90
- "rewrite-bash": ("context-guard-rewrite-bash",),
91
- "guard-read": ("context-guard-guard-read",),
92
- "failed-nudge": ("context-guard-failed-nudge",),
93
- "statusline": ("context-guard-statusline",),
94
- "statusline-merged": ("context-guard-statusline-merged",),
95
- }
79
+ DISPATCHER_SUBCOMMANDS = {'setup': ('context-guard-setup',),
80
+ 'doctor': ('context-guard-setup', '--verify'),
81
+ 'audit': ('context-guard-audit',),
82
+ 'diet': ('context-guard-diet',),
83
+ 'experiments': ('context-guard-experiments',),
84
+ 'scan': ('context-guard-diet', 'scan'),
85
+ 'trim-output': ('context-guard-trim-output',),
86
+ 'trim': ('context-guard-trim-output',),
87
+ 'sanitize-output': ('context-guard-sanitize-output',),
88
+ 'sanitize': ('context-guard-sanitize-output',),
89
+ 'filter': ('context-guard-filter',),
90
+ 'artifact': ('context-guard-artifact',),
91
+ 'pack': ('context-guard-pack',),
92
+ 'tool-prune': ('context-guard-tool-prune',),
93
+ 'compress': ('context-guard-compress',),
94
+ 'cost': ('context-guard-cost',),
95
+ 'route-advisor': ('context-guard-cost', 'route-advisor'),
96
+ 'route': ('context-guard-cost', 'route-advisor'),
97
+ 'cache-score': ('context-guard-cache-score',),
98
+ 'bench': ('context-guard-bench',),
99
+ 'read-symbol': ('context-guard-read-symbol',),
100
+ 'rewrite-bash': ('context-guard-rewrite-bash',),
101
+ 'guard-read': ('context-guard-guard-read',),
102
+ 'failed-nudge': ('context-guard-failed-nudge',),
103
+ 'statusline': ('context-guard-statusline',),
104
+ 'statusline-merged': ('context-guard-statusline-merged',)}
96
105
 
97
- LEGACY_WRAPPERS: tuple[str, ...] = (
98
- "claude-read-symbol",
99
- "claude-sanitize-output",
100
- "claude-token-artifact",
101
- "claude-token-audit",
102
- "claude-token-bench",
103
- "claude-token-diet",
104
- "claude-token-failed-nudge",
105
- "claude-token-guard-read",
106
- "claude-token-rewrite-bash",
107
- "claude-token-setup",
108
- "claude-token-statusline",
109
- "claude-token-statusline-merged",
110
- "claude-trim-output",
111
- )
106
+ LEGACY_WRAPPERS = ('claude-read-symbol',
107
+ 'claude-sanitize-output',
108
+ 'claude-token-artifact',
109
+ 'claude-token-audit',
110
+ 'claude-token-bench',
111
+ 'claude-token-diet',
112
+ 'claude-token-failed-nudge',
113
+ 'claude-token-guard-read',
114
+ 'claude-token-rewrite-bash',
115
+ 'claude-token-setup',
116
+ 'claude-token-statusline',
117
+ 'claude-token-statusline-merged',
118
+ 'claude-trim-output')
112
119
 
113
- ENTRYPOINT_SMOKE_CASES: dict[str, dict[str, Any]] = {
114
- "context-guard": {"args": ["--version"], "mode": "text"},
115
- "context-guard-read-symbol": {"args": ["--help"], "mode": "text"},
116
- "context-guard-sanitize-output": {"args": ["--help"], "mode": "text"},
117
- "context-guard-artifact": {"args": ["--help"], "mode": "text"},
118
- "context-guard-audit": {"args": ["--help"], "mode": "text"},
119
- "context-guard-bench": {"args": ["--help"], "mode": "text"},
120
- "context-guard-compress": {"args": ["--help"], "mode": "text"},
121
- "context-guard-cost": {"args": ["--help"], "mode": "text"},
122
- "context-guard-cache-score": {"args": ["--help"], "mode": "text"},
123
- "context-guard-pack": {"args": ["--help"], "mode": "text"},
124
- "context-guard-tool-prune": {"args": ["--help"], "mode": "text"},
125
- "context-guard-diet": {"args": ["--help"], "mode": "text"},
126
- "context-guard-experiments": {"args": ["--help"], "mode": "text"},
127
- "context-guard-failed-nudge": {"args": [], "mode": "hook-json"},
128
- "context-guard-filter": {"args": ["--help"], "mode": "text"},
129
- "context-guard-guard-read": {"args": [], "mode": "hook-json"},
130
- "context-guard-rewrite-bash": {"args": [], "mode": "hook-json"},
131
- "context-guard-setup": {"args": ["--help"], "mode": "text"},
132
- "context-guard-statusline": {"args": [], "mode": "statusline"},
133
- "context-guard-statusline-merged": {"args": [], "mode": "statusline"},
134
- "context-guard-trim-output": {"args": ["--help"], "mode": "text"},
135
- # Legacy wrappers kept so existing automation does not break during the rebrand.
136
- "claude-read-symbol": {"args": ["--help"], "mode": "text"},
137
- "claude-sanitize-output": {"args": ["--help"], "mode": "text"},
138
- "claude-token-artifact": {"args": ["--help"], "mode": "text"},
139
- "claude-token-audit": {"args": ["--help"], "mode": "text"},
140
- "claude-token-bench": {"args": ["--help"], "mode": "text"},
141
- "claude-token-diet": {"args": ["--help"], "mode": "text"},
142
- "claude-token-failed-nudge": {"args": [], "mode": "hook-json"},
143
- "claude-token-guard-read": {"args": [], "mode": "hook-json"},
144
- "claude-token-rewrite-bash": {"args": [], "mode": "hook-json"},
145
- "claude-token-setup": {"args": ["--help"], "mode": "text"},
146
- "claude-token-statusline": {"args": [], "mode": "statusline"},
147
- "claude-token-statusline-merged": {"args": [], "mode": "statusline"},
148
- "claude-trim-output": {"args": ["--help"], "mode": "text"},
149
- }
120
+ ENTRYPOINT_SMOKE_CASES = {'context-guard': {'args': ['--version'], 'mode': 'text'},
121
+ 'context-guard-read-symbol': {'args': ['--help'], 'mode': 'text'},
122
+ 'context-guard-sanitize-output': {'args': ['--help'], 'mode': 'text'},
123
+ 'context-guard-artifact': {'args': ['--help'], 'mode': 'text'},
124
+ 'context-guard-audit': {'args': ['--help'], 'mode': 'text'},
125
+ 'context-guard-bench': {'args': ['--help'], 'mode': 'text'},
126
+ 'context-guard-compress': {'args': ['--help'], 'mode': 'text'},
127
+ 'context-guard-cost': {'args': ['--help'], 'mode': 'text'},
128
+ 'context-guard-cache-score': {'args': ['--help'], 'mode': 'text'},
129
+ 'context-guard-pack': {'args': ['--help'], 'mode': 'text'},
130
+ 'context-guard-tool-prune': {'args': ['--help'], 'mode': 'text'},
131
+ 'context-guard-diet': {'args': ['--help'], 'mode': 'text'},
132
+ 'context-guard-experiments': {'args': ['--help'], 'mode': 'text'},
133
+ 'context-guard-failed-nudge': {'args': [], 'mode': 'hook-json'},
134
+ 'context-guard-filter': {'args': ['--help'], 'mode': 'text'},
135
+ 'context-guard-guard-read': {'args': [], 'mode': 'hook-json'},
136
+ 'context-guard-rewrite-bash': {'args': [], 'mode': 'hook-json'},
137
+ 'context-guard-setup': {'args': ['--help'], 'mode': 'text'},
138
+ 'context-guard-statusline': {'args': [], 'mode': 'statusline'},
139
+ 'context-guard-statusline-merged': {'args': [], 'mode': 'statusline'},
140
+ 'context-guard-trim-output': {'args': ['--help'], 'mode': 'text'},
141
+ 'claude-read-symbol': {'args': ['--help'], 'mode': 'text'},
142
+ 'claude-sanitize-output': {'args': ['--help'], 'mode': 'text'},
143
+ 'claude-token-artifact': {'args': ['--help'], 'mode': 'text'},
144
+ 'claude-token-audit': {'args': ['--help'], 'mode': 'text'},
145
+ 'claude-token-bench': {'args': ['--help'], 'mode': 'text'},
146
+ 'claude-token-diet': {'args': ['--help'], 'mode': 'text'},
147
+ 'claude-token-failed-nudge': {'args': [], 'mode': 'hook-json'},
148
+ 'claude-token-guard-read': {'args': [], 'mode': 'hook-json'},
149
+ 'claude-token-rewrite-bash': {'args': [], 'mode': 'hook-json'},
150
+ 'claude-token-setup': {'args': ['--help'], 'mode': 'text'},
151
+ 'claude-token-statusline': {'args': [], 'mode': 'statusline'},
152
+ 'claude-token-statusline-merged': {'args': [], 'mode': 'statusline'},
153
+ 'claude-trim-output': {'args': ['--help'], 'mode': 'text'}}
150
154
 
151
- PLUGIN_ENTRYPOINTS: tuple[str, ...] = (
152
- "claude-read-symbol",
153
- "claude-sanitize-output",
154
- "claude-token-artifact",
155
- "claude-token-audit",
156
- "claude-token-bench",
157
- "claude-token-diet",
158
- "claude-token-failed-nudge",
159
- "claude-token-guard-read",
160
- "claude-token-rewrite-bash",
161
- "claude-token-setup",
162
- "claude-token-statusline",
163
- "claude-token-statusline-merged",
164
- "claude-trim-output",
165
- "context-guard",
166
- "context-guard-artifact",
167
- "context-guard-audit",
168
- "context-guard-bench",
169
- "context-guard-compress",
170
- "context-guard-cost",
171
- "context-guard-cache-score",
172
- "context-guard-diet",
173
- "context-guard-experiments",
174
- "context-guard-failed-nudge",
175
- "context-guard-filter",
176
- "context-guard-guard-read",
177
- "context-guard-pack",
178
- "context-guard-read-symbol",
179
- "context-guard-rewrite-bash",
180
- "context-guard-sanitize-output",
181
- "context-guard-setup",
182
- "context-guard-statusline",
183
- "context-guard-statusline-merged",
184
- "context-guard-tool-prune",
185
- "context-guard-trim-output",
186
- )
155
+ PLUGIN_ENTRYPOINTS = ('claude-read-symbol',
156
+ 'claude-sanitize-output',
157
+ 'claude-token-artifact',
158
+ 'claude-token-audit',
159
+ 'claude-token-bench',
160
+ 'claude-token-diet',
161
+ 'claude-token-failed-nudge',
162
+ 'claude-token-guard-read',
163
+ 'claude-token-rewrite-bash',
164
+ 'claude-token-setup',
165
+ 'claude-token-statusline',
166
+ 'claude-token-statusline-merged',
167
+ 'claude-trim-output',
168
+ 'context-guard',
169
+ 'context-guard-artifact',
170
+ 'context-guard-audit',
171
+ 'context-guard-bench',
172
+ 'context-guard-compress',
173
+ 'context-guard-cost',
174
+ 'context-guard-cache-score',
175
+ 'context-guard-diet',
176
+ 'context-guard-experiments',
177
+ 'context-guard-failed-nudge',
178
+ 'context-guard-filter',
179
+ 'context-guard-guard-read',
180
+ 'context-guard-pack',
181
+ 'context-guard-read-symbol',
182
+ 'context-guard-rewrite-bash',
183
+ 'context-guard-sanitize-output',
184
+ 'context-guard-setup',
185
+ 'context-guard-statusline',
186
+ 'context-guard-statusline-merged',
187
+ 'context-guard-tool-prune',
188
+ 'context-guard-trim-output')
187
189
 
188
- DISPATCHER_SMOKE_CASES: tuple[dict[str, Any], ...] = (
189
- {"entrypoint": "context-guard", "args": ["experiments", "list", "--json"], "mode": "json"},
190
- {"entrypoint": "context-guard", "args": ["cost", "--help"], "mode": "text"},
191
- {"entrypoint": "context-guard", "args": ["route-advisor", "--help"], "mode": "text"},
192
- {"entrypoint": "context-guard", "args": ["cache-score", "--help"], "mode": "text"},
193
- {"entrypoint": "context-guard-pack", "args": ["suggest", "--help"], "mode": "text"},
194
- {"entrypoint": "context-guard-pack", "args": ["auto", "--help"], "mode": "text"},
195
- )
190
+ DISPATCHER_SMOKE_CASES = ({'entrypoint': 'context-guard', 'args': ['experiments', 'list', '--json'], 'mode': 'json'},
191
+ {'entrypoint': 'context-guard', 'args': ['cost', '--help'], 'mode': 'text'},
192
+ {'entrypoint': 'context-guard', 'args': ['route-advisor', '--help'], 'mode': 'text'},
193
+ {'entrypoint': 'context-guard', 'args': ['cache-score', '--help'], 'mode': 'text'},
194
+ {'entrypoint': 'context-guard-pack', 'args': ['suggest', '--help'], 'mode': 'text'},
195
+ {'entrypoint': 'context-guard-pack', 'args': ['auto', '--help'], 'mode': 'text'})
196
196
 
197
-
198
- def expected_command_pack_files() -> tuple[str, ...]:
199
- # npm packages ship the plugin-local executable/helper copies only. The
200
- # checkout-local ``context-guard-kit`` files remain the source of truth for
201
- # maintainers and are kept byte-synchronized with these packaged copies by
202
- # ``scripts/sync_plugin_copies.py`` and ``scripts/prepublish_check.py``.
203
- files = {f"plugins/context-guard/bin/{bin_name}" for _kit_name, bin_name in IMPLEMENTATION_PAIRS}
204
- files.update(f"plugins/context-guard/{plugin_rel}" for _kit_name, plugin_rel in HELPER_PAIRS)
205
- files.update(f"plugins/context-guard/bin/{wrapper}" for wrapper in LEGACY_WRAPPERS)
206
- return tuple(sorted(files))
197
+ EXPECTED_COMMAND_PACK_FILES = ('plugins/context-guard/bin/claude-read-symbol',
198
+ 'plugins/context-guard/bin/claude-sanitize-output',
199
+ 'plugins/context-guard/bin/claude-token-artifact',
200
+ 'plugins/context-guard/bin/claude-token-audit',
201
+ 'plugins/context-guard/bin/claude-token-bench',
202
+ 'plugins/context-guard/bin/claude-token-diet',
203
+ 'plugins/context-guard/bin/claude-token-failed-nudge',
204
+ 'plugins/context-guard/bin/claude-token-guard-read',
205
+ 'plugins/context-guard/bin/claude-token-rewrite-bash',
206
+ 'plugins/context-guard/bin/claude-token-setup',
207
+ 'plugins/context-guard/bin/claude-token-statusline',
208
+ 'plugins/context-guard/bin/claude-token-statusline-merged',
209
+ 'plugins/context-guard/bin/claude-trim-output',
210
+ 'plugins/context-guard/bin/context-guard',
211
+ 'plugins/context-guard/bin/context-guard-artifact',
212
+ 'plugins/context-guard/bin/context-guard-audit',
213
+ 'plugins/context-guard/bin/context-guard-bench',
214
+ 'plugins/context-guard/bin/context-guard-cache-score',
215
+ 'plugins/context-guard/bin/context-guard-compress',
216
+ 'plugins/context-guard/bin/context-guard-cost',
217
+ 'plugins/context-guard/bin/context-guard-diet',
218
+ 'plugins/context-guard/bin/context-guard-experiments',
219
+ 'plugins/context-guard/bin/context-guard-failed-nudge',
220
+ 'plugins/context-guard/bin/context-guard-filter',
221
+ 'plugins/context-guard/bin/context-guard-guard-read',
222
+ 'plugins/context-guard/bin/context-guard-pack',
223
+ 'plugins/context-guard/bin/context-guard-read-symbol',
224
+ 'plugins/context-guard/bin/context-guard-rewrite-bash',
225
+ 'plugins/context-guard/bin/context-guard-sanitize-output',
226
+ 'plugins/context-guard/bin/context-guard-setup',
227
+ 'plugins/context-guard/bin/context-guard-statusline',
228
+ 'plugins/context-guard/bin/context-guard-statusline-merged',
229
+ 'plugins/context-guard/bin/context-guard-tool-prune',
230
+ 'plugins/context-guard/bin/context-guard-trim-output',
231
+ 'plugins/context-guard/lib/context_guard_command_manifest_loader.py',
232
+ 'plugins/context-guard/lib/context_guard_commands.py',
233
+ 'plugins/context-guard/lib/hook_secret_patterns.py')