@ictechgy/context-guard 0.4.15 → 0.4.16

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 (29) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.ko.md +46 -1
  3. package/README.md +58 -2
  4. package/package.json +1 -1
  5. package/plugins/context-guard/.claude-plugin/plugin.json +1 -1
  6. package/plugins/context-guard/README.ko.md +18 -0
  7. package/plugins/context-guard/README.md +18 -0
  8. package/plugins/context-guard/bin/context-guard-artifact +90 -9
  9. package/plugins/context-guard/bin/context-guard-audit +169 -66
  10. package/plugins/context-guard/bin/context-guard-bench +5765 -224
  11. package/plugins/context-guard/bin/context-guard-compress +90 -8
  12. package/plugins/context-guard/bin/context-guard-diet +1 -7
  13. package/plugins/context-guard/bin/context-guard-experiments +5 -1
  14. package/plugins/context-guard/bin/context-guard-failed-nudge +705 -83
  15. package/plugins/context-guard/bin/context-guard-guard-read +490 -55
  16. package/plugins/context-guard/bin/context-guard-pack +110 -11
  17. package/plugins/context-guard/bin/context-guard-read-symbol +7 -2
  18. package/plugins/context-guard/bin/context-guard-rewrite-bash +2204 -223
  19. package/plugins/context-guard/bin/context-guard-sanitize-output +560 -85
  20. package/plugins/context-guard/bin/context-guard-setup +1073 -147
  21. package/plugins/context-guard/bin/context-guard-statusline +131 -54
  22. package/plugins/context-guard/bin/context-guard-statusline-merged +7 -3
  23. package/plugins/context-guard/bin/context-guard-tool-prune +44 -11
  24. package/plugins/context-guard/bin/context-guard-trim-output +89 -13
  25. package/plugins/context-guard/brief/README.md +19 -0
  26. package/plugins/context-guard/brief/narration-mode.quiet.md +21 -0
  27. package/plugins/context-guard/lib/context_guard_commands.py +6 -2
  28. package/plugins/context-guard/lib/credential_policy.py +177 -0
  29. package/plugins/context-guard/lib/transcript_usage_reducer.py +378 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes for the ContextGuard plugin are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.4.16] - 2026-08-01
8
+
9
+ - Sanitizer output-scanning is now one monotonic left-to-right pass. All nine location-prefix consumers previously re-parsed the same optional `path:line:` fragment at every offset, which made lines with few colons quadratic: a single 82,015-byte line took 2.62 s and a 100,014-byte line took 3.98 s. The leading prefix is identified once and the seven unanchored consumers run as fragment-free twins, while the two `^`-anchored header consumers keep their original patterns because they were never a per-offset cost. The same lines now take 64.3 ms and 0.078 s, with doubling ratios of 1.84 to 2.01. Redaction output is byte-identical to the previous implementation, pinned by a differential oracle against a hash-frozen baseline across eleven corpora, three sanitization contexts, both path-display modes, and both shared-state and per-line runs.
10
+ - `context-guard-trim-output --digest` no longer inflates small output. When a command succeeds and its output is already smaller than the digest would be, the output is passed through with a one-line marker; a 19-byte output previously produced a 461-byte markdown digest or a 755-byte JSON digest. A failing command always keeps the digest, because that is where the exit code and failure signature live, and requesting `--artifact-receipt` always keeps it too. Pass `--digest-always` to force the structured digest in every case.
11
+ - Both READMEs now document the standing per-request cost of the advisory rule blocks (`brief-mode.lite` 1,487 bytes, `brief-mode.standard` 1,568, `brief-mode.ultra` 1,523, `narration-mode.quiet` 866) and frame that size as the break-even threshold a reply-length reduction has to clear. Hook guardrails are contrasted honestly: they charge only when they act, and a sub-threshold `Read` adds 3 bytes.
12
+ - Added a default-off, Claude-only quiet-narration rule managed through a dedicated rules-only setup path. It suppresses discretionary narration while preserving approvals, blockers, failures, safety warnings, final results, changed files, and verification; it does not activate settings or hooks or claim guaranteed savings.
13
+ - Benchmark measurement substrate for the planned token-savings study: scheduling, accounting, resume, and inference surfaces plus a real twelve-task fixture suite with out-of-workspace success checkers and a zero-cost seventy-two-run fake-provider rehearsal. These are measurement-enabling only. No provider-measured token or cost savings are claimed, and the rehearsal explicitly records that its own token counts are scripted local fixtures.
14
+
7
15
  ## [0.4.15] - 2026-07-15
8
16
 
9
17
  - Added conservative plan-only and evaluation-only proof-carrying-context, semantic-GC, and image-context-pack surfaces without enabling automatic omission, renderer/OCR/provider/proxy execution, promotion authority, or hosted savings claims.
package/README.ko.md CHANGED
@@ -95,6 +95,33 @@ brief 모드는 코딩 에이전트가 군더더기를 줄이도록 요청하되
95
95
 
96
96
  사전 정의된 세 레벨이 [`plugins/context-guard/brief/`](plugins/context-guard/brief/)에 포함됩니다: `lite`, `standard`, `ultra`. 각 레벨은 에이전트 규칙·지시 파일(`AGENTS.md`, `CLAUDE.md`, Cursor 규칙 파일, Copilot 지시 등)에 들어가는 마커 구분 블록입니다. `context-guard setup --agent codex --scope project --brief-mode standard --plan`으로 미리 보고, 적용은 `--yes`로 다시 실행하며, 제거는 `--brief-mode off`를 사용하세요. 자세한 내용은 [`plugins/context-guard/brief/README.md`](plugins/context-guard/brief/README.md)를 참고하세요.
97
97
 
98
+ ### 상시 비용과 손익분기
99
+
100
+ 안내용 규칙 블록은 공짜가 아닙니다. 에이전트의 규칙 파일에 상주하므로 설치해 둔 동안 모든 요청에 다시 실려 갑니다.
101
+
102
+ | 관리 블록 | 설치 크기 |
103
+ | --- | --- |
104
+ | `brief-mode.lite` | 1,487 바이트 |
105
+ | `brief-mode.standard` | 1,568 바이트 |
106
+ | `brief-mode.ultra` | 1,523 바이트 |
107
+ | `narration-mode.quiet` | 866 바이트 |
108
+
109
+ 이 비용은 요청마다 선불로 확정되는 반면, 이득은 ContextGuard가 강제할 수 없는 확률적인 응답 길이 감소입니다. 턴 수가 적은 세션이나 이미 간결하게 답하는 에이전트에서는 블록이 절감분보다 더 들 수 있습니다. 훅 기반 가드레일은 성질이 다릅니다. 실제로 개입할 때만 비용이 들고, 측정된 최악의 경우도 작습니다. 임계값 이하 `Read`는 3바이트를 더하고, 대용량 읽기를 반복 시도해도 첫 경고 이후에는 누적되지 않고 오히려 줄어듭니다.
110
+
111
+ 토큰 효과를 노려 규칙 블록을 설치하기 전에 직접 측정하십시오. `context-guard-bench`로 블록이 있는 경우와 없는 경우를 동일 과제에서 비교하고, 블록의 설치 크기를 응답 길이 감소가 넘어야 하는 손익분기점으로 취급하십시오. 여기 적힌 바이트 수는 관측값이지만 토큰 효과는 관측값이 아니며, 고정된 절감률을 보장하지 않습니다.
112
+
113
+ ## Claude 조용한 진행 설명 (안내용)
114
+
115
+ 조용한 진행 설명은 기본적으로 꺼져 있는 별도의 Claude 전용 규칙입니다. 선택적 사전 설명, 도구별 진행 중계, 군더더기, 반복 중간 요약은 줄이지만 승인·결정 요청, 차단 요인, 실패, 파괴적 작업·보안 경고, 상위 우선순위가 요구하는 진행 보고, 최종 결과, 변경 파일, 검증 결과는 유지합니다. 최종 답변의 간결성이나 추론 깊이와는 별개인 최선 노력 규칙이며, 토큰·비용 절감을 **보장하지 않습니다.**
116
+
117
+ ```bash
118
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --plan
119
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --yes
120
+ context-guard setup --rules-only --agent claude --scope project --narration-mode default --yes
121
+ ```
122
+
123
+ 이 격리된 작업은 프로젝트 `CLAUDE.md` 안의 ContextGuard narration 구간만 관리합니다. Claude settings, hook, permission, statusline, model default 또는 다른 에이전트 규칙 파일은 읽거나 바꾸지 않습니다. brief mode, 초기화, skill 생성 또는 일반 setup 작업과 함께 사용할 수 없습니다. Gate C는 정적 규칙과 setup 부작용만 검증하며 모델의 준수나 수치 절감 주장을 증명하지 않습니다.
124
+
98
125
  ## 직접 측정하는 방법
99
126
 
100
127
  절감 수치가 필요하면 실제 작업에서 직접 측정하세요.
@@ -234,6 +261,19 @@ context-guard setup --agent claude --scope user --verify --json
234
261
 
235
262
  선택형 Read 가드는 큰 파일에 대해 검색 → 심볼 구간 → 작은 줄 범위 순서의 단계적 축소 전략을 제안합니다. 가능하면 제한된 최상위 개요도 함께 보여줍니다. 같은 대용량 파일을 반복해서 전체 읽으려 하면 중복 읽기 경고를 표시해 같은 컨텍스트 낭비 경로를 반복하지 않게 합니다.
236
263
 
264
+ 적용 범위는 의도적으로 Claude Code `PreToolUse`의 `Read` matcher 훅으로 한정됩니다.
265
+
266
+ 이 Read 가드를 선택하면 setup은 기존 deny 값 중 정확히 `Read(./.env)`와 `Read(./.env.*)`만 제거합니다. 비슷한 permission 항목과 그 상대적 순서는 유지합니다.
267
+
268
+ | Claude 도구 | 보호 범위 |
269
+ | --- | --- |
270
+ | `Read` | 제한된 대용량 파일 범위를 검사하고, basename이 `.env`로 시작하면 차단합니다. 단, 정확히 `.env.example`, `.env.sample`, `.env.template`인 템플릿 이름은 허용합니다. 중첩 경로도 포함하며 symlink 여부가 모호하면 닫힌 상태로 실패합니다. |
271
+ | `Glob` | 일치하는 이름을 나열할 수 있습니다. 이 `Read` 훅을 통해 파일 내용을 읽지는 않습니다. |
272
+ | `Grep` | 이 훅의 범위 밖이며 일치하는 파일 내용을 읽을 수 있습니다. |
273
+ | `Bash` | 이 훅의 범위 밖이며 파일 내용을 읽을 수 있습니다. |
274
+
275
+ 이는 Claude `Read` 보호이지 범용 `.env` 보호나 Bash 보호가 아닙니다. 훅은 symlink를 따라가지 않고 직접 연 파일 descriptor의 상태를 다시 검증하지만, 실제 Claude `Read`는 훅이 반환된 뒤 파일을 다시 엽니다. 그 사이 파일이 교체될 수 있는 post-hook 구간은 문서화된 TOCTOU 한계입니다.
276
+
237
277
  ### 큰 로그를 로컬에 저장하고 필요한 부분만 조회
238
278
 
239
279
  ```bash
@@ -332,7 +372,7 @@ cat sanitized-prose.txt | ./plugins/context-guard/bin/context-guard-compress --j
332
372
  ./plugins/context-guard/bin/context-guard-trim-output --max-lines 120 -- npm test
333
373
  ```
334
374
 
335
- head/tail 로그 대신 의미 요약이 필요하면 `--digest markdown` 또는 `--digest json`을 사용하세요. 요약 모드는 원래 종료 코드를 보존하면서 상태, 종료 코드, 잘린 줄 수, 실행기 실패 정보, 가림 처리된 실패 signature, 중복 라인 그룹, 대표 라인, 가림 처리 횟수, 다음 조회 제안을 남깁니다. 요약 모드에서 가림 처리된 전체 출력을 로컬 `context-guard-artifact` 보관본에 저장하려면 `--artifact-receipt`를 함께 사용하세요. 출력된 `contextguard-artifact:<id>` 핸들을 agent context에 남기고, 생략된 세부 내용에 의존하기 전에 `context-guard-artifact receipt/get/search ...` 명령으로 필요한 부분을 정확히 다시 가져오세요. 래핑된 명령은 기본 600초 뒤 종료되며, `--timeout-seconds`로 조정할 수 있습니다.
375
+ head/tail 로그 대신 의미 요약이 필요하면 `--digest markdown` 또는 `--digest json`을 사용하세요. 명령이 성공하고 출력이 이미 요약보다 작으면 요약 대신 원래 출력을 한 줄 표식과 함께 그대로 통과시키므로, 출력이 적은 명령에 요약 모드를 켜도 컨텍스트가 늘어나지 않습니다. 실패한 명령은 종료 코드와 실패 signature가 요약에 담기므로 항상 요약을 유지합니다. 모든 경우에 구조화된 요약을 유지하려면 `--digest-always`를 전달하세요. 요약 모드는 원래 종료 코드를 보존하면서 상태, 종료 코드, 잘린 줄 수, 실행기 실패 정보, 가림 처리된 실패 signature, 중복 라인 그룹, 대표 라인, 가림 처리 횟수, 다음 조회 제안을 남깁니다. 요약 모드에서 가림 처리된 전체 출력을 로컬 `context-guard-artifact` 보관본에 저장하려면 `--artifact-receipt`를 함께 사용하세요. 출력된 `contextguard-artifact:<id>` 핸들을 agent context에 남기고, 생략된 세부 내용에 의존하기 전에 `context-guard-artifact receipt/get/search ...` 명령으로 필요한 부분을 정확히 다시 가져오세요. 래핑된 명령은 기본 600초 뒤 종료되며, `--timeout-seconds`로 조정할 수 있습니다.
336
376
 
337
377
  ### 검색·diff 출력 민감정보 가림
338
378
 
@@ -375,6 +415,11 @@ JSON 출력에는 여러 증거 surface가 포함될 수 있습니다.
375
415
  --ledger-jsonl bench/cost-shift.jsonl --report-json bench/report.json
376
416
  ```
377
417
 
418
+ 각 task fixture의 선택 필드 `output_format`은 기본값이 `json`이며 `json|stream-json`만
419
+ 허용합니다. `stream-json` 모드는 runner가 관리하는 `--verbose`를 추가하고 bounded NDJSON의
420
+ 마지막 event가 유효한 terminal result일 때만 성공으로 처리합니다. 이 경로의 client cost도
421
+ provider billing을 authoritative하게 증명하지 않는 진단값입니다.
422
+
378
423
  보고서를 읽을 때는 먼저 주장 범위를 확인하세요.
379
424
 
380
425
  - 성공한 기준/변형 실행은 실제 토큰과 `cost_usd + external_cost_usd` 기준으로 비교하고, 바이트 감소는 간접 증거로만 기록합니다.
package/README.md CHANGED
@@ -80,7 +80,7 @@ ContextGuard complements provider and semantic caches, and works alongside promp
80
80
  | Provider prompt/context caching | Reusing stable prompt prefixes. | Complementary; ContextGuard helps keep the changing tail of context smaller and cleaner, `context-guard-audit` can flag likely volatile prefix layouts, and `context-guard cost` can warn when an Anthropic request is likely to cache-write instead of cache-read. |
81
81
  | Semantic response cache | Reusing answers to identical or similar requests. | Complementary; ContextGuard does not serve cached AI answers. |
82
82
  | Prompt/context compression | Shortening text that is already selected for the model. | Adjacent; ContextGuard trims and summarizes local output, but does not promise lossless semantic compression. |
83
- | Experimental planners and local runtimes | Default-off and explicit-command-only; covers plan-only `image-context-pack` and `semantic-checkpoint` gates plus local-proxy plans/gate records and narrow local runtimes for caller-supplied context-diff, visual evidence-pack, learned-compression, and self-hosted metrics evidence. | `image-context-pack` and `semantic-checkpoint` are dry-run planning gates only: they do not emit replacements, call models/providers, proxy traffic, write files, or make hosted token/cost savings claims. `semantic-checkpoint` additionally requires exact context fallback/re-expand metadata, provenance review acknowledgement, provider-boundary acknowledgement, protected-zone denial, and missed-context notes before the JSON payload reports readiness. The local proxy `record` command starts no listener and forwards no traffic; `serve local-proxy` binds and forwards only literal loopback IPs for one bounded request; `--response-sandbox` can replace a safe UTF-8 upstream body with a compact local artifact rehydration envelope. Compressor/model execution, OCR/crop services, external forwarding, credential persistence, runtime checkpoint replacement, and hosted-savings claims stay out of scope until a separate evidence gate and future PR allow them. |
83
+ | Experimental planners and local runtimes | Default-off and explicit-command-only; covers plan-only `image-context-pack` and `semantic-checkpoint` gates plus local-proxy plans/gate records and narrow local runtimes for caller-supplied context-diff, visual evidence-pack, learned-compression, and self-hosted metrics evidence. | `image-context-pack` is a pxpipe-inspired dry-run planning gate only: it does not render images, run OCR, emit replacements, store binary image artifacts, call providers, or proxy traffic, and it requires exact text fallback plus provider-measured matched-task evidence before claims. `semantic-checkpoint` likewise emits planning metadata only, writes no files or replacement context, and requires exact fallback/re-expand metadata, provenance review acknowledgement, provider-boundary acknowledgement, protected-zone denial, and missed-context notes before reporting readiness. The local proxy `record` command starts no listener and forwards no traffic; `serve local-proxy` binds and forwards only literal loopback IPs for one bounded request; `--response-sandbox` can replace a safe UTF-8 upstream body with a compact local artifact rehydration envelope. Compressor/model execution, OCR/crop services, external forwarding, credential persistence, runtime checkpoint replacement, and hosted-savings claims stay out of scope until a separate evidence gate and future PR allow them. |
84
84
  | ContextGuard | Avoiding unnecessary files, logs, repeated failures, and noisy output before they enter agent context. | Local guardrails, reversible artifacts, and measurement. |
85
85
 
86
86
  Related patterns that informed the design:
@@ -97,6 +97,44 @@ Brief mode is a set of agent-neutral, advisory rule snippets that ask a coding a
97
97
 
98
98
  Three deterministic levels ship under [`plugins/context-guard/brief/`](plugins/context-guard/brief/): `lite`, `standard`, and `ultra`. Each level is a single marker-delimited block for an agent's rule/instruction file (for example `AGENTS.md`, `CLAUDE.md`, a Cursor rules file, or Copilot instructions). Manage it through setup with `context-guard setup --agent codex --scope project --brief-mode standard --plan`, rerun with `--yes` to apply, and use `--brief-mode off` to remove the managed block. See [`plugins/context-guard/brief/README.md`](plugins/context-guard/brief/README.md).
99
99
 
100
+ ### Standing cost and break-even
101
+
102
+ Advisory rule blocks are not free. They live in an agent's rule file, so they are
103
+ re-sent with every request for as long as they are installed:
104
+
105
+ | Managed block | Installed size |
106
+ | --- | --- |
107
+ | `brief-mode.lite` | 1,487 bytes |
108
+ | `brief-mode.standard` | 1,568 bytes |
109
+ | `brief-mode.ultra` | 1,523 bytes |
110
+ | `narration-mode.quiet` | 866 bytes |
111
+
112
+ That is a fixed per-request cost paid up front, while the benefit is a
113
+ probabilistic reduction in reply length that ContextGuard cannot enforce. On a
114
+ session with few turns, or with an agent that already answers tersely, the block
115
+ can cost more than it saves. The hook-based guardrails behave differently: they
116
+ charge only when they act, and the measured worst cases stay small — a
117
+ sub-threshold `Read` adds 3 bytes, and repeated large-read attempts shrink after
118
+ the first warning instead of accumulating.
119
+
120
+ Before installing a rule block for its token effect, measure it. Use
121
+ `context-guard-bench` on matched tasks with and without the block, and treat the
122
+ block's installed size as the break-even threshold your reply-length reduction has
123
+ to clear. Byte counts here are observed; token effects are not, and no fixed
124
+ saving is claimed.
125
+
126
+ ## Quiet narration for Claude (advisory)
127
+
128
+ Quiet narration is a separate, default-off Claude-only rule for reducing discretionary preambles, per-tool narration, filler, and repeated interim summaries. It still requires approvals and decisions, blockers, failures, destructive or security warnings, higher-priority progress updates, the final result, changed files, and verification. It is best-effort guidance, independent of final-answer brevity or reasoning depth, and does **not** guarantee token or cost savings.
129
+
130
+ ```bash
131
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --plan
132
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --yes
133
+ context-guard setup --rules-only --agent claude --scope project --narration-mode default --yes
134
+ ```
135
+
136
+ This isolated operation manages only ContextGuard's narration span in the project's `CLAUDE.md`. It does not read or change Claude settings, hooks, permissions, statusline, model defaults, or other agents' rule files. It cannot be combined with brief mode, initialization, skill generation, or normal setup actions. Gate C verifies the static rule and setup side effects only; it does not prove model compliance or authorize a numeric savings claim.
137
+
100
138
  ## What to measure
101
139
 
102
140
  If you need a savings claim, measure it on your own tasks:
@@ -247,6 +285,19 @@ The structural-waste doctor is opt-in and read-only. It reuses the diet scanner'
247
285
 
248
286
  The optional Read guard uses a progressive path for oversized files: search first, then symbol slices, then small line ranges. When possible, it also returns a bounded top-level outline. Repeated attempts to full-read the same oversized file get a deduplicated warning instead of repeating the same context-heavy path.
249
287
 
288
+ Its enforcement surface is deliberately limited to the installed Claude Code `PreToolUse` hook whose matcher is `Read`:
289
+
290
+ When that Read guard is selected, setup removes only the exact legacy deny values `Read(./.env)` and `Read(./.env.*)`; similar permission entries and their relative order are preserved.
291
+
292
+ | Claude tool | Covered behavior |
293
+ | --- | --- |
294
+ | `Read` | The hook checks bounded large-file ranges and denies a basename beginning with `.env`, except the exact template names `.env.example`, `.env.sample`, and `.env.template`. Nested paths are included; ambiguous symlink paths fail closed. |
295
+ | `Glob` | May list matching names. It does not read file contents through this `Read` hook. |
296
+ | `Grep` | Out of scope for this hook and may read matching file contents. |
297
+ | `Bash` | Out of scope for this hook and may read file contents. |
298
+
299
+ This is Claude `Read` protection, not universal `.env` or Bash protection. The hook proves the file state it opens without following symlinks and revalidates that same descriptor, but Claude performs the actual `Read` with a later open after the hook returns. A replacement in that post-hook window is a documented TOCTOU limitation.
300
+
250
301
  ### Store and query large logs locally
251
302
 
252
303
  ```bash
@@ -354,7 +405,7 @@ Add `--mode readable` only for sanitized prose previews. It uses a deterministic
354
405
  ./plugins/context-guard/bin/context-guard-trim-output --max-lines 120 -- npm test
355
406
  ```
356
407
 
357
- Use `--digest markdown` or `--digest json` for a compact semantic digest instead of head/tail logs. Digest mode keeps status, exit code, truncation counts, runner failure facts, a sanitized failure signature, duplicate-line groups, representative lines, redaction counts, and suggested next queries while preserving the wrapped command exit code. Add `--artifact-receipt` with digest mode when you want the exact sanitized full output stored locally as a `context-guard-artifact` receipt; keep the emitted `contextguard-artifact:<id>` handle in agent context and re-expand with the emitted `context-guard-artifact receipt/get/search ...` commands before relying on omitted details. Wrapped commands time out after 600 seconds by default; tune this with `--timeout-seconds`.
408
+ Use `--digest markdown` or `--digest json` for a compact semantic digest instead of head/tail logs. When a command succeeds and its output is already smaller than the digest would be, the output is passed through with a one-line marker instead, so enabling digest mode on quiet commands cannot inflate context. A failing command always keeps the digest, because that is where the exit code and failure signature live; pass `--digest-always` to keep the structured digest in every case. Digest mode keeps status, exit code, truncation counts, runner failure facts, a sanitized failure signature, duplicate-line groups, representative lines, redaction counts, and suggested next queries while preserving the wrapped command exit code. Add `--artifact-receipt` with digest mode when you want the exact sanitized full output stored locally as a `context-guard-artifact` receipt; keep the emitted `contextguard-artifact:<id>` handle in agent context and re-expand with the emitted `context-guard-artifact receipt/get/search ...` commands before relying on omitted details. Wrapped commands time out after 600 seconds by default; tune this with `--timeout-seconds`.
358
409
 
359
410
  ### Sanitize search and diff output
360
411
 
@@ -420,6 +471,11 @@ These fields can flag likely volatile content near the prompt prefix, stable-pre
420
471
  --dashboard-md bench/dashboard.md
421
472
  ```
422
473
 
474
+ Each task fixture may set `output_format` to `json` (the default) or opt in to
475
+ `stream-json`. Stream mode adds the runner-controlled `--verbose` flag and only
476
+ accepts a bounded NDJSON stream whose final event is a valid terminal result.
477
+ Its client-reported cost remains diagnostic and is not authoritative provider billing.
478
+
423
479
  For deterministic local replay before a live provider run, add `--evidence-jsonl docs/benchmark-fixtures/token-savings-12task.evidence.example.jsonl` and, for the 12-task fixture, `--baseline-variant baseline_full_context_fixture`. Replay mode skips provider and `success_command` execution, writes the same CSV/report/dashboard surfaces, and marks synthetic/manual evidence as non-public-claim-eligible.
424
480
 
425
481
  Read the report through its claim boundaries before writing any savings statement:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ictechgy/context-guard",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "ContextGuard CLI helpers for keeping AI coding agent context focused and local-first.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/ictechgy/context-guard#readme",
@@ -37,5 +37,5 @@
37
37
  "gated-experiments",
38
38
  "future-roadmap"
39
39
  ],
40
- "version": "0.4.15"
40
+ "version": "0.4.16"
41
41
  }
@@ -110,6 +110,12 @@ context-guard-statusline-merged
110
110
  - **반복 실패 알림**은 Bash 실패가 반복될 때 같은 경로를 계속 재시도하지 않고 전략을 바꾸도록 안내합니다.
111
111
  - **벤치마크 헬퍼**는 기준/변형 실행을 대응해 실제 토큰·비용 필드, 별도의 바이트 감소 간접 증거, 진단용 `wall_time_seconds`, `provider_cached_tokens`, provider-cache 사용 가능성 텔레메트리, report-level measurement-baseline contract, 파일 기반 `variant_prompt_files`, 선택적 run별 `self_hosted_metrics` JSONL ledger sidecar를 기록합니다. 이 sidecar는 hosted API 절감 주장에 합치지 않습니다.
112
112
 
113
+ ### Claude Read의 정확한 적용 범위
114
+
115
+ 설치되는 가드는 Claude Code `PreToolUse`의 `Read` matcher 훅입니다. 이 가드를 선택하면 setup은 기존 deny 값 중 정확히 `Read(./.env)`와 `Read(./.env.*)`만 제거하고, 비슷한 항목과 상대적 순서는 유지합니다. 훅은 제한된 대용량 파일 범위를 검사하고, root 또는 중첩 경로의 basename이 `.env`로 시작하면 차단합니다. 단, 정확히 `.env.example`, `.env.sample`, `.env.template`인 템플릿 이름은 허용하며 symlink 여부가 모호하면 차단합니다. `Glob`은 이름을 나열할 수 있습니다. `Grep`과 `Bash`는 파일 내용을 읽을 수 있고 이 훅의 범위 밖입니다. 이는 범용 `.env` 보호나 Bash 보호가 아닙니다.
116
+
117
+ 훅은 symlink를 따라가지 않고 파일을 열어 같은 descriptor의 identity, size, modification time을 다시 검증합니다. 실제 Claude `Read`는 훅 반환 뒤 별도로 파일을 열기 때문에, 그 post-hook 구간의 파일 교체 가능성은 문서화된 TOCTOU 한계로 남습니다.
118
+
113
119
  비용 가드의 로컬 HMAC 키는 기본적으로 `.context-guard/cost-ledger/hmac.key`에 자동 생성됩니다. 관리자가 직접 주입하는 경우 파일에는 필수 padding을 포함한 canonical URL-safe base64 32바이트 키만 정확히 들어 있어야 하며, trailing newline이나 공백은 허용하지 않습니다. 리포트는 키와 원문 프롬프트를 출력하지 않고, 로컬 ledger는 Anthropic/provider prompt cache를 대체하지 않습니다.
114
120
 
115
121
  ## brief 모드 (안내용)
@@ -118,6 +124,18 @@ brief 모드는 코딩 에이전트가 군더더기를 줄이도록 요청하되
118
124
 
119
125
  세 가지 고정 레벨(`lite`, `standard`, `ultra`)이 [`brief/`](brief/)에 있습니다. 각 레벨은 에이전트 규칙·지시 파일(`AGENTS.md`, `CLAUDE.md`, Cursor 규칙 파일, Copilot 지시 등)에 들어가는 마커 구분 블록입니다. `context-guard setup --agent codex --scope project --brief-mode standard --plan`으로 미리 보고, `--yes`로 적용하며, 제거는 `--brief-mode off`를 사용하세요. 자세한 내용은 [`brief/README.md`](brief/README.md)를 참고하세요.
120
126
 
127
+ ## Claude 조용한 진행 설명 (안내용)
128
+
129
+ 조용한 진행 설명은 기본적으로 꺼져 있는 별도의 Claude 전용 규칙입니다. 선택적 사전 설명, 도구별 진행 중계, 군더기, 반복 중간 요약은 줄이되 승인·결정, 차단 요인, 실패, 파괴적 작업·보안 경고, 필수 진행 보고, 최종 결과, 변경 파일, 검증 결과는 유지합니다.
130
+
131
+ ```bash
132
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --plan
133
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --yes
134
+ context-guard setup --rules-only --agent claude --scope project --narration-mode default --yes
135
+ ```
136
+
137
+ 이 격리된 작업은 프로젝트 `CLAUDE.md`의 ContextGuard narration 구간만 관리하며 settings, hook 또는 다른 에이전트 파일을 읽거나 바꾸지 않고 일반 setup 작업과 함께 사용할 수 없습니다. 최종 답변의 간결성·추론 깊이와 별개인 최선 노력 규칙이며, Gate C는 모델 준수나 절감 효과를 주장하지 않습니다.
138
+
121
139
  ## 절감 수치를 과장하지 않습니다
122
140
 
123
141
  이 헬퍼들은 흔히 컨텍스트를 불필요하게 키우는 원인을 줄이지만, 고정된 절감률을 보장하지 않습니다. 실제 전후 비교 증거가 필요하면 `context-guard-bench --ledger-jsonl ... --report-json ... --dashboard-md ...`로 본인 작업에서 측정하세요. `--evidence-jsonl ...`는 deterministic local replay용이며 provider-export provenance가 완전하지 않으면 public claim 불가로 표시됩니다. 토큰 절감 주장은 대응 태스크 양쪽 모두에 `primary_tokens_measured`가 있을 때만 계산하며, report의 `matched_pair_evidence`가 성공한 baseline/variant task bucket을 transform, quality gate, 측정 가능 여부, claim boundary와 연결합니다. report의 `default_matrix`는 같은 evidence에서 trimming, artifact escrow, tool pruning, cache advice, adaptive-k, optional compression을 `default-on`/`advisory`/`experimental`/`reject/rework`로 분류하지만 report 전용이며 runtime default나 hosted savings claim을 바꾸지 않습니다. report의 `public_claim_readiness`는 release/public claim의 최종 gate로, matched successful task, provider-measured primary token/cost, quality non-inferiority, shifted-cost accounting, 명시적 confidence/failure note, complete provider-export provenance가 모두 통과해야 `claim_allowed=true`가 되며 그 외 hosted savings claim은 금지됩니다. wall-time과 provider-cache 필드는 진단용 텔레메트리이지 단독 절감 증거가 아닙니다. 감사의 `cache_friendliness`, [`cache_diagnostics`](https://github.com/ictechgy/context-guard/blob/main/docs/cache-diagnostics-schema.md), `cache_layout_advice`는 관측/추론/가설/불가 경계를 둔 휴리스틱 배치·cache-read 신호와 순위화된 확인/실험이며 청구 기준이나 provider-cache 증명이 아닙니다. 벤치마크 CSV 스키마는 엄격하므로 헬퍼 업그레이드 후에는 새 CSV를 시작하거나 헤더를 마이그레이션하세요. 작업 유형별 합성 예시는 [`docs/benchmark-workflow-examples.md`](https://github.com/ictechgy/context-guard/blob/main/docs/benchmark-workflow-examples.md)에 있고, fixture-only 실험 시작 예시는 [`docs/experimental-benchmark-fixtures.md`](https://github.com/ictechgy/context-guard/blob/main/docs/experimental-benchmark-fixtures.md)에 있습니다.
@@ -119,6 +119,12 @@ Opt-in `build`/`auto --sketch-duplicate-veto` applies a rank-stable pre-budget g
119
119
  - **Repeated-failure nudge** warns after repeated Bash failures so the agent switches strategy instead of retrying the same context-heavy path.
120
120
  - **Benchmark helper** records matched baseline/variant runs with real token and cost fields, separate byte-reduction proxy evidence, diagnostic `wall_time_seconds`, `provider_cached_tokens`, provider-cache availability telemetry, a report-level measurement-baseline contract, file-backed `variant_prompt_files`, and optional per-run `self_hosted_metrics` JSONL ledger sidecars that stay out of hosted API savings claims.
121
121
 
122
+ ### Exact Claude Read surface
123
+
124
+ The installed guard is a Claude Code `PreToolUse` hook with matcher `Read`. When selected, setup removes only the exact legacy deny values `Read(./.env)` and `Read(./.env.*)` while preserving similar entries and their relative order. The hook checks bounded large-file ranges and denies root or nested paths whose basename begins `.env`, except the exact template names `.env.example`, `.env.sample`, and `.env.template`; ambiguous symlink paths fail closed. `Glob` can still list names. `Grep` and `Bash` can read file contents and are outside this hook. This is not universal `.env` or Bash protection.
125
+
126
+ The hook opens without following symlinks and revalidates identity, size, and modification time on that descriptor. Claude performs the actual `Read` with a separate open after the hook returns, so replacement during that post-hook window remains a documented TOCTOU limitation.
127
+
122
128
  Cost guard creates its local HMAC key automatically at `.context-guard/cost-ledger/hmac.key`. If you provision that file yourself, it must contain exactly one canonical URL-safe base64 32-byte key with required padding and no trailing newline or whitespace. Reports never emit the key or raw prompt text, and the local ledger does not replace Anthropic/provider prompt caching.
123
129
 
124
130
  ## Brief mode (advisory)
@@ -127,6 +133,18 @@ Brief mode ships agent-neutral, advisory rule snippets that ask a coding agent t
127
133
 
128
134
  Three deterministic levels — `lite`, `standard`, `ultra` — live under [`brief/`](brief/). Each is a single marker-delimited block for an agent's rule/instruction file (such as `AGENTS.md`, `CLAUDE.md`, a Cursor rules file, or Copilot instructions). Use `context-guard setup --agent codex --scope project --brief-mode standard --plan`, apply with `--yes`, and remove with `--brief-mode off`. See [`brief/README.md`](brief/README.md).
129
135
 
136
+ ## Quiet narration for Claude (advisory)
137
+
138
+ Quiet narration is a separate, default-off Claude-only rule that suppresses discretionary preambles, per-tool narration, filler, and repeated interim summaries while preserving approvals and decisions, blockers, failures, destructive or security warnings, required progress, the final result, changed files, and verification.
139
+
140
+ ```bash
141
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --plan
142
+ context-guard setup --rules-only --agent claude --scope project --narration-mode quiet --yes
143
+ context-guard setup --rules-only --agent claude --scope project --narration-mode default --yes
144
+ ```
145
+
146
+ The isolated operation manages only ContextGuard's narration span in project `CLAUDE.md`; it does not read or change settings, hooks, or other agents' files and cannot be combined with normal setup actions. The rule is best-effort, independent of final-answer brevity or reasoning depth, and Gate C makes no model-compliance or savings claim.
147
+
130
148
  ## Conservative claims
131
149
 
132
150
  These helpers reduce common sources of context bloat, but they do not guarantee a fixed percentage savings. Use `context-guard-bench --ledger-jsonl ... --report-json ... --dashboard-md ...` when you need measured before/after evidence for your own tasks; add `--evidence-jsonl ...` only for deterministic local replay that remains non-claim-eligible unless provider-export provenance is complete; token-savings claims require `primary_tokens_measured` on both matched sides, and the report's `matched_pair_evidence` links each successful baseline/variant task bucket to the transform, quality gate, measurement availability, and claim boundary. The report's `default_matrix` classifies trimming, artifact escrow, tool pruning, cache advice, adaptive-k, and optional compression as `default-on`, `advisory`, `experimental`, or `reject/rework` from that evidence, but it is reporting-only and does not change runtime defaults or authorize hosted savings claims. The report's `public_claim_readiness` is the authoritative release/public-claim gate: matched successful tasks, provider-measured primary tokens/cost, quality non-inferiority, shifted-cost accounting, explicit confidence/failure notes, and complete provider-export provenance must all pass before `claim_allowed=true`; unsupported hosted savings claims are forbidden otherwise. Wall-time/provider-cache fields are diagnostic telemetry, not standalone savings proof. Audit `cache_friendliness`, [`cache_diagnostics`](https://github.com/ictechgy/context-guard/blob/main/docs/cache-diagnostics-schema.md), and `cache_layout_advice` findings are heuristic layout/cache-read signals and ranked checks/experiments with observed/inferred/hypothesis/unavailable boundaries, not billing authority or provider-cache proof. Benchmark CSV schemas are strict, so start a new CSV or migrate the header after helper upgrades. Workflow-specific synthetic examples live in [`docs/benchmark-workflow-examples.md`](https://github.com/ictechgy/context-guard/blob/main/docs/benchmark-workflow-examples.md), and fixture-only experimental task/variant starters live in [`docs/experimental-benchmark-fixtures.md`](https://github.com/ictechgy/context-guard/blob/main/docs/experimental-benchmark-fixtures.md).
@@ -144,8 +144,14 @@ def compact_items(lines: Iterable[str], *, limit: int, max_chars: int = MAX_LINE
144
144
 
145
145
 
146
146
  class FallbackLineSanitizer:
147
- def __init__(self, *, show_paths: bool = False) -> None:
147
+ def __init__(
148
+ self,
149
+ *,
150
+ show_paths: bool = False,
151
+ context: str = "unknown_text",
152
+ ) -> None:
148
153
  self.show_paths = show_paths
154
+ self.context = context
149
155
  self.redactions = 0
150
156
 
151
157
  def sanitize(self, raw_line: str) -> tuple[str, bool]:
@@ -161,7 +167,32 @@ class FallbackLineSanitizer:
161
167
  return line, bool(count)
162
168
 
163
169
 
164
- def load_line_sanitizer(show_paths: bool) -> object:
170
+ def instantiate_line_sanitizer(
171
+ factory: object,
172
+ *,
173
+ show_paths: bool,
174
+ context: str,
175
+ private_roots: tuple[str, ...] = (),
176
+ ) -> object:
177
+ try:
178
+ return factory( # type: ignore[operator]
179
+ show_paths=show_paths,
180
+ context=context,
181
+ private_roots=private_roots,
182
+ )
183
+ except TypeError:
184
+ if context != "unknown_text" or private_roots:
185
+ raise RuntimeError(
186
+ "adjacent sanitizer does not support required explicit context"
187
+ )
188
+ return factory(show_paths=show_paths) # type: ignore[operator]
189
+
190
+
191
+ def load_line_sanitizer(
192
+ show_paths: bool,
193
+ context: str = "unknown_text",
194
+ private_roots: tuple[str, ...] = (),
195
+ ) -> object:
165
196
  script_dir = Path(__file__).resolve().parent
166
197
  for name in ("sanitize_output.py", "context-guard-sanitize-output", "claude-sanitize-output"):
167
198
  candidate = script_dir / name
@@ -173,15 +204,35 @@ def load_line_sanitizer(show_paths: bool) -> object:
173
204
  if spec is None:
174
205
  raise RuntimeError("import spec unavailable")
175
206
  module = importlib.util.module_from_spec(spec)
176
- loader.exec_module(module)
177
- return module.LineSanitizer(show_paths=show_paths)
207
+ sys.modules[loader.name] = module
208
+ try:
209
+ loader.exec_module(module)
210
+ except Exception:
211
+ sys.modules.pop(loader.name, None)
212
+ raise
213
+ return instantiate_line_sanitizer(
214
+ module.LineSanitizer,
215
+ show_paths=show_paths,
216
+ context=context,
217
+ private_roots=private_roots,
218
+ )
178
219
  except Exception as exc:
179
220
  raise RuntimeError(f"could not load sanitizer {candidate}: {exc}") from exc
180
- return FallbackLineSanitizer(show_paths=show_paths)
221
+ return FallbackLineSanitizer(show_paths=show_paths, context=context)
181
222
 
182
223
 
183
- def sanitize_text(text: str, *, show_paths: bool = False) -> tuple[str, int]:
184
- sanitizer = load_line_sanitizer(show_paths)
224
+ def sanitize_text(
225
+ text: str,
226
+ *,
227
+ show_paths: bool = False,
228
+ context: str = "unknown_text",
229
+ private_roots: tuple[str, ...] = (),
230
+ ) -> tuple[str, int]:
231
+ sanitizer = load_line_sanitizer(
232
+ show_paths,
233
+ context=context,
234
+ private_roots=private_roots,
235
+ )
185
236
  redacted = 0
186
237
  out: list[str] = []
187
238
  for line in text.splitlines(True):
@@ -193,7 +244,11 @@ def sanitize_text(text: str, *, show_paths: bool = False) -> tuple[str, int]:
193
244
 
194
245
 
195
246
  def sanitize_one_line(text: str, *, show_paths: bool = False) -> str:
196
- sanitized, _ = sanitize_text(text + "\n", show_paths=show_paths)
247
+ sanitized, _ = sanitize_text(
248
+ text + "\n",
249
+ show_paths=show_paths,
250
+ context="unknown_text",
251
+ )
197
252
  return cap_utf8_bytes(cap_line(" ".join(sanitized.strip().split())), MAX_COMMAND_PREVIEW_BYTES)
198
253
 
199
254
 
@@ -1103,7 +1158,12 @@ def store_command(args: argparse.Namespace) -> int:
1103
1158
  directory = normalize_allowed_first_absolute_symlink(Path(args.dir).expanduser())
1104
1159
  max_bytes = bounded_int(args.max_bytes, DEFAULT_MAX_BYTES, 1, MAX_MAX_BYTES)
1105
1160
  raw_text, input_truncated, input_bytes = read_bounded_stdin(max_bytes)
1106
- sanitized_text, redacted_lines = sanitize_text(raw_text, show_paths=args.show_paths)
1161
+ sanitized_text, redacted_lines = sanitize_text(
1162
+ raw_text,
1163
+ show_paths=args.show_paths,
1164
+ context=args.sanitize_context,
1165
+ private_roots=tuple(args.private_root),
1166
+ )
1107
1167
  content_bytes = len(sanitized_text.encode("utf-8", errors="replace"))
1108
1168
  content_sha = hashlib.sha256(sanitized_text.encode("utf-8", errors="replace")).hexdigest()
1109
1169
  command_preview = sanitize_one_line(args.command or "", show_paths=args.show_paths) if args.command else None
@@ -1125,6 +1185,10 @@ def store_command(args: argparse.Namespace) -> int:
1125
1185
  "created_at": int(time.time()),
1126
1186
  "command_preview": command_preview,
1127
1187
  "content_type": content_type,
1188
+ "sanitization": {
1189
+ "context": args.sanitize_context,
1190
+ "redacted_lines": redacted_lines,
1191
+ },
1128
1192
  "input": {
1129
1193
  "bytes_read": input_bytes,
1130
1194
  "truncated": input_truncated,
@@ -1682,6 +1746,23 @@ def build_parser() -> argparse.ArgumentParser:
1682
1746
  action="store_true",
1683
1747
  help="show raw absolute paths instead of path hashes; local debugging only because private paths may be exposed",
1684
1748
  )
1749
+ store.add_argument(
1750
+ "--sanitize-context",
1751
+ choices=(
1752
+ "unknown_text",
1753
+ "command_search_diff",
1754
+ "filesystem_listing",
1755
+ "source_code",
1756
+ ),
1757
+ default="unknown_text",
1758
+ help="persist the declared input origin used for sanitization",
1759
+ )
1760
+ store.add_argument(
1761
+ "--private-root",
1762
+ action="append",
1763
+ default=[],
1764
+ help="private root for filesystem_listing sanitization; may be repeated",
1765
+ )
1685
1766
  store.add_argument("--json", action="store_true", help="emit receipt JSON")
1686
1767
  store.set_defaults(func=store_command)
1687
1768