@softspark/ai-toolkit 4.3.0 → 4.3.2

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.3.2 - quiet hooks and no-RAG search-first hardening (2026-05-21)
11
+
12
+ Patch release. Fixes noisy lifecycle hook output in Codex and Claude prompt-submit flows while preserving search-first enforcement and blocking decisions.
13
+
14
+ ### Fixed
15
+
16
+ - **Quiet Codex startup hooks** - `scripts/generate_codex_hooks.py` now prefixes generated Codex hook commands with `AI_TOOLKIT_HOOK_QUIET=1`, preventing informational `SessionStart` and prompt reminders from appearing as visible hook context.
17
+ - **Quiet Claude prompt-submit hook** - `app/hooks.json` now installs `user-prompt-submit.sh` with `AI_TOOLKIT_HOOK_QUIET=1`, suppressing non-blocking prompt governance output while still arming the per-session search-first flag.
18
+ - **No-RAG false positives** - `_search-capability.sh` now detects search providers from actual MCP server definitions only, so hook matchers and permission allowlists no longer make no-RAG installs block incorrectly.
19
+ - **Codex search tracking gap** - `stop-search-check.sh` now checks the Codex TUI log for search tool calls after the search-first flag timestamp before blocking, covering Codex MCP calls that do not fire the shared `PostToolUse` tracker.
20
+
21
+ ### Changed
22
+
23
+ - **Hook output helper** - `_hook-io.sh` supports `AI_TOOLKIT_HOOK_QUIET=1` for non-blocking context output.
24
+ - **Runtime hook docs** - `kb/reference/hooks-catalog.md` and `kb/reference/codex-cli-compatibility.md` document quiet mode and the Codex search-first fallback.
25
+ - **Manifest hook count** - `manifest.json` now describes the current 28 hook entries across 14 lifecycle events.
26
+
27
+ ### Tests
28
+
29
+ - **Hook quiet-mode coverage** - `tests/test_hooks.bats`, `tests/test_codex.bats`, and `tests/test_install.bats` cover quiet mode for `SessionStart`, `UserPromptSubmit`, `PostToolUse`, Codex hook generation, and installed Claude hook configuration.
30
+ - **Search-first flow coverage** - `tests/test_search_first_flow.bats` covers real-provider detection, no-RAG behavior, custom `customer-rag` style providers, and Codex log fallback.
31
+
32
+ ### Ecosystem
33
+
34
+ - **Snapshot refresh** - `benchmarks/ecosystem-doctor-snapshot.json` refreshed after class A/C upstream documentation and version drift review. No generator contract changes were required.
35
+
36
+ ### Verification
37
+
38
+ - `npm test` - 1142 passing.
39
+ - `python3 scripts/validate.py --strict` - passed.
40
+ - `python3 scripts/audit_skills.py --ci` - passed with 0 HIGH / 0 WARN.
41
+
42
+ ---
43
+
44
+ ## v4.3.1 - per-session search-first flag (2026-05-19)
45
+
46
+ Patch release. Fixes a cross-session race condition in the search-first enforcement trio (`user-prompt-submit.sh` + `search-tracker.sh` + `stop-search-check.sh`): the single global flag file `~/.softspark/ai-toolkit/state/search-required.flag` was shared by every parallel Claude Code window, so a Stop in session B could consume session A's flag (or vice versa), blocking unrelated turns with someone else's prompt. Also unblocks `bats 1.13` regression in the test-cohesion runner default.
47
+
48
+ ### Fixed
49
+
50
+ - **Search-first flag is now per-session** - `user-prompt-submit.sh`, `search-tracker.sh`, and `stop-search-check.sh` key the flag by `session_id` from the hook stdin payload (`search-required-<session_id>.flag`), falling back to `transcript_path` basename, then `default`. Parallel sessions no longer interfere with each other. Each Stop check reads only its own session's flag.
51
+ - **`bats 1.13` regression in test-cohesion runner** - `scripts/test_cohesion.py` no longer passes `--no-parallelize-within-files` (now requires `--jobs 2` in bats 1.13). Default sequential mode is used, which is what the hook expected anyway.
52
+
53
+ ### Added
54
+
55
+ - **`hook_session_id()` helper in `_hook-io.sh`** - Resolves and sanitizes the per-session key for any hook that needs to scope state to a single Claude Code window.
56
+ - **`session-start.sh` GC** - Stale per-session search-required flags older than 60 minutes are deleted on every `SessionStart`, so crashed sessions do not leave residue.
57
+ - **Two new bats tests** in `tests/test_search_first_flow.bats` verifying per-session isolation and that `search-tracker` only clears its own session's flag.
58
+
59
+ ### Changed
60
+
61
+ - **`kb/reference/hooks-catalog.md`** - Documents the new per-session flag layout, GC behavior, and isolation guarantee.
62
+ - **`README.md`** - Test count badge 1131 → 1133.
63
+
64
+ ---
65
+
10
66
  ## v4.3.0 - inject-mcp extension API (2026-05-12)
11
67
 
12
68
  Minor release. Closes the asymmetry between `inject-rule` / `inject-hook` and MCP servers by adding `inject-mcp` and `remove-mcp` as first-class members of the extension API. External tools (rag-mcp, jira-mcp, custom integrations) can now register their MCP templates the same way they register rules and hooks -- from a local file or HTTPS URL, with full editor propagation and auto-refresh on `ai-toolkit update`.
package/README.md CHANGED
@@ -6,16 +6,17 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-107-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1131%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-1142%20passing-success)](tests/)
10
10
 
11
- ## What's New in v4.3.0
11
+ ## What's New in v4.3.2
12
12
 
13
- Minor release. Closes the asymmetry between `inject-rule` / `inject-hook` and MCP servers -- external tools (rag-mcp, jira-mcp, custom integrations) can now register MCP templates the same way they register rules and hooks.
13
+ Patch release. Fixes noisy hook output and hardens search-first behavior across Claude and Codex.
14
14
 
15
- - **`ai-toolkit inject-mcp <file|url>`**: register an external MCP template into `~/.mcp.json` + every editor with a `global_path` (Claude, Cursor, Codex, Gemini, Windsurf, Cline, Augment, Copilot) in one command.
16
- - **`ai-toolkit remove-mcp <name>`**: strip injected servers from `~/.mcp.json` and every editor config in one command.
17
- - **URL templates + auto-refresh**: HTTPS-sourced templates are cached and re-fetched on every `ai-toolkit update`, just like URL rules and hooks.
18
- - **`--name` and `--force` flags**: explicit source naming for both files and URLs, plus collision-safe overwrites that still protect `ai-toolkit` built-in entries.
15
+ - **Quiet hook context**: Codex startup hooks and Claude/Codex `UserPromptSubmit` now suppress non-blocking reminder output while preserving side effects and blocking decisions.
16
+ - **Search provider detection**: search-first hooks now count only real MCP server definitions, not hook matchers or permission allowlists, so no-RAG installs stay advisory instead of blocked.
17
+ - **Codex search-first fallback**: `stop-search-check.sh` recognizes search calls recorded in the Codex TUI log when MCP tool calls do not trigger `PostToolUse`.
18
+ - **Runtime refresh coverage**: installer tests now assert that Claude `UserPromptSubmit` is installed with quiet mode enabled.
19
+ - **Ecosystem snapshot refresh**: release prep refreshed upstream editor/tool drift baselines after class A/C documentation and version drift review.
19
20
 
20
21
  See [CHANGELOG.md](CHANGELOG.md) for full history.
21
22
 
@@ -125,7 +126,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
125
126
  | `skills/` (hybrid) | 30 | Slash commands with agent knowledge base |
126
127
  | `skills/` (knowledge) | 45 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
127
128
  | `agents/` | 44 | Specialized agents across 10 categories |
128
- | `hooks/` | 22 global + 5 skill-scoped | Quality gates, path safety, prompt governance, session lifecycle |
129
+ | `hooks/` | 28 entries / 14 events | Quality gates, path safety, prompt governance, session lifecycle |
129
130
  | `plugins/` | 11 packs | Opt-in domain bundles (security, research, frontend, enterprise, 6 language packs) |
130
131
  | `constitution.md` | 6 articles | Machine-enforced safety rules |
131
132
  | `rules/` | auto-injected | Language-specific and custom rules injected into your configs |
@@ -148,7 +149,7 @@ ai-toolkit/
148
149
  │ └── ARCHITECTURE.md # Full system design
149
150
  ├── kb/ # Reference docs, procedures, plans
150
151
  ├── scripts/ # Validation, install, evaluation scripts
151
- ├── tests/ # Bats test suite (1131 tests)
152
+ ├── tests/ # Bats test suite (1142 tests)
152
153
  └── CHANGELOG.md
153
154
  ```
154
155
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-toolkit",
3
3
  "description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
4
- "version": "4.3.0",
4
+ "version": "4.3.2",
5
5
  "author": {
6
6
  "name": "SoftSpark",
7
7
  "url": "https://github.com/softspark"
@@ -8,7 +8,7 @@ Universal multi-agent system for software development. Works across all reposito
8
8
  |-----------|-------|
9
9
  | Agents | See agents catalog |
10
10
  | Skills | See skills catalog |
11
- | Hooks | 12 events / 22 entries (SessionStart ×3, Notification ×1, PreToolUse ×4, UserPromptSubmit ×2, PostToolUse ×2, Stop ×3, TaskCompleted ×1, TeammateIdle ×1, SubagentStart ×1, SubagentStop ×1, PreCompact ×2, SessionEnd ×1) |
11
+ | Hooks | 14 events / 28 entries (SessionStart ×3, Notification ×1, PreToolUse ×5, UserPromptSubmit ×2, PostToolUse ×4, Stop ×4, TaskCompleted ×1, TeammateIdle ×1, SubagentStart ×1, SubagentStop ×1, PreCompact ×2, SessionEnd ×1, InstructionsLoaded ×1, ConfigChange ×1) |
12
12
 
13
13
  ---
14
14
 
@@ -332,7 +332,9 @@ Lead Session (You)
332
332
  | `UserPromptSubmit` | Before prompt execution | Prompt governance reminder |
333
333
  | `UserPromptSubmit` | Before prompt execution | Usage tracking (skill invocations) |
334
334
  | `PostToolUse` | After edit/write tools | Lightweight validation reminder |
335
+ | `PostToolUse` | After search tools | Clears the per-session search-first flag |
335
336
  | `Stop` | After Claude response | Multi-language quality check + saves session context |
337
+ | `Stop` | After Claude response | Blocks skipped search-first only when a real MCP/Web search provider is configured, with Codex log fallback |
336
338
  | `TaskCompleted` | Teammate marks task done | Multi-language lint + type check (blocking) |
337
339
  | `TeammateIdle` | Teammate goes idle | Reminds to verify completeness |
338
340
  | `SubagentStart` | Subagent starts | Scope reminder for spawned subagents |
@@ -353,6 +355,9 @@ Lead Session (You)
353
355
 
354
356
  Language rules are propagated to **all configured editors** — not just Claude. `dir_rules_shared.build_language_rules()` reads `app/rules/<lang>/*.md`, strips frontmatter, and returns combined content per language. Each directory-based generator (Cursor, Windsurf, Cline, Roo, Augment, Antigravity, Codex) emits `ai-toolkit-lang-<lang>` files in its native format. Registered custom rules (`~/.softspark/ai-toolkit/rules/`) are similarly propagated as `ai-toolkit-custom-<name>` files via `build_registered_rules()`.
355
357
 
358
+ ### Codex Integration
359
+ Codex receives `AGENTS.md`, `.agents/rules/*.md`, optional `.agents/skills/*`, and `.codex/hooks.json`. `generate_codex_hooks.py` emits only Codex-supported lifecycle events and prefixes commands with `AI_TOOLKIT_HOOK_QUIET=1`, so informational hook context is not shown at session start or prompt submit while side effects and blocking Stop decisions still run. Claude's bundled `UserPromptSubmit` hook also runs in quiet mode for the same reason.
360
+
356
361
  ### opencode Integration (v2.5.0+)
357
362
  opencode is the 11th supported editor. Five generators handle its integration surface:
358
363
 
@@ -19,6 +19,20 @@ hook_prompt() {
19
19
  hook_json '.prompt // .tool_info.user_prompt // .user_prompt // .input // empty'
20
20
  }
21
21
 
22
+ hook_session_id() {
23
+ local sid
24
+ sid=$(hook_json '.session_id // empty')
25
+ if [ -z "$sid" ] || [ "$sid" = "null" ]; then
26
+ local tp
27
+ tp=$(hook_json '.transcript_path // empty')
28
+ if [ -n "$tp" ] && [ "$tp" != "null" ]; then
29
+ sid=$(basename "$tp" .jsonl)
30
+ fi
31
+ fi
32
+ [ -z "$sid" ] && sid="default"
33
+ printf '%s' "$sid" | LC_ALL=C tr -c 'a-zA-Z0-9_-' '_'
34
+ }
35
+
22
36
  hook_command() {
23
37
  hook_json '.tool_input.command // .tool_input.command_line // .tool_info.command // .tool_info.command_line // .command // empty'
24
38
  }
@@ -44,6 +58,9 @@ hook_new_content() {
44
58
 
45
59
  hook_emit_context() {
46
60
  local message="$1"
61
+ if [ "${AI_TOOLKIT_HOOK_QUIET:-0}" = "1" ]; then
62
+ return 0
63
+ fi
47
64
  if [ "${AI_TOOLKIT_HOOK_FORMAT:-}" = "json" ]; then
48
65
  jq -nc --arg msg "$message" \
49
66
  '{"hookSpecificOutput":{"additionalContext":$msg},"suppressOutput":true}'
@@ -24,23 +24,71 @@ ai_toolkit_has_search_provider() {
24
24
  [ -n "${AI_TOOLKIT_SEARCH_PROVIDER:-}" ] && return 0
25
25
  [ -n "${CLAUDE_SEARCH_PROVIDER:-}" ] && return 0
26
26
 
27
- local candidates=(
27
+ local json_candidates=(
28
28
  "$PWD/.mcp.json"
29
+ "$PWD/.claude/settings.local.json"
30
+ "$PWD/.claude/settings.json"
29
31
  "$PWD/.claude/mcp.json"
30
32
  "$PWD/.cursor/mcp.json"
31
33
  "$PWD/.gemini/settings.json"
34
+ "$HOME/.mcp.json"
32
35
  "$HOME/.claude.json"
33
36
  "$HOME/.claude/settings.json"
34
- "$HOME/.codex/config.toml"
37
+ "$HOME/.cursor/mcp.json"
35
38
  "$HOME/.gemini/settings.json"
36
39
  )
37
40
  local path
38
- for path in "${candidates[@]}"; do
41
+ for path in "${json_candidates[@]}"; do
42
+ [ -f "$path" ] || continue
43
+ if ai_toolkit_json_has_search_provider "$path"; then
44
+ return 0
45
+ fi
46
+ done
47
+
48
+ local toml_candidates=(
49
+ "$HOME/.codex/config.toml"
50
+ )
51
+ for path in "${toml_candidates[@]}"; do
39
52
  [ -f "$path" ] || continue
40
- if grep -Eqi 'rag[-_]?mcp|smart_query|hybrid_search_kb|crag_search|multi_hop_search|websearch|web-fetch|web_fetch|web_search' "$path"; then
53
+ if ai_toolkit_toml_has_search_provider "$path"; then
41
54
  return 0
42
55
  fi
43
56
  done
44
57
 
45
58
  return 1
46
59
  }
60
+
61
+ ai_toolkit_search_provider_pattern() {
62
+ printf '%s\n' '(^|[^[:alnum:]])(([^[:space:]]*[-_])?rag([-_][^[:space:]]*)?|rag[-_]?mcp|web[-_]?search|search)([^[:alnum:]]|$)'
63
+ }
64
+
65
+ ai_toolkit_json_has_search_provider() {
66
+ local path="$1"
67
+ local pattern
68
+ pattern="$(ai_toolkit_search_provider_pattern)"
69
+
70
+ command -v jq >/dev/null 2>&1 || return 1
71
+ jq -er '
72
+ [
73
+ (.mcpServers // {} | to_entries[]? | .key),
74
+ (.mcp_servers // {} | to_entries[]? | .key),
75
+ (.mcp // {} | to_entries[]? | .key)
76
+ ] | join(" ")
77
+ ' "$path" 2>/dev/null | grep -Eiq "$pattern"
78
+ }
79
+
80
+ ai_toolkit_toml_has_search_provider() {
81
+ local path="$1"
82
+ local pattern
83
+ pattern="$(ai_toolkit_search_provider_pattern)"
84
+
85
+ awk '
86
+ /^\[mcp_servers[."]/ {
87
+ line=$0
88
+ sub(/^\[mcp_servers[."]?/, "", line)
89
+ sub(/"\]$/, "", line)
90
+ sub(/\]$/, "", line)
91
+ print line
92
+ }
93
+ ' "$path" 2>/dev/null | grep -Eiq "$pattern"
94
+ }
@@ -4,6 +4,7 @@
4
4
  # Fires on: PreToolUse (Bash)
5
5
  # Non-blocking: always exits 0 (warnings only).
6
6
 
7
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
7
8
  INPUT=$(cat)
8
9
  # shellcheck source=_hook-io.sh
9
10
  source "$(dirname "$0")/_hook-io.sh"
@@ -13,6 +13,7 @@ LOG_FILE="$LOG_DIR/governance.log"
13
13
  TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
14
14
  SESSION="${CLAUDE_SESSION_ID:-unknown}"
15
15
 
16
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
16
17
  INPUT=$(cat)
17
18
  # shellcheck source=_hook-io.sh
18
19
  source "$(dirname "$0")/_hook-io.sh"
@@ -4,6 +4,7 @@
4
4
  # Fires on: PreToolUse (Edit|Write|MultiEdit)
5
5
  # Exit 2 = block the tool call. Stderr message goes to Claude as feedback.
6
6
 
7
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
7
8
  INPUT=$(cat)
8
9
  # shellcheck source=_hook-io.sh
9
10
  source "$(dirname "$0")/_hook-io.sh"
@@ -4,6 +4,7 @@
4
4
  # Fires on: PreToolUse (Bash)
5
5
  # Exit 2 = block the command. Stderr message goes to Claude as feedback.
6
6
 
7
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
7
8
  INPUT=$(cat)
8
9
  # shellcheck source=_hook-io.sh
9
10
  source "$(dirname "$0")/_hook-io.sh"
@@ -45,7 +45,9 @@ for CONFIG in "${CONFIG_FILES[@]}"; do
45
45
  [ -z "$CMD" ] && continue
46
46
  if ! command -v "$CMD" >/dev/null 2>&1; then
47
47
  HINT=$(get_install_hint "$CMD")
48
- echo "MCP health: ${SERVER_NAME} command not found (${CMD}). Install with: ${HINT}"
48
+ if [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ]; then
49
+ echo "MCP health: ${SERVER_NAME} command not found (${CMD}). Install with: ${HINT}"
50
+ fi
49
51
  fi
50
52
  done <<< "$SERVERS"
51
53
  done
@@ -22,6 +22,7 @@ source "$(dirname "$0")/_locate-toolkit.sh"
22
22
  # shellcheck source=_hook-io.sh
23
23
  source "$(dirname "$0")/_hook-io.sh"
24
24
 
25
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
25
26
  INPUT=$(cat)
26
27
  COMMAND=$(hook_command)
27
28
 
@@ -2,7 +2,7 @@
2
2
  # search-tracker.sh — Clear search-required flag when a search tool runs.
3
3
  #
4
4
  # Fires on: PostToolUse
5
- # Matcher: mcp__rag-mcp__smart_query|mcp__rag-mcp__hybrid_search_kb|mcp__rag-mcp__crag_search|mcp__rag-mcp__multi_hop_search|WebSearch|WebFetch
5
+ # Matcher: mcp__.*__(smart_query|hybrid_search_kb|crag_search|multi_hop_search|verify_answer)|WebSearch|WebFetch
6
6
  # Non-blocking: always exits 0.
7
7
  # Skipped when TOOLKIT_HOOK_PROFILE=minimal.
8
8
  #
@@ -12,7 +12,11 @@
12
12
 
13
13
  # shellcheck source=_profile-check.sh
14
14
  source "$(dirname "$0")/_profile-check.sh"
15
+ # shellcheck source=_hook-io.sh
16
+ source "$(dirname "$0")/_hook-io.sh"
15
17
 
16
- FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
18
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
19
+ INPUT=$(cat 2>/dev/null || true)
20
+ FLAG="$HOME/.softspark/ai-toolkit/state/search-required-$(hook_session_id).flag"
17
21
  rm -f "$FLAG" 2>/dev/null
18
22
  exit 0
@@ -4,13 +4,19 @@
4
4
  # Fires on: SessionStart (startup|compact)
5
5
  # Output goes to Claude's context as plain text.
6
6
 
7
- # 1. Mandatory rules reminder
8
- echo "MANDATORY: Before answering ANY technical question, apply ALL rules from your CLAUDE.md files (global + project). Follow the exact order of operations defined there. Do NOT skip mandatory steps even if you think you already know the answer."
9
- echo "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."
10
-
11
7
  # shellcheck source=_locate-toolkit.sh
12
8
  source "$(dirname "$0")/_locate-toolkit.sh"
13
9
 
10
+ emit_context() {
11
+ if [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ]; then
12
+ printf '%s\n' "$1"
13
+ fi
14
+ }
15
+
16
+ # 1. Mandatory rules reminder
17
+ emit_context "MANDATORY: Before answering ANY technical question, apply ALL rules from your CLAUDE.md files (global + project). Follow the exact order of operations defined there. Do NOT skip mandatory steps even if you think you already know the answer."
18
+ 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."
19
+
14
20
  # 1a. Reset per-session edit state (used by revert-guard, test-cohesion, quality-gate)
15
21
  SESSION_ID_INPUT=""
16
22
  if [ ! -t 0 ]; then
@@ -22,10 +28,14 @@ if [ -n "$TOOLKIT_DIR" ] && command -v python3 >/dev/null 2>&1; then
22
28
  ${SESSION_ID_INPUT:+--session-id "$SESSION_ID_INPUT"} >/dev/null 2>&1 || true
23
29
  fi
24
30
 
31
+ # 1b. GC stale per-session search-required flags (older than 60 min)
32
+ find "$HOME/.softspark/ai-toolkit/state" -maxdepth 1 -name 'search-required-*.flag' \
33
+ -type f -mmin +60 -delete 2>/dev/null || true
34
+
25
35
  # 2. Check for updates (cached, max once per 24h, non-blocking)
26
36
  VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
27
37
  if [ -n "$VERSION_MSG" ]; then
28
- echo "$VERSION_MSG"
38
+ emit_context "$VERSION_MSG"
29
39
  # Strip shell/AppleScript/PowerShell metacharacters before interpolating into
30
40
  # notification commands. VERSION_MSG is version_check.py output which should
31
41
  # be plain ASCII, but sanitize anyway as defense in depth.
@@ -42,20 +52,20 @@ fi
42
52
 
43
53
  # 3. Load session context (if available)
44
54
  SESSION_FILE=".claude/session-context.md"
45
- if [ -f "$SESSION_FILE" ]; then
46
- echo "=== Session Context ==="
55
+ if [ -f "$SESSION_FILE" ] && [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ]; then
56
+ printf '%s\n' "=== Session Context ==="
47
57
  cat "$SESSION_FILE"
48
- echo "====================="
58
+ printf '%s\n' "====================="
49
59
  fi
50
60
 
51
61
  # 3. Load active instincts (if any)
52
62
  INSTINCTS_DIR=".claude/instincts"
53
- if [ -d "$INSTINCTS_DIR" ] && ls "$INSTINCTS_DIR"/*.md >/dev/null 2>&1; then
54
- echo "=== Active Instincts ==="
63
+ if [ -d "$INSTINCTS_DIR" ] && [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ] && ls "$INSTINCTS_DIR"/*.md >/dev/null 2>&1; then
64
+ printf '%s\n' "=== Active Instincts ==="
55
65
  for f in "$INSTINCTS_DIR"/*.md; do
56
- echo "- $(head -1 "$f")"
66
+ printf '%s\n' "- $(head -1 "$f")"
57
67
  done
58
- echo "========================"
68
+ printf '%s\n' "========================"
59
69
  fi
60
70
 
61
71
  exit 0
@@ -21,7 +21,9 @@ source "$(dirname "$0")/_search-capability.sh"
21
21
 
22
22
  [ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" = "1" ] && exit 0
23
23
 
24
- FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
24
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
25
+ INPUT=$(cat 2>/dev/null || true)
26
+ FLAG="$HOME/.softspark/ai-toolkit/state/search-required-$(hook_session_id).flag"
25
27
  [ -f "$FLAG" ] || exit 0
26
28
 
27
29
  if ! ai_toolkit_has_search_provider; then
@@ -29,6 +31,59 @@ if ! ai_toolkit_has_search_provider; then
29
31
  exit 0
30
32
  fi
31
33
 
34
+ ai_toolkit_codex_log_has_search_since() {
35
+ local flag="$1"
36
+ local log="$HOME/.codex/log/codex-tui.log"
37
+ [ -f "$log" ] || return 1
38
+
39
+ python3 - "$flag" "$log" <<'PY' 2>/dev/null
40
+ import re
41
+ import sys
42
+ from datetime import datetime
43
+ from pathlib import Path
44
+
45
+ flag_path = Path(sys.argv[1])
46
+ log_path = Path(sys.argv[2])
47
+ try:
48
+ since = int(flag_path.read_text(encoding="utf-8").splitlines()[0])
49
+ except (OSError, ValueError, IndexError):
50
+ sys.exit(1)
51
+
52
+ tool_pattern = re.compile(
53
+ r"(ToolCall: (mcp__[^ ]*__(smart_query|hybrid_search_kb|crag_search|multi_hop_search|verify_answer)|web_(search|fetch))|"
54
+ r'tool\.name="(smart_query|hybrid_search_kb|crag_search|multi_hop_search|verify_answer)")',
55
+ re.IGNORECASE,
56
+ )
57
+
58
+ try:
59
+ with log_path.open("rb") as handle:
60
+ handle.seek(0, 2)
61
+ size = handle.tell()
62
+ handle.seek(max(0, size - 2_000_000))
63
+ lines = handle.read().decode("utf-8", errors="replace").splitlines()
64
+ except OSError:
65
+ sys.exit(1)
66
+
67
+ for line in lines:
68
+ if not tool_pattern.search(line):
69
+ continue
70
+ raw_ts = line.split(" ", 1)[0]
71
+ try:
72
+ ts = datetime.fromisoformat(raw_ts.replace("Z", "+00:00")).timestamp()
73
+ except ValueError:
74
+ continue
75
+ if ts >= since:
76
+ sys.exit(0)
77
+
78
+ sys.exit(1)
79
+ PY
80
+ }
81
+
82
+ if ai_toolkit_codex_log_has_search_since "$FLAG"; then
83
+ rm -f "$FLAG" 2>/dev/null
84
+ exit 0
85
+ fi
86
+
32
87
  # Read the original prompt (line 2 of the flag file) for the reminder.
33
88
  PROMPT_PREVIEW=$(sed -n '2p' "$FLAG" 2>/dev/null | head -c 200)
34
89
  rm -f "$FLAG" 2>/dev/null # one-shot; do not loop forever
@@ -32,6 +32,7 @@ source "$(dirname "$0")/_hook-io.sh"
32
32
  [ "${CLAUDE_HOOK_BOOTSTRAP:-0}" = "1" ] && exit 0
33
33
  [ "${CLAUDE_SKIP_COHESION:-0}" = "1" ] && exit 0
34
34
 
35
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
35
36
  INPUT=$(cat)
36
37
  FILE_PATH=$(hook_file_path)
37
38
 
@@ -10,6 +10,7 @@
10
10
  STATS_FILE="${HOME}/.softspark/ai-toolkit/stats.json"
11
11
 
12
12
  # Read prompt from stdin (Claude Code passes JSON with .prompt field)
13
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
13
14
  INPUT=$(cat)
14
15
  # shellcheck source=_hook-io.sh
15
16
  source "$(dirname "$0")/_hook-io.sh"
@@ -13,6 +13,7 @@ source "$(dirname "$0")/_hook-io.sh"
13
13
  source "$(dirname "$0")/_search-capability.sh"
14
14
 
15
15
  # Read prompt from stdin (Claude Code passes JSON with .prompt field)
16
+ # shellcheck disable=SC2034 # INPUT is consumed via sourced _hook-io.sh
16
17
  INPUT=$(cat)
17
18
  PROMPT_TEXT=$(hook_prompt)
18
19
  LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
@@ -22,7 +23,7 @@ LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
22
23
  # Cleared by search-tracker.sh after smart_query / hybrid_search_kb / web runs.
23
24
  PROMPT_LEN=${#PROMPT_TEXT}
24
25
  STATE_DIR="$HOME/.softspark/ai-toolkit/state"
25
- FLAG="$STATE_DIR/search-required.flag"
26
+ FLAG="$STATE_DIR/search-required-$(hook_session_id).flag"
26
27
  mkdir -p "$STATE_DIR" 2>/dev/null
27
28
  if [ "$PROMPT_LEN" -gt 30 ] && \
28
29
  [ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" != "1" ] && \
package/app/hooks.json CHANGED
@@ -103,7 +103,7 @@
103
103
  "hooks": [
104
104
  {
105
105
  "type": "command",
106
- "command": "\"$HOME/.softspark/ai-toolkit/hooks/user-prompt-submit.sh\""
106
+ "command": "AI_TOOLKIT_HOOK_QUIET=1 \"$HOME/.softspark/ai-toolkit/hooks/user-prompt-submit.sh\""
107
107
  }
108
108
  ]
109
109
  },
@@ -151,7 +151,7 @@
151
151
  },
152
152
  {
153
153
  "_source": "ai-toolkit",
154
- "matcher": "mcp__rag-mcp__smart_query|mcp__rag-mcp__hybrid_search_kb|mcp__rag-mcp__crag_search|mcp__rag-mcp__multi_hop_search|mcp__rag-mcp__verify_answer|WebSearch|WebFetch",
154
+ "matcher": "mcp__.*__(smart_query|hybrid_search_kb|crag_search|multi_hop_search|verify_answer)|WebSearch|WebFetch",
155
155
  "hooks": [
156
156
  {
157
157
  "type": "command",