@softspark/ai-toolkit 4.16.0 → 4.17.0

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 (50) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +11 -16
  3. package/app/.claude-plugin/plugin.json +1 -1
  4. package/app/hooks/session-end.sh +1 -13
  5. package/app/hooks.json +0 -10
  6. package/benchmarks/ecosystem-doctor-snapshot.json +14 -15
  7. package/bin/ai-toolkit.js +0 -2
  8. package/kb/history/completed/output-filter-retirement-20260726.md +128 -0
  9. package/kb/reference/architecture-overview.md +2 -3
  10. package/kb/reference/cli-reference.md +3 -13
  11. package/kb/reference/enterprise-config-guide.md +1 -21
  12. package/kb/reference/hooks-catalog.md +3 -60
  13. package/kb/reference/supported-tools-registry.md +0 -4
  14. package/llms-full.txt +143 -395
  15. package/llms.txt +1 -1
  16. package/manifest.json +147 -36
  17. package/package.json +1 -2
  18. package/scripts/claude_app.py +2 -21
  19. package/scripts/config_cli.py +4 -0
  20. package/scripts/config_merger.py +0 -17
  21. package/scripts/config_validator.py +11 -138
  22. package/scripts/doctor.py +3 -20
  23. package/scripts/generate_copilot.py +35 -4
  24. package/scripts/install.py +7 -2
  25. package/scripts/install_steps/ai_tools.py +28 -99
  26. package/scripts/install_steps/hooks.py +26 -24
  27. package/scripts/merge-hooks.py +33 -2
  28. package/scripts/output_filter_retirement.py +395 -0
  29. package/scripts/schemas/ai-toolkit-config.schema.json +0 -60
  30. package/scripts/uninstall.py +13 -27
  31. package/app/hooks/filter-tool-output.sh +0 -76
  32. package/app/output-filter-policy.json +0 -15
  33. package/benchmarks/output-filter/README.md +0 -11
  34. package/benchmarks/output-filter/scenarios.json +0 -25
  35. package/kb/reference/tool-output-filter.md +0 -288
  36. package/scripts/benchmark_output_filter.py +0 -343
  37. package/scripts/output_filter_cli.py +0 -347
  38. package/scripts/output_filter_hook.py +0 -23
  39. package/scripts/tool_output_filter/__init__.py +0 -33
  40. package/scripts/tool_output_filter/contracts.py +0 -173
  41. package/scripts/tool_output_filter/engine.py +0 -260
  42. package/scripts/tool_output_filter/hook_runtime.py +0 -369
  43. package/scripts/tool_output_filter/input.py +0 -56
  44. package/scripts/tool_output_filter/invariants.py +0 -40
  45. package/scripts/tool_output_filter/policy.py +0 -153
  46. package/scripts/tool_output_filter/profiles/__init__.py +0 -68
  47. package/scripts/tool_output_filter/profiles/repeat_lines.py +0 -71
  48. package/scripts/tool_output_filter/profiles/tap_success.py +0 -154
  49. package/scripts/tool_output_filter/recovery.py +0 -846
  50. package/scripts/tool_output_filter/telemetry.py +0 -13
package/llms-full.txt CHANGED
@@ -19,6 +19,7 @@
19
19
  - [Spike: F2 MCP Context Trim — Hook Feasibility & Path Decision](kb/history/completed/f2-mcp-trim-spike-20260504.md)
20
20
  - [Implementation Plan: Native Tool Output Filter](kb/history/completed/native-tool-output-filter-plan.md)
21
21
  - [Plan: Offline-First SLM Profile — Lightweight Mode for Local Models](kb/history/completed/offline-slm-profile-plan-20260411.md)
22
+ - [Retirement: Native Tool-Output Filter](kb/history/completed/output-filter-retirement-20260726.md)
22
23
  - [Plan: Output & Token Discipline](kb/history/completed/output-token-discipline-plan-20260504.md)
23
24
  - [How-To Guides](kb/howto/README.md)
24
25
  - [Plan: Cloud Security Pack — Multi-Cloud Audit](kb/planning/cloud-security-pack-plan.md)
@@ -63,7 +64,6 @@
63
64
  - [Usage Statistics](kb/reference/stats.md)
64
65
  - [Supported Tools Registry](kb/reference/supported-tools-registry.md)
65
66
  - [Config Sync](kb/reference/sync.md)
66
- - [Native Tool Output Filter](kb/reference/tool-output-filter.md)
67
67
  - [Unique Features & Differentiators](kb/reference/unique-features.md)
68
68
  - [Windows Support](kb/reference/windows-support.md)
69
69
  - [Troubleshooting](kb/troubleshooting/README.md)
@@ -3048,6 +3048,139 @@ This plan shares modification targets with the Enterprise Config plan:
3048
3048
 
3049
3049
  ---
3050
3050
 
3051
+ ## kb/history/completed/output-filter-retirement-20260726.md
3052
+
3053
+ ---
3054
+ title: "Retirement: Native Tool-Output Filter — Measured 0% and Removed"
3055
+ category: planning
3056
+ service: ai-toolkit
3057
+ tags:
3058
+ - output-filter
3059
+ - token-reduction
3060
+ - postmortem
3061
+ - measurement
3062
+ - claude-code
3063
+ doc_type: postmortem
3064
+ status: completed
3065
+ created: "2026-07-26"
3066
+ last_updated: "2026-07-26"
3067
+ shipped_in: "v4.17.0 (removal)"
3068
+ description: "Why the native tool-output filter shipped in v4.16.0 was removed in v4.17.0: measured 0.0000% whole-session token saving on real traffic, because agent-issued commands are compound and the design accepted only simple registered shapes."
3069
+ ---
3070
+
3071
+ # Retirement: Native Tool-Output Filter
3072
+
3073
+ **Shipped:** v4.16.0 (2026-07-23). **Removed:** v4.17.0 (2026-07-26).
3074
+
3075
+ ## The number
3076
+
3077
+ Measured whole-session input-token saving: **0.0000%**.
3078
+
3079
+ Real Bash results from local Claude Code transcripts were replayed through the
3080
+ shipped classifier and the full filter registry. The filters ran on the actual
3081
+ captured output; this is a measurement, not an estimate.
3082
+
3083
+ | Scope | Value |
3084
+ |---|---:|
3085
+ | Transcripts replayed | 134, across 22 distinct projects |
3086
+ | Successful Bash results | 7600 |
3087
+ | Of those, parsed as a simple command shape | 145 (1.9%) |
3088
+ | Of those, matched a registered shape | 18 (0.24%) — 16 `git diff`, 2 `git show` |
3089
+ | Accepted by any filter | **0** |
3090
+ | Bytes saved | **0** |
3091
+
3092
+ The classifier was verified working before the result was accepted: `git
3093
+ status`, `git log -n 20`, `pytest -v`, `bats --tap`, and `npm test` each
3094
+ produced exactly one candidate. The zero is real.
3095
+
3096
+ ## Why: the premise, not the implementation
3097
+
3098
+ Seventeen filters were correct. They cleared their byte floors on owned
3099
+ fixtures (44–95% reduction), stayed inside every latency budget at the 8 MiB
3100
+ engine cap, passed adversarial safety review, and never once compressed a
3101
+ failure. None of that mattered, because the commands they were built for are
3102
+ not the commands that get issued.
3103
+
3104
+ 95% of successful Bash invocations are compound. The byte pool breaks down as:
3105
+
3106
+ | Class | Share of compound bytes | Why the filter refused it |
3107
+ |---|---:|---|
3108
+ | `;` chain | 44.3% | multiple output producers, attribution ambiguous |
3109
+ | multiline script | 29.1% | rejected at the raw-string boundary |
3110
+ | pipeline | 12.7% | the pipe transformed the output |
3111
+ | `&&` chain with producing segments | 5.6% | multiple output producers |
3112
+ | redirect, substitution | 4.4% | rejected at the raw-string boundary |
3113
+ | heredoc | 3.5% | rejected at the raw-string boundary |
3114
+
3115
+ Every one of those refusals was the correct safety decision in isolation.
3116
+ Together they excluded the entire population.
3117
+
3118
+ The most-frequent single shape was `cd <path> && …`, at 375 results and 480 KB.
3119
+ A bounded `cd`-prefix subset had already been designed, threat-modelled, and
3120
+ measured during Phase 3, and it was dropped because it covered 0.00% of the
3121
+ compound pool. The retirement measurement confirms why: of those 375 results,
3122
+ only 5 had a single simple second segment, and those 5 produced 0 bytes of
3123
+ output.
3124
+
3125
+ ## What was already rejected on the way, and still stands
3126
+
3127
+ - **Read-result coverage: rejected on the `Edit` exact-match hazard.** On
3128
+ 670.7 KB of real Read content, adjacent-duplicate collapse saves 0.00%,
3129
+ blank-run collapse 0.04%, trailing-whitespace 0.00%. Anything above noise
3130
+ requires elision, and 80.7% of `Edit` old-strings target a file read earlier
3131
+ in the same session, 63.4% of them multi-line byte-exact quotes. A Read
3132
+ result asserts what is on disk, so omission is a false claim rather than a
3133
+ summary.
3134
+ - **Compound-command subset: designed, measured at 0.00% coverage, dropped.**
3135
+ The pipeline-truncator shape failed on an inversion: a truncated document
3136
+ parses cleanly exactly where the shape would pay, and rejects exactly where
3137
+ truncation is detectable.
3138
+
3139
+ ## The process lesson
3140
+
3141
+ The plan validated its **design** exhaustively across five phases and its
3142
+ **premise** not at all until the fifth. Fixtures measured the filter; only real
3143
+ traffic measured the value, and the two disagreed by two orders of magnitude.
3144
+
3145
+ The end-to-end replay that produced the 0% took under an hour and could have
3146
+ run on day one, before any filter existed. Any future plan of this shape must
3147
+ put premise validation in Phase 0, with a kill number published before the
3148
+ measurement rather than argued after it.
3149
+
3150
+ ## Evaluated as a replacement: rtk
3151
+
3152
+ `rtk` (https://github.com/rtk-ai/rtk, Apache-2.0) rewrites commands at
3153
+ `PreToolUse` rather than filtering output afterwards, which is the mechanism
3154
+ this project's own safety contract had excluded. Its rewrite pipeline was
3155
+ ported and validated against 197 of its own test assertions (197/197 exact
3156
+ agreement), then applied to the same traffic:
3157
+
3158
+ - addresses **31.5%** of successful Bash bytes, **9.7%** of all tool-result
3159
+ bytes — genuinely non-zero, so the in-house 0% was a coverage failure rather
3160
+ than a law of nature;
3161
+ - projected saving is **0.32–0.48%** of session input tokens on rtk's own
3162
+ 60–90% claim, and **0.15–0.21%** once its filters' actual behaviour is
3163
+ modelled;
3164
+ - its two largest families here under-deliver: `rtk read` returns files
3165
+ verbatim at the default `--level none`, and `rtk grep` models at 12.3%
3166
+ against a claimed 75%;
3167
+ - custom TOML filters, the documented extension point, would reach 1.91% of
3168
+ Bash bytes. The large misses are structurally unreachable from config:
3169
+ `| head` and `| tail` (34.6%) are blocked by the pipeline-final rule, and
3170
+ `sed` (19.4%) sits in the hard-ignored prefix list.
3171
+
3172
+ Not adopted.
3173
+
3174
+ ## Where the tokens actually are
3175
+
3176
+ The measurement points somewhere other than command output. In this traffic,
3177
+ `Read` is 53.8% of tool-result bytes, and within Bash the two largest buckets
3178
+ are `sed` used as a file reader (16.2%) and `| head` / `| tail` pipeline tails
3179
+ (34.6%). Those are file-reading patterns, not tool reports. Any future attempt
3180
+ at token reduction should start there, and should start by measuring.
3181
+
3182
+ ---
3183
+
3051
3184
  ## kb/history/completed/output-token-discipline-plan-20260504.md
3052
3185
 
3053
3186
  ---
@@ -6844,9 +6977,9 @@ title: "AI Toolkit - Architecture Overview"
6844
6977
  category: reference
6845
6978
  service: ai-toolkit
6846
6979
  tags: [architecture, overview, design, structure]
6847
- version: "1.5.0"
6980
+ version: "1.6.0"
6848
6981
  created: "2026-03-23"
6849
- last_updated: "2026-07-23"
6982
+ last_updated: "2026-07-26"
6850
6983
  description: "Architecture of ai-toolkit: directory layout, Claude app export, global install model, editor-aware MCP install, Codex translation layer, skill tiers, and integration with projects."
6851
6984
  ---
6852
6985
 
@@ -7141,7 +7274,6 @@ Agents (code-reviewer, debugger, devops-implementer, ...)
7141
7274
  | UserPromptSubmit | Before user prompt execution | `track-usage.sh` | Record skill invocations to stats.json |
7142
7275
  | PostToolUse | After edit/write tools | `post-tool-use.sh` | Lightweight validation reminders |
7143
7276
  | PostToolUse | After any tool | `governance-capture.sh` | Log security-sensitive operations |
7144
- | PostToolUse | After successful Bash | `filter-tool-output.sh` | Observe eligible text or replace it only after exact recovery succeeds; Claude Code only |
7145
7277
  | Stop | After response | `quality-check.sh` | Multi-language lint |
7146
7278
  | Stop | After response | `save-session.sh` | Persist session context |
7147
7279
  | Stop | Before final stop | `quality-gate.sh` | Block final response on lint/type errors |
@@ -7558,9 +7690,9 @@ The reference benchmark set is intentionally curated:
7558
7690
  title: "CLI Reference"
7559
7691
  category: reference
7560
7692
  service: ai-toolkit
7561
- tags: [cli, commands, reference, install, update, plugin, mcp, telemetry, output-filter]
7693
+ tags: [cli, commands, reference, install, update, plugin, mcp, telemetry]
7562
7694
  created: "2026-04-13"
7563
- last_updated: "2026-07-23"
7695
+ last_updated: "2026-07-26"
7564
7696
  description: "Complete CLI reference for all ai-toolkit commands, options, and flags."
7565
7697
  ---
7566
7698
 
@@ -7635,18 +7767,8 @@ Usage: ai-toolkit <command> [options]
7635
7767
 
7636
7768
  | Command | Description |
7637
7769
  |---------|-------------|
7638
- | `output-filter status [--policy PATH]` | Print the effective trusted project/global policy, or inspect an explicit policy |
7639
- | `output-filter inspect --profile repeat-lines\|tap-success` | Read stdin and print content-free candidate metadata without changing or echoing the input |
7640
- | `output-filter recover <handle> [--base-directory PATH] [--session-id ID]` | Print the exact stored native response object for an opaque recovery handle |
7641
- | `output-filter clean [--base-directory PATH] [--session-id ID]` | Remove owned filter artifacts for one session or all sessions in the current repository |
7642
- | `output-filter clean --session-id ID --expired` | Remove expired exact-response artifacts for one native session |
7643
- | `output-filter hook --policy PATH` | **Internal, not for manual use.** `PostToolUse` entry point that reads the native hook payload on stdin and emits any replacement. The installed hook `filter-tool-output.sh` calls the same runtime through the lean `output_filter_hook.py` process instead |
7644
-
7645
- `off` is the default. Configure `toolOutputFilter.mode` in
7646
- `.softspark-toolkit.json`, then run `install --local` or `update --local`.
7770
+
7647
7771
  `AI_TOOLKIT_OUTPUT_FILTER_DISABLE=1` bypasses active filtering immediately.
7648
- See [Native Tool Output Filter](tool-output-filter.md) for eligibility,
7649
- privacy, recovery, and runtime support.
7650
7772
 
7651
7773
  ## Project Registry
7652
7774
 
@@ -8944,7 +9066,7 @@ tags:
8944
9066
  - multi-repo
8945
9067
  doc_type: reference
8946
9068
  created: "2026-04-11"
8947
- last_updated: "2026-07-23"
9069
+ last_updated: "2026-07-26"
8948
9070
  description: "Comprehensive guide for setting up and using ai-toolkit configuration inheritance. Covers base config creation, project setup, enforcement rules, CI integration, and troubleshooting."
8949
9071
  ---
8950
9072
 
@@ -9011,7 +9133,6 @@ ai-toolkit config check # CI enforcement check
9011
9133
  |-------|------|-------------|
9012
9134
  | `extends` | string | Base config source (npm, git URL, local path) |
9013
9135
  | `profile` | enum | `minimal`, `standard`, `strict`, `full`, `offline-slm` |
9014
- | `toolOutputFilter` | object | Native Bash result filtering policy: mode, profiles, savings thresholds, and recovery limits |
9015
9136
  | `agents` | object | `enabled`, `disabled`, `custom` arrays |
9016
9137
  | `plugins` | object | Resolved `enabled` and `disabled` plugin intent |
9017
9138
  | `rules` | object | `inject`, `remove` arrays |
@@ -9091,25 +9212,6 @@ effective intent explicit and causes validation to reject a conflicting
9091
9212
  `plugins.disabled` entry. Install the named plugin pack separately with
9092
9213
  `ai-toolkit plugin install <name>` or your organization deployment workflow.
9093
9214
 
9094
- ### Native tool-output filtering
9095
-
9096
- Projects can opt into the dependency-free output filter without changing the
9097
- organization-wide default:
9098
-
9099
- ```json
9100
- {
9101
- "toolOutputFilter": {
9102
- "mode": "observe",
9103
- "profiles": ["repeat-lines", "tap-success"]
9104
- }
9105
- }
9106
- ```
9107
-
9108
- `ai-toolkit install --local` materializes the effective policy as the managed
9109
- `.claude/ai-toolkit-output-filter.json` file. `off` is the default, `observe`
9110
- collects metadata without changing model-visible output, and `safe` permits
9111
- replacement only when every invariant and exact-recovery check succeeds.
9112
-
9113
9215
  ### Overrides
9114
9216
 
9115
9217
  Projects can override base settings, but must declare intent:
@@ -10054,9 +10156,9 @@ title: "Hooks Catalog"
10054
10156
  category: reference
10055
10157
  service: ai-toolkit
10056
10158
  tags: [hooks, quality, safety, enforcement, settings.json]
10057
- version: "1.8.0"
10159
+ version: "1.9.0"
10058
10160
  created: "2026-03-27"
10059
- last_updated: "2026-07-23"
10161
+ last_updated: "2026-07-26"
10060
10162
  description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
10061
10163
  ---
10062
10164
 
@@ -10082,9 +10184,6 @@ ai-toolkit update # re-copies scripts, re-merges (idempotent)
10082
10184
  **File locations:**
10083
10185
 
10084
10186
  - Scripts: `~/.softspark/ai-toolkit/hooks/*.sh`
10085
- - Output-filter runtime: `~/.softspark/ai-toolkit/scripts/output_filter_hook.py`, `output_filter_cli.py`, and `tool_output_filter/`
10086
- - Global output-filter policy: `~/.softspark/ai-toolkit/hooks/output-filter-policy.json`
10087
- - Managed project policy: `<project>/.claude/ai-toolkit-output-filter.json`
10088
10187
  - Config: `~/.claude/settings.json` → `hooks` key
10089
10188
  - Source: `ai-toolkit/app/hooks/*.sh` + `app/hooks.json`
10090
10189
 
@@ -10229,59 +10328,6 @@ arms the corrective Stop hook.
10229
10328
 
10230
10329
  Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
10231
10330
 
10232
- ### PostToolUse (native Bash output filter): `filter-tool-output.sh`
10233
-
10234
- | Field | Value |
10235
- |-------|-------|
10236
- | Event | `PostToolUse` |
10237
- | Matcher | `Bash` |
10238
- | Script | `~/.softspark/ai-toolkit/hooks/filter-tool-output.sh` |
10239
- | Fires | After a successful Claude Code Bash tool call |
10240
-
10241
- The hook is disabled by default and runs last among the installed
10242
- `PostToolUse` handlers. Its modes are:
10243
-
10244
- | Mode | Behavior |
10245
- |------|----------|
10246
- | `off` | Shell fast path exits before Python starts |
10247
- | `observe` | Evaluates eligible output and writes content-free metadata, but emits no replacement |
10248
- | `safe` | Replaces eligible output only after invariants pass and the exact native response is stored for recovery |
10249
-
10250
- Only explicitly allowlisted test, lint, typecheck, and validation command
10251
- shapes are eligible. Failed or interrupted tools, non-empty stderr, image or
10252
- binary results, pipes, redirects, deployment, migrations, audits, security
10253
- scanners, malformed payloads, unavailable recovery, and any uncertain case
10254
- remain unchanged.
10255
-
10256
- The per-project policy `<project>/.claude/ai-toolkit-output-filter.json` is
10257
- honored only when **both** checks pass: the project root is registered in
10258
- `~/.softspark/ai-toolkit/projects.json`, and the sibling regular file
10259
- `<project>/.claude/.ai-toolkit-output-filter.owner` holds the ai-toolkit owner
10260
- marker. `ai-toolkit install --local` writes both. Registration is required
10261
- because the owner marker is a public constant, so a cloned or untrusted
10262
- checkout must never be able to self-enable filtering by shipping its own
10263
- marker. An unregistered project, a missing or foreign marker, or a symlinked
10264
- project root or `.claude` directory falls back to the installed global policy
10265
- at `~/.softspark/ai-toolkit/hooks/output-filter-policy.json`, which ships as
10266
- `off`.
10267
-
10268
- `jq` is a required system dependency (`python3 scripts/check_deps.py`
10269
- verifies it alongside `python3`, `git`, and `node`); without it `guard-path.sh`
10270
- blocks file tools rather than skipping path validation.
10271
-
10272
- Recovery data is private and session-scoped under
10273
- `~/.softspark/ai-toolkit/sessions/<repo-key>/output-filter/`. Telemetry records
10274
- only profile/version, byte and line counts, latency, outcome, and fallback
10275
- reason. Recovery-backed modes require a bounded native session ID containing
10276
- only ASCII letters, digits, underscores, or hyphens. Three consecutive
10277
- profile, invariant, or recovery safety failures open a session circuit
10278
- breaker. Set `AI_TOOLKIT_OUTPUT_FILTER_DISABLE=1` for an immediate bypass.
10279
-
10280
- The replacement adapter is Claude Code-specific. Claude Chat/Cowork exports
10281
- exclude it, and the generated hooks for other editors do not activate it.
10282
- The `minimal` hook profile and `AI_TOOLKIT_DISABLED_HOOKS=filter-tool-output`
10283
- bypass it before the Python runtime starts.
10284
-
10285
10331
  ### Stop (quality check) — `quality-check.sh`
10286
10332
 
10287
10333
  | Field | Value |
@@ -10395,8 +10441,7 @@ Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
10395
10441
  | Script | `~/.softspark/ai-toolkit/hooks/session-end.sh` |
10396
10442
  | Fires | When a Claude session ends |
10397
10443
 
10398
- **Action:** Removes private output-filter artifacts and isolated edit state for
10399
- the ending native session, then writes `session-end.md` to the per-repo session store
10444
+ **Action:** Removes isolated edit state for the ending native session, then writes `session-end.md` to the per-repo session store
10400
10445
  (`~/.softspark/ai-toolkit/sessions/<repo-key>/`) with a lightweight handoff note
10401
10446
  for the next session.
10402
10447
 
@@ -13157,7 +13202,6 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
13157
13202
  | Our generators | — (Claude Code is the primary target; toolkit content ships directly as `.md` files and `settings.json` merges) |
13158
13203
  | Tracked hook events | Core: `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Notification`, `MessageDisplay`. Tool: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`. Turn: `Stop`, `StopFailure`, `UserPromptExpansion`. Subagent: `SubagentStart`, `SubagentStop`. Compaction: `PreCompact`, `PostCompact`. Permissions: `PermissionRequest`, `PermissionDenied`. Elicitation: `Elicitation`, `ElicitationResult`. Teams: `TaskCreated`, `TaskCompleted`, `TeammateIdle`. Worktrees/env: `WorktreeCreate`, `WorktreeRemove`, `CwdChanged`, `FileChanged`, `ConfigChange`. Setup: `Setup`, `InstructionsLoaded` |
13159
13204
  | Tracked handler types | `command`, `prompt`, `agent`, `mcp_tool`, `http` (POST event JSON to allowlisted URLs via `allowedHttpHookUrls`) |
13160
- | Tool-output replacement | Native `PostToolUse.updatedToolOutput`; ai-toolkit exposes dependency-free `off`, byte-identical `observe`, and opt-in recoverable `safe` modes for eligible successful Bash text |
13161
13205
  | Other capabilities | slash commands, MCP server/client, sub-agent, output style, `SKILL.md` (≥500 lines warn) |
13162
13206
  | Version probe | `claude --version` |
13163
13207
  | Notes | v2.1.169 added `disableBundledSkills` setting + `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS` env var (hides bundled skills/built-in slash commands from the model; toolkit skills in `.claude/skills/` are unaffected — useful when toolkit skills overlap built-ins) and `claude --safe-mode` / `CLAUDE_CODE_SAFE_MODE` (starts with hooks, skills, agents, and CLAUDE.md disabled — first isolation step when debugging toolkit rule enforcement). `fallbackModel` settings key (v2.1.166) noted as not-adopted (class C, no toolkit surface writes model settings). |
@@ -13177,7 +13221,6 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
13177
13221
  | Plugin layout | `.claude-plugin/plugin.json`, `skills/*/SKILL.md`, `agents/*.md`, `hooks/hooks.json`; ai-toolkit uses manifest paths under `claude-app/` for its generated app-only rules and hooks |
13178
13222
  | Our generator | `scripts/claude_app.py` (`ai-toolkit claude-app export`) |
13179
13223
  | Runtime split | Skills work in Chat (web/Desktop) and Cowork. Hooks and sub-agents run only in Cowork. Claude app does **not** scan Claude Code's `~/.claude/rules/`, `CLAUDE.md`, or `~/.claude/settings.json`. |
13180
- | Tool-output replacement | Not shipped. The Claude Code-only filter hook and runtime are excluded from the app archive until Cowork has an independently verified replacement contract. |
13181
13224
  | Install/update | Export the ZIP, upload it from `Customize > Plugins`, then paste the generated global-instructions file into `Settings > Cowork > Global instructions`. Re-export/re-upload after toolkit updates. |
13182
13225
 
13183
13226
  ---
@@ -13325,7 +13368,6 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
13325
13368
  | Our generators | `scripts/generate_codex.py`, `scripts/generate_codex_agents.py` (native custom-agent TOML), `scripts/generate_codex_hooks.py`, `scripts/generate_codex_skills.py` (opt-in via `--codex-skills`) |
13326
13369
  | Rules delivery | Universal coding rules are inlined into `AGENTS.md` (Codex reads instructions only from AGENTS.md, not `.agents/rules/`); language rules ship as `<lang>-rules` skills under `.agents/skills/`. Global install writes `~/.codex/AGENTS.md` (not `~/AGENTS.md`, which Codex never loads globally); plugin-pack rules are marker-injected into the same file. `project_doc_max_bytes` default is 32 KiB and Codex silently truncates AGENTS.md past that (see codex-cli-compatibility.md). |
13327
13370
  | Tracked hook events | Upstream canonical (codex-rs `HookEventName` enum): `PreToolUse`, `PostToolUse`, `PermissionRequest`, `PreCompact`, `PostCompact`, `SessionStart`, `UserPromptSubmit`, `SubagentStart`, `SubagentStop`, `Stop` (10 events). We wire 9 through an explicit Codex map, including destructive-command and wrong-home path guards on both Bash `PreToolUse` and `PermissionRequest`. `PostCompact` is not wired (its only hook was the removed environment-snapshot probe). |
13328
- | Tool-output replacement | Manual `ai-toolkit output-filter inspect` only. The Claude-specific `updatedToolOutput` adapter is not emitted into Codex hooks. |
13329
13371
  | Tracked handler types | `command` (emitted by default; the only handler Codex actually runs). `prompt` and `agent` are parsed by Codex but NOT yet executed, so hand-authored handlers of those types are inert. |
13330
13372
  | Other capabilities | `AGENTS.md`, `config.toml`, `mcp_servers`, sandbox policies, `.agents/skills/*/SKILL.md` (native Codex skill discovery path), `.codex/agents/*.toml` (native custom agents) |
13331
13373
  | Version probe | `codex --version` |
@@ -13340,7 +13382,6 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
13340
13382
  | Config paths | `opencode.json`, `.opencode/agents/*.md`, `.opencode/commands/*.md`, `.opencode/plugins/*`, `.opencode/skills/*/SKILL.md` (v1.14+), `AGENTS.md`; skill fallback discovery: `.claude/skills/`, `.agents/skills/`, `~/.config/opencode/skills/`, `~/.claude/skills/`, `~/.agents/skills/` |
13341
13383
  | Our generators | `scripts/generate_opencode.py`, `scripts/generate_opencode_agents.py`, `scripts/generate_opencode_commands.py`, `scripts/generate_opencode_json.py`, `scripts/generate_opencode_plugin.py` |
13342
13384
  | Hook isolation | Tool hooks preserve native `sessionID` as normalized `session_id`; exit code 2 from a blocking pre-tool guard is raised back to OpenCode instead of being ignored. |
13343
- | Tool-output replacement | Manual `ai-toolkit output-filter inspect` only. No active OpenCode output adapter is shipped. |
13344
13385
  | Tracked plugin events | `session.created`, `session.compacted`, `session.deleted`, `message.updated`, `tool.execute.before`, `tool.execute.after`, `permission.asked`, `command.executed` |
13345
13386
  | Other capabilities | `opencode.json` config, primary + subagent modes, `@`-mention subagents, `/`-invocation commands, MCP (local + remote), plugin hooks in JS/TS, native `SKILL.md` discovery with Claude-compatible fallback, `permission.skill.*` matrix |
13346
13387
  | Version probe | `opencode --version` |
@@ -13478,299 +13519,6 @@ ai-toolkit sync --pull abc123def456 # Use gist ID from first push
13478
13519
 
13479
13520
  ---
13480
13521
 
13481
- ## kb/reference/tool-output-filter.md
13482
-
13483
- ---
13484
- title: "Native Tool Output Filter"
13485
- category: reference
13486
- service: ai-toolkit
13487
- tags: [output-filter, hooks, recovery, telemetry, claude-code]
13488
- version: "1.0.0"
13489
- created: "2026-07-23"
13490
- last_updated: "2026-07-23"
13491
- description: "Contract, configuration, safety boundaries, recovery, CLI, and runtime support for the native ai-toolkit output filter."
13492
- ---
13493
-
13494
- # Native Tool Output Filter
13495
-
13496
- ## Overview
13497
-
13498
- ai-toolkit includes an original, dependency-free filter for selected
13499
- post-execution tool results. It is disabled by default and does not depend on,
13500
- vendor, execute, or copy another output-filter package.
13501
-
13502
- The active adapter targets Claude Code because its `PostToolUse` contract can
13503
- replace a native tool response through
13504
- `hookSpecificOutput.updatedToolOutput`. The replacement object retains the
13505
- native response shape and changes only `stdout`. See the
13506
- [Claude Code hooks reference](https://code.claude.com/docs/en/hooks).
13507
-
13508
- The filter never changes the command, arguments, environment, working
13509
- directory, permission decision, exit status, or signal. [PATH:
13510
- scripts/tool_output_filter/hook_runtime.py] [PATH:
13511
- scripts/tool_output_filter/engine.py]
13512
-
13513
- ## Modes
13514
-
13515
- | Mode | Model-visible result | Recovery | Telemetry |
13516
- |------|----------------------|----------|-----------|
13517
- | `off` | Original | None | None |
13518
- | `observe` | Original | No raw response | Content-free decision metadata |
13519
- | `safe` | Replacement only after every gate passes | Exact native response saved first | Content-free decision metadata |
13520
-
13521
- `off` is a shell fast path, so the Python runtime is not started. Any runtime
13522
- error, malformed payload, unsafe command, failed invariant, unavailable secure
13523
- storage, or insufficient saving leaves the original response unchanged.
13524
-
13525
- Three consecutive profile, invariant, or recovery safety failures open a
13526
- persistent session-scoped circuit breaker. One bounded Claude system message
13527
- reports the bypass, then later results stay unchanged for that session.
13528
-
13529
- ## Configuration
13530
-
13531
- Configure the project in `.softspark-toolkit.json`:
13532
-
13533
- ```json
13534
- {
13535
- "toolOutputFilter": {
13536
- "mode": "observe",
13537
- "profiles": ["repeat-lines", "tap-success"],
13538
- "maxInputBytes": 8388608,
13539
- "minSavingsBytes": 1024,
13540
- "minSavingsRatio": 0.15,
13541
- "recovery": {
13542
- "mode": "ephemeral",
13543
- "ttlMinutes": 60,
13544
- "maxSessionBytes": 33554432
13545
- }
13546
- }
13547
- }
13548
- ```
13549
-
13550
- Run `ai-toolkit install --local` or `ai-toolkit update --local` to materialize
13551
- the effective policy as:
13552
-
13553
- ```text
13554
- <project>/.claude/ai-toolkit-output-filter.json
13555
- <project>/.claude/.ai-toolkit-output-filter.owner
13556
- ```
13557
-
13558
- The managed files use mode `0600`. The hook accepts a project policy only when
13559
- the project root is registered in `~/.softspark/ai-toolkit/projects.json`
13560
- **and** the regular owner marker matches ai-toolkit. Registration is the
13561
- security boundary: the owner marker is a public constant, so requiring the
13562
- registry stops a cloned or untrusted checkout from self-enabling filtering by
13563
- committing its own marker. An unregistered project, a missing or foreign
13564
- marker, or a symlinked project root or `.claude` directory falls back to the
13565
- installed global policy at
13566
- `~/.softspark/ai-toolkit/hooks/output-filter-policy.json`, which defaults to
13567
- `off`. [PATH: app/hooks/filter-tool-output.sh] [PATH:
13568
- scripts/install_steps/ai_tools.py]
13569
-
13570
- `jq` is a required system dependency for the lifecycle hooks and is verified
13571
- by `python3 scripts/check_deps.py` alongside `python3`, `git`, and `node`.
13572
-
13573
- Before executing anything, the hook validates the resolved Python runtime path:
13574
- it must be a readable regular file and must not be a symlink. A missing,
13575
- non-regular, unreadable, or symlinked runtime makes the hook exit silently and
13576
- leave the tool response unchanged, so a tampered or half-installed runtime
13577
- cannot be invoked. The same regular-file rule applies to every policy file the
13578
- hook reads. [PATH: app/hooks/filter-tool-output.sh]
13579
-
13580
- Set `AI_TOOLKIT_OUTPUT_FILTER_DISABLE=1` for an immediate bypass without
13581
- reinstalling. `AI_TOOLKIT_OUTPUT_FILTER_POLICY` may point the hook to an
13582
- explicit regular policy file for controlled operational testing. The hook is
13583
- also skipped by the `minimal` hook profile and may be listed in
13584
- `AI_TOOLKIT_DISABLED_HOOKS`. `AI_TOOLKIT_OUTPUT_FILTER_HOOK_RUNTIME` is reserved
13585
- for controlled runtime testing; the manual and cleanup CLI remains
13586
- `output_filter_cli.py`.
13587
-
13588
- ## Eligibility
13589
-
13590
- The Claude adapter considers only a completed `PostToolUse` event with:
13591
-
13592
- - tool name `Bash`;
13593
- - a non-empty native session ID of at most 160 ASCII letters, digits,
13594
- underscores, or hyphens;
13595
- - string `stdout`;
13596
- - empty `stderr`;
13597
- - `interrupted: false`;
13598
- - `isImage: false`;
13599
- - a command that matches a strict allowlist for test, lint, typecheck, or
13600
- validation tools;
13601
- - input at or below 8 MiB;
13602
- - valid text without binary or terminal-control content.
13603
-
13604
- The following always pass through unchanged:
13605
-
13606
- - failed, interrupted, image, binary, invalid-text, TTY, or streaming results;
13607
- - pipes, redirects, shell chaining, substitutions, and multiline commands;
13608
- - deployment, release, migration, publish, destroy, audit, and security-scanner
13609
- commands;
13610
- - arbitrary Python scripts and unknown command shapes;
13611
- - output with non-empty stderr;
13612
- - unknown profiles or native payload shapes;
13613
- - candidates that save less than both the configured byte and ratio threshold.
13614
-
13615
- The command classifier is eligibility logic only. It never parses and
13616
- re-executes a command. [PATH: scripts/tool_output_filter/hook_runtime.py]
13617
-
13618
- ## Profiles
13619
-
13620
- ### `repeat-lines`
13621
-
13622
- Collapses only adjacent identical, non-diagnostic lines. It retains the first
13623
- line and adds a versioned marker with the exact number of omitted copies.
13624
- Warnings, failures, permissions, security diagnostics, blank lines, comments,
13625
- existing filter markers, and control-bearing output are not collapsed.
13626
-
13627
- ### `tap-success`
13628
-
13629
- Accepts only a strict, complete, successful TAP stream with a single plan and
13630
- contiguous `ok` result numbers. It retains the TAP version, plan, directives,
13631
- comments, totals, duration, and other summary lines. Diagnostics, `not ok`,
13632
- non-zero failure summaries, malformed plans, gaps, duplicates, and unknown
13633
- content reject the whole profile.
13634
-
13635
- Both profiles are deterministic and idempotent. A safe replacement must remain
13636
- smaller after the recovery marker is added. [PATH:
13637
- scripts/tool_output_filter/profiles/] [PATH:
13638
- tests/test_tool_output_filter_properties.py]
13639
-
13640
- ## Exact Recovery and Privacy
13641
-
13642
- Before `safe` mode emits a replacement, it stores and reloads the complete
13643
- native tool-response object. Equality must succeed before the hook prints
13644
- `updatedToolOutput`.
13645
-
13646
- ```text
13647
- ~/.softspark/ai-toolkit/sessions/<repo-key>/
13648
- └── output-filter/
13649
- └── <hashed-session>/
13650
- ├── <opaque-handle>.json
13651
- ├── .circuit-state.json
13652
- └── .telemetry.jsonl
13653
- ```
13654
-
13655
- Recovery directories use `0700`; response, state, and telemetry files use
13656
- `0600`. Creation and cleanup use pinned directory descriptors, no-follow
13657
- operations, atomic publication, opaque random handles, a per-session quota,
13658
- and TTL cleanup. If this secure contract is unavailable, `safe` mode returns
13659
- the original response.
13660
-
13661
- The replacement ends with a marker similar to:
13662
-
13663
- ```text
13664
- [ai-toolkit-output-filter repeat-lines/v1; original_lines=500; emitted_lines=3; recovery=<opaque-handle>]
13665
- ```
13666
-
13667
- The recovery file can contain everything returned by the tool, including
13668
- secrets. Treat the session directory as sensitive. Telemetry never stores raw
13669
- output, commands, paths, environment values, session IDs, or recovery handles.
13670
- It contains only profile/version, input/output byte and line counts, latency,
13671
- outcome, and a bounded fallback reason.
13672
-
13673
- Session end, explicit cleanup, and global uninstall remove only validated
13674
- ai-toolkit-owned filter artifacts. Foreign files and directories are
13675
- preserved. [PATH: scripts/tool_output_filter/recovery.py] [PATH:
13676
- app/hooks/session-end.sh] [PATH: scripts/uninstall.py]
13677
-
13678
- ## CLI
13679
-
13680
- Inspect candidate savings without changing output:
13681
-
13682
- ```bash
13683
- some-test-command | ai-toolkit output-filter inspect --profile repeat-lines
13684
- some-tap-command | ai-toolkit output-filter inspect --profile tap-success
13685
- ```
13686
-
13687
- The JSON report contains counts, eligibility, outcome, and fallback reason. It
13688
- does not echo stdin.
13689
-
13690
- Inspect the effective trusted project or global policy:
13691
-
13692
- ```bash
13693
- ai-toolkit output-filter status
13694
- ai-toolkit output-filter status --policy /path/to/materialized-policy.json
13695
- ```
13696
-
13697
- Recover the exact native response object using the handle printed in a safe
13698
- replacement:
13699
-
13700
- ```bash
13701
- ai-toolkit output-filter recover <opaque-handle>
13702
- ```
13703
-
13704
- The default lookup derives the current repository session directory. Advanced
13705
- or test workflows can add `--base-directory PATH` or `--session-id ID`.
13706
-
13707
- Clean the ending session, expired exact responses, or all filter artifacts for
13708
- the current repository:
13709
-
13710
- ```bash
13711
- ai-toolkit output-filter clean --session-id <native-session-id>
13712
- ai-toolkit output-filter clean --session-id <native-session-id> --expired
13713
- ai-toolkit output-filter clean
13714
- ```
13715
-
13716
- `--expired` requires `--session-id`. Cleanup prints only the removed artifact
13717
- count and scope.
13718
-
13719
- ## Runtime Capability Matrix
13720
-
13721
- | Runtime | Active result replacement | Capability |
13722
- |---------|---------------------------|------------|
13723
- | Claude Code | Yes, opt-in | Native `PostToolUse.updatedToolOutput` adapter |
13724
- | Claude Chat / Cowork | No | Plugin export explicitly excludes the Claude Code-only hook |
13725
- | Cursor | No | Manual `output-filter inspect` only |
13726
- | Windsurf / Devin | No | Manual `output-filter inspect` only |
13727
- | GitHub Copilot | No | Manual `output-filter inspect` only |
13728
- | Gemini CLI | No | Manual `output-filter inspect` only |
13729
- | Cline | No | Manual `output-filter inspect` only |
13730
- | Roo Code | No | Manual `output-filter inspect` only |
13731
- | Aider | No | Manual `output-filter inspect` only |
13732
- | Augment | No | Manual `output-filter inspect` only |
13733
- | Google Antigravity | No | Manual `output-filter inspect` only |
13734
- | Codex CLI | No | Manual `output-filter inspect` only |
13735
- | OpenCode | No | Manual `output-filter inspect` only |
13736
-
13737
- An editor hook, extra context message, or command wrapper is not treated as
13738
- result replacement. A new adapter requires a verified native replacement
13739
- contract and dedicated native payload tests.
13740
-
13741
- ## Benchmark Semantics
13742
-
13743
- Run the deterministic offline corpus:
13744
-
13745
- ```bash
13746
- PYTHONDONTWRITEBYTECODE=1 python3 scripts/benchmark_output_filter.py
13747
- ```
13748
-
13749
- The benchmark measures profile p95 latency, production Bash-wrapper latency
13750
- with a fresh Python process per sample in one native session, traced peak
13751
- allocation, and eligible-output byte reduction. It uses 100 samples by default
13752
- to avoid a one-sample p95 swing. The current gates are:
13753
-
13754
- - at least 30% candidate byte reduction;
13755
- - at most 20 ms p95 for profile inputs up to 100 KiB;
13756
- - at most 150 ms p95 for the 8 MiB profile case;
13757
- - at most 75 ms p95 for a cold end-to-end hook process;
13758
- - peak traced allocation no greater than three input sizes plus 16 MiB.
13759
-
13760
- Byte reduction is not billed-token savings and is not a whole-session cost
13761
- claim. Measure actual model token receipts separately before changing the
13762
- default mode. [PATH: scripts/benchmark_output_filter.py] [PATH:
13763
- benchmarks/output-filter/]
13764
-
13765
- ## Related
13766
-
13767
- - [Hooks Catalog](hooks-catalog.md)
13768
- - [Supported Tools Registry](supported-tools-registry.md)
13769
- - [Architecture Overview](architecture-overview.md)
13770
- - [Output Token Discipline Plan](../history/completed/output-token-discipline-plan-20260504.md)
13771
-
13772
- ---
13773
-
13774
13522
  ## kb/reference/unique-features.md
13775
13523
 
13776
13524
  ---