@softspark/ai-toolkit 4.15.0 → 4.16.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 (76) hide show
  1. package/AGENTS.md +117 -0
  2. package/CHANGELOG.md +43 -0
  3. package/README.md +19 -13
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/app/ARCHITECTURE.md +4 -3
  6. package/app/hooks/_hook-io.sh +18 -3
  7. package/app/hooks/ai-toolkit-statusline.sh +30 -5
  8. package/app/hooks/filter-tool-output.sh +76 -0
  9. package/app/hooks/governance-capture.sh +1 -1
  10. package/app/hooks/guard-path.sh +2 -2
  11. package/app/hooks/post-tool-use.sh +5 -3
  12. package/app/hooks/pre-compact-save.sh +4 -3
  13. package/app/hooks/quality-gate.sh +12 -1
  14. package/app/hooks/revert-guard.sh +5 -2
  15. package/app/hooks/save-session.sh +4 -2
  16. package/app/hooks/session-end.sh +36 -4
  17. package/app/hooks/session-start.sh +11 -5
  18. package/app/hooks.json +10 -0
  19. package/app/output-filter-policy.json +15 -0
  20. package/app/skills/brand-voice/scripts/measure.py +7 -5
  21. package/benchmarks/ecosystem-doctor-snapshot.json +22 -22
  22. package/benchmarks/output-filter/README.md +11 -0
  23. package/benchmarks/output-filter/scenarios.json +25 -0
  24. package/bin/ai-toolkit.js +2 -0
  25. package/kb/history/completed/native-tool-output-filter-plan.md +517 -0
  26. package/kb/procedures/release-preparation-sop.md +6 -5
  27. package/kb/reference/architecture-overview.md +6 -5
  28. package/kb/reference/cli-reference.md +19 -2
  29. package/kb/reference/codex-cli-compatibility.md +1 -0
  30. package/kb/reference/copilot-compatibility.md +173 -0
  31. package/kb/reference/enterprise-config-guide.md +28 -2
  32. package/kb/reference/global-install-model.md +6 -2
  33. package/kb/reference/hooks-catalog.md +105 -16
  34. package/kb/reference/opencode-compatibility.md +1 -0
  35. package/kb/reference/supported-tools-registry.md +10 -5
  36. package/kb/reference/tool-output-filter.md +288 -0
  37. package/kb/reference/windows-support.md +4 -3
  38. package/llms-full.txt +1182 -40
  39. package/llms.txt +3 -0
  40. package/manifest.json +9 -6
  41. package/package.json +3 -2
  42. package/scripts/benchmark_output_filter.py +343 -0
  43. package/scripts/check_deps.py +16 -0
  44. package/scripts/claude_app.py +30 -2
  45. package/scripts/config_cli.py +4 -4
  46. package/scripts/config_lock.py +120 -14
  47. package/scripts/config_merger.py +103 -20
  48. package/scripts/config_resolver.py +22 -2
  49. package/scripts/config_validator.py +268 -16
  50. package/scripts/copilot_legacy_hashes.json +338 -0
  51. package/scripts/doctor.py +1 -0
  52. package/scripts/generate_codex_hooks.py +2 -0
  53. package/scripts/generate_copilot.py +464 -71
  54. package/scripts/generate_copilot_hooks.py +124 -7
  55. package/scripts/generate_gemini_hooks.py +33 -10
  56. package/scripts/generate_opencode_plugin.py +28 -12
  57. package/scripts/install_steps/ai_tools.py +115 -3
  58. package/scripts/install_steps/hooks.py +25 -1
  59. package/scripts/output_filter_cli.py +347 -0
  60. package/scripts/output_filter_hook.py +23 -0
  61. package/scripts/plugin_schema.py +27 -1
  62. package/scripts/schemas/ai-toolkit-config.schema.json +83 -5
  63. package/scripts/session_state.py +156 -42
  64. package/scripts/tool_output_filter/__init__.py +33 -0
  65. package/scripts/tool_output_filter/contracts.py +173 -0
  66. package/scripts/tool_output_filter/engine.py +260 -0
  67. package/scripts/tool_output_filter/hook_runtime.py +369 -0
  68. package/scripts/tool_output_filter/input.py +56 -0
  69. package/scripts/tool_output_filter/invariants.py +40 -0
  70. package/scripts/tool_output_filter/policy.py +153 -0
  71. package/scripts/tool_output_filter/profiles/__init__.py +68 -0
  72. package/scripts/tool_output_filter/profiles/repeat_lines.py +71 -0
  73. package/scripts/tool_output_filter/profiles/tap_success.py +154 -0
  74. package/scripts/tool_output_filter/recovery.py +846 -0
  75. package/scripts/tool_output_filter/telemetry.py +13 -0
  76. package/scripts/uninstall.py +96 -3
@@ -8,15 +8,17 @@
8
8
  source "$(dirname "$0")/_profile-check.sh"
9
9
  # shellcheck source=_session-paths.sh
10
10
  source "$(dirname "$0")/_session-paths.sh"
11
+ # shellcheck source=_hook-io.sh
12
+ source "$(dirname "$0")/_hook-io.sh"
11
13
 
12
14
  # Read from stdin (Claude Code passes JSON with .session_id, .last_assistant_message)
13
15
  INPUT=$(cat)
14
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
16
+ SESSION_ID=$(hook_session_id)
15
17
  LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // "No summary available"' 2>/dev/null | head -5)
16
18
 
17
19
  SESSION_FILE="$SESSION_CONTEXT_FILE"
18
20
 
19
- if [ -n "$SESSION_ID" ]; then
21
+ if [ "$SESSION_ID" != "default" ]; then
20
22
  mkdir -p "$SESSION_DIR"
21
23
 
22
24
  # Gather git state for richer context
@@ -3,12 +3,45 @@
3
3
  #
4
4
  # Fires on: SessionEnd
5
5
  # Matcher: all
6
- # Skipped when TOOLKIT_HOOK_PROFILE=minimal.
6
+ # The handoff snapshot is skipped for the minimal profile. Owned recovery
7
+ # cleanup still runs for every profile.
7
8
 
8
- # shellcheck source=_profile-check.sh
9
- source "$(dirname "$0")/_profile-check.sh"
10
9
  # shellcheck source=_session-paths.sh
11
10
  source "$(dirname "$0")/_session-paths.sh"
11
+ # shellcheck source=_hook-io.sh
12
+ source "$(dirname "$0")/_hook-io.sh"
13
+
14
+ # Cleanup must run before _profile-check.sh can exit for the minimal profile.
15
+ INPUT=""
16
+ if [ ! -t 0 ]; then
17
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
18
+ INPUT=$(cat)
19
+ fi
20
+ SESSION_ID=$(hook_session_id)
21
+ RECOVERY_ROOT="$SESSION_DIR/output-filter"
22
+ OUTPUT_FILTER_CLI="${AI_TOOLKIT_OUTPUT_FILTER_CLI:-$HOME/.softspark/ai-toolkit/scripts/output_filter_cli.py}"
23
+ if [ "$SESSION_ID" != "default" ] &&
24
+ [ -d "$RECOVERY_ROOT" ] &&
25
+ [ ! -L "$RECOVERY_ROOT" ] &&
26
+ [ -f "$OUTPUT_FILTER_CLI" ] &&
27
+ [ ! -L "$OUTPUT_FILTER_CLI" ] &&
28
+ command -v python3 >/dev/null 2>&1; then
29
+ python3 -S "$OUTPUT_FILTER_CLI" clean \
30
+ --base-directory "$SESSION_DIR" \
31
+ --session-id "$SESSION_ID" >/dev/null 2>&1 || true
32
+ fi
33
+
34
+ SESSION_STATE_CLI="${AI_TOOLKIT_SESSION_STATE_CLI:-$HOME/.softspark/ai-toolkit/scripts/session_state.py}"
35
+ if [ "$SESSION_ID" != "default" ] &&
36
+ [ -f "$SESSION_STATE_CLI" ] &&
37
+ [ ! -L "$SESSION_STATE_CLI" ] &&
38
+ command -v python3 >/dev/null 2>&1; then
39
+ python3 -S "$SESSION_STATE_CLI" clean \
40
+ --session-id "$SESSION_ID" >/dev/null 2>&1 || true
41
+ fi
42
+
43
+ # shellcheck source=_profile-check.sh
44
+ source "$(dirname "$0")/_profile-check.sh"
12
45
 
13
46
  SESSION_FILE="$SESSION_CONTEXT_FILE"
14
47
  HANDOFF_FILE="$SESSION_END_FILE"
@@ -35,4 +68,3 @@ else
35
68
  fi
36
69
 
37
70
  exit 0
38
-
@@ -8,6 +8,8 @@
8
8
  source "$(dirname "$0")/_locate-toolkit.sh"
9
9
  # shellcheck source=_session-paths.sh
10
10
  source "$(dirname "$0")/_session-paths.sh"
11
+ # shellcheck source=_hook-io.sh
12
+ source "$(dirname "$0")/_hook-io.sh"
11
13
 
12
14
  emit_context() {
13
15
  [ "${AI_TOOLKIT_HOOK_QUIET:-0}" = "1" ] && return 0
@@ -20,14 +22,18 @@ emit_context "MANDATORY: Before answering ANY technical question, apply ALL rule
20
22
  emit_context "REMINDER: When writing features or fixing bugs, ensure tests cover the changes. When modifying API, config, or setup, update relevant documentation. Propose these steps to the user — do not silently skip them."
21
23
 
22
24
  # 1a. Reset per-session edit state (used by revert-guard, test-cohesion, quality-gate)
23
- SESSION_ID_INPUT=""
25
+ INPUT=""
24
26
  if [ ! -t 0 ]; then
25
- STDIN_PAYLOAD="$(cat)"
26
- SESSION_ID_INPUT="$(printf '%s' "$STDIN_PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)"
27
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
28
+ INPUT="$(cat)"
27
29
  fi
28
- if [ -n "$TOOLKIT_DIR" ] && command -v python3 >/dev/null 2>&1; then
30
+ SESSION_ID_INPUT=$(hook_session_id)
31
+ SESSION_SOURCE=$(hook_json '.source // empty')
32
+ if [ "$SESSION_SOURCE" != "compact" ] &&
33
+ [ -n "$TOOLKIT_DIR" ] &&
34
+ command -v python3 >/dev/null 2>&1; then
29
35
  python3 "$TOOLKIT_DIR/scripts/session_state.py" reset \
30
- ${SESSION_ID_INPUT:+--session-id "$SESSION_ID_INPUT"} >/dev/null 2>&1 || true
36
+ --session-id "$SESSION_ID_INPUT" >/dev/null 2>&1 || true
31
37
  fi
32
38
 
33
39
  # 1b. GC stale per-session search-required flags (older than 60 min)
package/app/hooks.json CHANGED
@@ -158,6 +158,16 @@
158
158
  "command": "\"$HOME/.softspark/ai-toolkit/hooks/search-tracker.sh\""
159
159
  }
160
160
  ]
161
+ },
162
+ {
163
+ "_source": "ai-toolkit",
164
+ "matcher": "Bash",
165
+ "hooks": [
166
+ {
167
+ "type": "command",
168
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/filter-tool-output.sh\""
169
+ }
170
+ ]
161
171
  }
162
172
  ],
163
173
  "Stop": [
@@ -0,0 +1,15 @@
1
+ {
2
+ "mode": "off",
3
+ "profiles": [
4
+ "repeat-lines",
5
+ "tap-success"
6
+ ],
7
+ "maxInputBytes": 8388608,
8
+ "minSavingsBytes": 1024,
9
+ "minSavingsRatio": 0.15,
10
+ "recovery": {
11
+ "mode": "ephemeral",
12
+ "ttlMinutes": 60,
13
+ "maxSessionBytes": 33554432
14
+ }
15
+ }
@@ -8,8 +8,9 @@ and aggregate ratios. Asserts:
8
8
  concise <= CONCISE_BUDGET (default 0.60)
9
9
  strict <= STRICT_BUDGET (default 0.40)
10
10
 
11
- Also asserts fact preservation: every file path, identifier, line:number, and
12
- fenced code block from default.md must appear in concise.md and strict.md.
11
+ Also asserts fact preservation: every extracted file path, identifier, line
12
+ reference, and number with a unit from default.md must appear in concise.md
13
+ and strict.md. Fenced code bodies are intentionally excluded.
13
14
 
14
15
  Usage:
15
16
  python3 app/skills/brand-voice/scripts/measure.py \\
@@ -36,6 +37,7 @@ LINE_REF_SUFFIX_RE = re.compile(r":\d+$")
36
37
  CODE_BLOCK_RE = re.compile(r"```[\w]*\n.*?```", re.DOTALL)
37
38
  NUMBER_WITH_UNIT_RE = re.compile(r"\b\d+(?:\.\d+)?(?:ms|s|kb|mb|gb|%)\b", re.IGNORECASE)
38
39
  IDENT_BACKTICK_RE = re.compile(r"`([^`\n]+)`")
40
+ IDENTIFIER_RE = re.compile(r"^[A-Za-z_$][A-Za-z0-9_$.-]*(?:\(\))?$")
39
41
 
40
42
 
41
43
  def count_tokens(text: str) -> int:
@@ -65,7 +67,7 @@ def extract_facts(text: str) -> set[str]:
65
67
 
66
68
  for match in IDENT_BACKTICK_RE.findall(text_no_code):
67
69
  cleaned = match.strip()
68
- if len(cleaned) >= 3 and not cleaned.startswith("```"):
70
+ if len(cleaned) >= 3 and IDENTIFIER_RE.fullmatch(cleaned):
69
71
  facts.add(normalize_path(cleaned))
70
72
 
71
73
  for match in NUMBER_WITH_UNIT_RE.findall(text_no_code):
@@ -138,8 +140,8 @@ def evaluate_fixture(
138
140
 
139
141
  budget_ok_concise = concise_ratio <= concise_budget
140
142
  budget_ok_strict = strict_ratio <= strict_budget
141
- facts_ok_concise = not concise_required_missing
142
- facts_ok_strict = not strict_required_missing
143
+ facts_ok_concise = not concise_missing and not concise_required_missing
144
+ facts_ok_strict = not strict_missing and not strict_required_missing
143
145
 
144
146
  return {
145
147
  "fixture": fixture_dir.name,
@@ -1,5 +1,5 @@
1
1
  {
2
- "last_run": "2026-07-15T05:28:09Z",
2
+ "last_run": "2026-07-24T03:52:58Z",
3
3
  "schema_version": 1,
4
4
  "tools": {
5
5
  "aider": {
@@ -24,7 +24,7 @@
24
24
  }
25
25
  },
26
26
  "augment": {
27
- "docs_hash": "480f89501d47f5ec",
27
+ "docs_hash": "388c0b5e25ab4906",
28
28
  "headings": [
29
29
  "Admin",
30
30
  "Auggie CLI",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "claude-app": {
69
- "docs_hash": "01981c240e67db02",
69
+ "docs_hash": "3c7ff437aec0ab98",
70
70
  "headings": [
71
71
  "Add global and folder instructions",
72
72
  "Availability",
@@ -107,7 +107,7 @@
107
107
  }
108
108
  },
109
109
  "claude-code": {
110
- "docs_hash": "517ae09f5f1feba9",
110
+ "docs_hash": "a6ae93657498688c",
111
111
  "headings": [
112
112
  "Core concepts",
113
113
  "Documentation Index",
@@ -135,15 +135,15 @@
135
135
  "PermissionRequest": false,
136
136
  "PostCompact": false,
137
137
  "PostToolBatch": false,
138
- "PostToolUse": true,
138
+ "PostToolUse": false,
139
139
  "PostToolUseFailure": false,
140
140
  "PreCompact": false,
141
- "PreToolUse": true,
142
- "SKILL.md": true,
141
+ "PreToolUse": false,
142
+ "SKILL.md": false,
143
143
  "SessionEnd": false,
144
144
  "SessionStart": false,
145
145
  "Setup": true,
146
- "Stop": true,
146
+ "Stop": false,
147
147
  "StopFailure": false,
148
148
  "SubagentStart": false,
149
149
  "SubagentStop": false,
@@ -159,14 +159,14 @@
159
159
  "hook handler: http": false,
160
160
  "hook handler: mcp_tool": false,
161
161
  "hook handler: prompt": false,
162
- "output style": true,
163
- "slash command": true,
162
+ "output style": false,
163
+ "slash command": false,
164
164
  "sub-agent": true
165
165
  },
166
- "version": "2.1.210 (Claude Code)"
166
+ "version": "2.1.218 (Claude Code)"
167
167
  },
168
168
  "cline": {
169
- "docs_hash": "9a024e9356105f48",
169
+ "docs_hash": "3eaa20e45d385a1f",
170
170
  "headings": [
171
171
  "API Reference",
172
172
  "Best Practices",
@@ -213,7 +213,7 @@
213
213
  }
214
214
  },
215
215
  "codex-cli": {
216
- "docs_hash": "cc18a01c239497e8",
216
+ "docs_hash": "87a5a879a39dc62c",
217
217
  "headings": [
218
218
  "API",
219
219
  "API Reference",
@@ -291,7 +291,7 @@
291
291
  "Reference",
292
292
  "Releases",
293
293
  "Resources",
294
- "Return to a saved session",
294
+ "Return to a saved chat",
295
295
  "Review changes before they ship",
296
296
  "Run Codex and sign in",
297
297
  "SDKs and CLI",
@@ -346,10 +346,10 @@
346
346
  "mcp_servers": false,
347
347
  "sandbox": true
348
348
  },
349
- "version": "codex-cli 0.144.4"
349
+ "version": "codex-cli 0.145.0"
350
350
  },
351
351
  "cursor": {
352
- "docs_hash": "34efb96589c7f123",
352
+ "docs_hash": "5387d04443d26da1",
353
353
  "headings": [],
354
354
  "markers": {
355
355
  ".cursor/rules": false,
@@ -365,7 +365,7 @@
365
365
  }
366
366
  },
367
367
  "gemini-cli": {
368
- "docs_hash": "c24d346e0c5a1bb9",
368
+ "docs_hash": "8db25b552ffbed80",
369
369
  "headings": [
370
370
  "Breadcrumbs",
371
371
  "Directory actions",
@@ -408,7 +408,7 @@
408
408
  }
409
409
  },
410
410
  "github-copilot": {
411
- "docs_hash": "58d9980c29b7edb8",
411
+ "docs_hash": "5d021df2f3b4d39b",
412
412
  "headings": [
413
413
  "About Copilot auto model selection",
414
414
  "About Copilot automations",
@@ -445,7 +445,7 @@
445
445
  }
446
446
  },
447
447
  "google-antigravity": {
448
- "docs_hash": "9c9b420a22b35ae6",
448
+ "docs_hash": "03145f425dd475d1",
449
449
  "headings": [],
450
450
  "markers": {
451
451
  "AGENTS.md": false,
@@ -461,7 +461,7 @@
461
461
  }
462
462
  },
463
463
  "opencode": {
464
- "docs_hash": "505b9771a6c768e6",
464
+ "docs_hash": "f1dd69ffcfb280fa",
465
465
  "headings": [
466
466
  "Add features",
467
467
  "Ask questions",
@@ -521,7 +521,7 @@
521
521
  }
522
522
  },
523
523
  "windsurf": {
524
- "docs_hash": "2d4eb599c906bf2c",
524
+ "docs_hash": "bee6ba0ee1a517dd",
525
525
  "headings": [
526
526
  "Accounts",
527
527
  "Advanced",
@@ -558,7 +558,7 @@
558
558
  "AGENTS.md": true,
559
559
  "Cascade": true,
560
560
  "MCP": true,
561
- "SKILL.md": true,
561
+ "SKILL.md": false,
562
562
  "always_on": false,
563
563
  "glob": true,
564
564
  "hooks": true,
@@ -0,0 +1,11 @@
1
+ # Native output-filter benchmark corpus
2
+
3
+ The corpus is deterministic, synthetic, offline, and authored for ai-toolkit.
4
+ It measures pure profile transformation separately from hook process startup.
5
+
6
+ The gates are 20 ms p95 for inputs up to 100 KiB, 150 ms p95 for the 8 MiB
7
+ hard-cap case, at least 30% reduction, and peak traced allocation no greater
8
+ than three input sizes plus 16 MiB. The cold-process gate invokes the production
9
+ Bash wrapper with a fresh Python process for every sample in one native session;
10
+ its p95 limit is 75 ms. The default 100 samples keep the p95 gate stable enough
11
+ for release validation.
@@ -0,0 +1,25 @@
1
+ [
2
+ {
3
+ "name": "repeat-lines-100k",
4
+ "profile": "repeat-lines",
5
+ "kind": "repeat",
6
+ "targetBytes": 102400,
7
+ "lineWidth": 96,
8
+ "maxP95Ms": 20
9
+ },
10
+ {
11
+ "name": "tap-success-2k",
12
+ "profile": "tap-success",
13
+ "kind": "tap",
14
+ "testCount": 2000,
15
+ "maxP95Ms": 20
16
+ },
17
+ {
18
+ "name": "repeat-lines-8m",
19
+ "profile": "repeat-lines",
20
+ "kind": "repeat",
21
+ "targetBytes": 8388608,
22
+ "lineWidth": 1024,
23
+ "maxP95Ms": 150
24
+ }
25
+ ]
package/bin/ai-toolkit.js CHANGED
@@ -50,6 +50,7 @@ const SCRIPT_COMMANDS = {
50
50
  'benchmark-ecosystem': { script: 'benchmark_ecosystem.py', toolkitCwd: true },
51
51
  'evaluate': { script: 'evaluate_skills.py', toolkitCwd: true },
52
52
  'stats': { script: 'stats.py' },
53
+ 'output-filter': { script: 'output_filter_cli.py' },
53
54
  'compile-slm': { script: 'compile_slm.py' },
54
55
  'pack-codebase': { script: 'pack_codebase.py' },
55
56
  'claude-app': { script: 'claude_app.py', toolkitCwd: true },
@@ -79,6 +80,7 @@ const COMMANDS = {
79
80
  'benchmark-ecosystem': 'Generate ecosystem benchmark snapshot (GitHub metadata + offline fallback)',
80
81
  evaluate: 'Run skill evaluation suite',
81
82
  stats: 'Show skill usage statistics (--summary for product telemetry, --reset to clear)',
83
+ 'output-filter': 'Manage native tool-output filter (status, inspect, recover, clean)',
82
84
  create: 'Scaffold new skill from template (e.g. create skill my-lint --template=linter)',
83
85
  mcp: 'Manage MCP templates and install native editor MCP configs',
84
86
  config: 'Manage config inheritance (validate, diff, init, create-base, check)',