@softspark/ai-toolkit 4.3.1 → 4.3.3
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 +59 -0
- package/README.md +9 -9
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/ARCHITECTURE.md +6 -1
- package/app/hooks/_hook-io.sh +4 -1
- package/app/hooks/_search-capability.sh +52 -4
- package/app/hooks/mcp-health.sh +3 -1
- package/app/hooks/search-tracker.sh +1 -1
- package/app/hooks/session-start.sh +18 -12
- package/app/hooks/stop-search-check.sh +53 -0
- package/app/hooks.json +2 -2
- package/benchmarks/ecosystem-doctor-snapshot.json +42 -37
- package/kb/procedures/release-verification-sop.md +7 -5
- package/kb/reference/codex-cli-compatibility.md +15 -2
- package/kb/reference/hooks-catalog.md +26 -8
- package/llms-full.txt +48 -15
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/scripts/generate_codex_hooks.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,65 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.3.3 - silent hook context roll-forward (2026-05-21)
|
|
11
|
+
|
|
12
|
+
Patch release. Rolls forward the quiet-hook release with a stricter default: non-blocking plain-text hook context is now silent even when a runtime uses a stale or manually copied command without `AI_TOOLKIT_HOOK_QUIET=1`.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Silent plain-text context by default** - non-blocking hook context output now requires `AI_TOOLKIT_HOOK_VERBOSE=1` in plain-text mode, so prompt-submit and startup reminders do not leak into the visible chat window.
|
|
17
|
+
- **SessionStart default output** - `session-start.sh` keeps session-state reset, stale search-flag cleanup, and update-notification side effects, but no longer prints startup reminders or loaded context unless verbose mode is enabled.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Hook output helper** - `_hook-io.sh` supports `AI_TOOLKIT_HOOK_VERBOSE=1` for local debugging while keeping plain-text context silent by default.
|
|
22
|
+
- **Runtime hook docs** - `kb/reference/hooks-catalog.md` and `kb/reference/codex-cli-compatibility.md` document the new silent-by-default behavior and verbose opt-in.
|
|
23
|
+
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
- **Silent default coverage** - `tests/test_hooks.bats` now verifies default silence for `SessionStart` and `UserPromptSubmit`, plus verbose opt-in for the same context messages.
|
|
27
|
+
|
|
28
|
+
### Verification
|
|
29
|
+
|
|
30
|
+
- `npm test` - 1144 passing.
|
|
31
|
+
- `python3 scripts/validate.py --strict` - passed.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## v4.3.2 - quiet hooks and no-RAG search-first hardening (2026-05-21)
|
|
36
|
+
|
|
37
|
+
Patch release. Fixes noisy lifecycle hook output in Codex and Claude prompt-submit flows while preserving search-first enforcement and blocking decisions.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **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.
|
|
42
|
+
- **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.
|
|
43
|
+
- **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.
|
|
44
|
+
- **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.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **Hook output helper** - `_hook-io.sh` supports `AI_TOOLKIT_HOOK_QUIET=1` for non-blocking context output.
|
|
49
|
+
- **Runtime hook docs** - `kb/reference/hooks-catalog.md` and `kb/reference/codex-cli-compatibility.md` document quiet mode and the Codex search-first fallback.
|
|
50
|
+
- **Manifest hook count** - `manifest.json` now describes the current 28 hook entries across 14 lifecycle events.
|
|
51
|
+
|
|
52
|
+
### Tests
|
|
53
|
+
|
|
54
|
+
- **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.
|
|
55
|
+
- **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.
|
|
56
|
+
|
|
57
|
+
### Ecosystem
|
|
58
|
+
|
|
59
|
+
- **Snapshot refresh** - `benchmarks/ecosystem-doctor-snapshot.json` refreshed after class A/C upstream documentation and version drift review. No generator contract changes were required.
|
|
60
|
+
|
|
61
|
+
### Verification
|
|
62
|
+
|
|
63
|
+
- `npm test` - 1142 passing.
|
|
64
|
+
- `python3 scripts/validate.py --strict` - passed.
|
|
65
|
+
- `python3 scripts/audit_skills.py --ci` - passed with 0 HIGH / 0 WARN.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
10
69
|
## v4.3.1 - per-session search-first flag (2026-05-19)
|
|
11
70
|
|
|
12
71
|
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.
|
package/README.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
10
|
|
|
11
|
-
## What's New in v4.3.
|
|
11
|
+
## What's New in v4.3.3
|
|
12
12
|
|
|
13
|
-
Patch release.
|
|
13
|
+
Patch release. Rolls forward quiet hook handling so informational hook context stays out of the visible chat window even with stale or manually copied hook commands.
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
-
|
|
18
|
-
- **
|
|
15
|
+
- **Silent hook context by default**: `SessionStart`, `UserPromptSubmit`, and other non-blocking plain-text context output now require `AI_TOOLKIT_HOOK_VERBOSE=1`.
|
|
16
|
+
- **Side effects preserved**: search-first flags, session-state reset, stale-flag cleanup, and blocking decisions still run without printing reminder text.
|
|
17
|
+
- **Runtime refresh applied**: installed hook scripts and Codex/Claude hook config pick up the silent default.
|
|
18
|
+
- **Test coverage updated**: hook tests now cover default silence and verbose opt-in, bringing the suite to 1144 tests.
|
|
19
19
|
|
|
20
20
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
21
21
|
|
|
@@ -125,7 +125,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
|
|
|
125
125
|
| `skills/` (hybrid) | 30 | Slash commands with agent knowledge base |
|
|
126
126
|
| `skills/` (knowledge) | 45 | Domain knowledge auto-loaded by agents (includes 13 `<lang>-rules` skills) |
|
|
127
127
|
| `agents/` | 44 | Specialized agents across 10 categories |
|
|
128
|
-
| `hooks/` |
|
|
128
|
+
| `hooks/` | 28 entries / 14 events | Quality gates, path safety, prompt governance, session lifecycle |
|
|
129
129
|
| `plugins/` | 11 packs | Opt-in domain bundles (security, research, frontend, enterprise, 6 language packs) |
|
|
130
130
|
| `constitution.md` | 6 articles | Machine-enforced safety rules |
|
|
131
131
|
| `rules/` | auto-injected | Language-specific and custom rules injected into your configs |
|
|
@@ -148,7 +148,7 @@ ai-toolkit/
|
|
|
148
148
|
│ └── ARCHITECTURE.md # Full system design
|
|
149
149
|
├── kb/ # Reference docs, procedures, plans
|
|
150
150
|
├── scripts/ # Validation, install, evaluation scripts
|
|
151
|
-
├── tests/ # Bats test suite (
|
|
151
|
+
├── tests/ # Bats test suite (1144 tests)
|
|
152
152
|
└── CHANGELOG.md
|
|
153
153
|
```
|
|
154
154
|
|
|
@@ -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.
|
|
4
|
+
"version": "4.3.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "SoftSpark",
|
|
7
7
|
"url": "https://github.com/softspark"
|
package/app/ARCHITECTURE.md
CHANGED
|
@@ -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 |
|
|
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
|
|
package/app/hooks/_hook-io.sh
CHANGED
|
@@ -58,10 +58,13 @@ hook_new_content() {
|
|
|
58
58
|
|
|
59
59
|
hook_emit_context() {
|
|
60
60
|
local message="$1"
|
|
61
|
+
if [ "${AI_TOOLKIT_HOOK_QUIET:-0}" = "1" ]; then
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
61
64
|
if [ "${AI_TOOLKIT_HOOK_FORMAT:-}" = "json" ]; then
|
|
62
65
|
jq -nc --arg msg "$message" \
|
|
63
66
|
'{"hookSpecificOutput":{"additionalContext":$msg},"suppressOutput":true}'
|
|
64
|
-
|
|
67
|
+
elif [ "${AI_TOOLKIT_HOOK_VERBOSE:-0}" = "1" ]; then
|
|
65
68
|
printf '%s\n' "$message"
|
|
66
69
|
fi
|
|
67
70
|
}
|
|
@@ -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
|
|
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/.
|
|
37
|
+
"$HOME/.cursor/mcp.json"
|
|
35
38
|
"$HOME/.gemini/settings.json"
|
|
36
39
|
)
|
|
37
40
|
local path
|
|
38
|
-
for path in "${
|
|
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
|
|
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
|
+
}
|
package/app/hooks/mcp-health.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -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:
|
|
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
|
#
|
|
@@ -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
|
+
[ "${AI_TOOLKIT_HOOK_QUIET:-0}" = "1" ] && return 0
|
|
12
|
+
[ "${AI_TOOLKIT_HOOK_VERBOSE:-0}" != "1" ] && return 0
|
|
13
|
+
printf '%s\n' "$1"
|
|
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
|
|
@@ -29,7 +35,7 @@ find "$HOME/.softspark/ai-toolkit/state" -maxdepth 1 -name 'search-required-*.fl
|
|
|
29
35
|
# 2. Check for updates (cached, max once per 24h, non-blocking)
|
|
30
36
|
VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
|
|
31
37
|
if [ -n "$VERSION_MSG" ]; then
|
|
32
|
-
|
|
38
|
+
emit_context "$VERSION_MSG"
|
|
33
39
|
# Strip shell/AppleScript/PowerShell metacharacters before interpolating into
|
|
34
40
|
# notification commands. VERSION_MSG is version_check.py output which should
|
|
35
41
|
# be plain ASCII, but sanitize anyway as defense in depth.
|
|
@@ -46,20 +52,20 @@ fi
|
|
|
46
52
|
|
|
47
53
|
# 3. Load session context (if available)
|
|
48
54
|
SESSION_FILE=".claude/session-context.md"
|
|
49
|
-
if [ -f "$SESSION_FILE" ]; then
|
|
50
|
-
|
|
55
|
+
if [ -f "$SESSION_FILE" ] && [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ] && [ "${AI_TOOLKIT_HOOK_VERBOSE:-0}" = "1" ]; then
|
|
56
|
+
printf '%s\n' "=== Session Context ==="
|
|
51
57
|
cat "$SESSION_FILE"
|
|
52
|
-
|
|
58
|
+
printf '%s\n' "====================="
|
|
53
59
|
fi
|
|
54
60
|
|
|
55
61
|
# 3. Load active instincts (if any)
|
|
56
62
|
INSTINCTS_DIR=".claude/instincts"
|
|
57
|
-
if [ -d "$INSTINCTS_DIR" ] && ls "$INSTINCTS_DIR"/*.md >/dev/null 2>&1; then
|
|
58
|
-
|
|
63
|
+
if [ -d "$INSTINCTS_DIR" ] && [ "${AI_TOOLKIT_HOOK_QUIET:-0}" != "1" ] && [ "${AI_TOOLKIT_HOOK_VERBOSE:-0}" = "1" ] && ls "$INSTINCTS_DIR"/*.md >/dev/null 2>&1; then
|
|
64
|
+
printf '%s\n' "=== Active Instincts ==="
|
|
59
65
|
for f in "$INSTINCTS_DIR"/*.md; do
|
|
60
|
-
|
|
66
|
+
printf '%s\n' "- $(head -1 "$f")"
|
|
61
67
|
done
|
|
62
|
-
|
|
68
|
+
printf '%s\n' "========================"
|
|
63
69
|
fi
|
|
64
70
|
|
|
65
71
|
exit 0
|
|
@@ -31,6 +31,59 @@ if ! ai_toolkit_has_search_provider; then
|
|
|
31
31
|
exit 0
|
|
32
32
|
fi
|
|
33
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
|
+
|
|
34
87
|
# Read the original prompt (line 2 of the flag file) for the reminder.
|
|
35
88
|
PROMPT_PREVIEW=$(sed -n '2p' "$FLAG" 2>/dev/null | head -c 200)
|
|
36
89
|
rm -f "$FLAG" 2>/dev/null # one-shot; do not loop forever
|
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": "
|
|
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",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"last_run": "2026-05-
|
|
2
|
+
"last_run": "2026-05-21T08:27:23Z",
|
|
3
3
|
"schema_version": 1,
|
|
4
4
|
"tools": {
|
|
5
5
|
"aider": {
|
|
@@ -24,17 +24,18 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"augment": {
|
|
27
|
-
"docs_hash": "
|
|
27
|
+
"docs_hash": "8be23f4fb78816c3",
|
|
28
28
|
"headings": [
|
|
29
|
-
"
|
|
30
|
-
"Chat",
|
|
29
|
+
"Code",
|
|
31
30
|
"Documentation Index",
|
|
32
31
|
"Introduction",
|
|
32
|
+
"On this page",
|
|
33
|
+
"Other automation",
|
|
34
|
+
"Review",
|
|
33
35
|
"\u200bAuggie CLI",
|
|
34
|
-
"\
|
|
35
|
-
"\
|
|
36
|
-
"\
|
|
37
|
-
"\u200bVisual Studio Code"
|
|
36
|
+
"\u200bAutomate your SDLC",
|
|
37
|
+
"\u200bCosmos",
|
|
38
|
+
"\u200bGet started in minutes"
|
|
38
39
|
],
|
|
39
40
|
"markers": {
|
|
40
41
|
".augment": true,
|
|
@@ -57,10 +58,11 @@
|
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
60
|
"claude-code": {
|
|
60
|
-
"docs_hash": "
|
|
61
|
+
"docs_hash": "0518086bd4527849",
|
|
61
62
|
"headings": [
|
|
62
|
-
"Claude Code overview",
|
|
63
63
|
"Documentation Index",
|
|
64
|
+
"On this page",
|
|
65
|
+
"Overview",
|
|
64
66
|
"\u200bGet started",
|
|
65
67
|
"\u200bNext steps",
|
|
66
68
|
"\u200bUse Claude Code everywhere",
|
|
@@ -103,24 +105,27 @@
|
|
|
103
105
|
"slash command": true,
|
|
104
106
|
"sub-agent": true
|
|
105
107
|
},
|
|
106
|
-
"version": "2.1.
|
|
108
|
+
"version": "2.1.146 (Claude Code)"
|
|
107
109
|
},
|
|
108
110
|
"cline": {
|
|
109
|
-
"docs_hash": "
|
|
111
|
+
"docs_hash": "c2ce2443743365fa",
|
|
110
112
|
"headings": [
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"Customization",
|
|
113
|
+
"API Reference",
|
|
114
|
+
"CLI",
|
|
115
|
+
"Cline Overview",
|
|
115
116
|
"Documentation Index",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
117
|
+
"JetBrains Plugin",
|
|
118
|
+
"Kanban",
|
|
119
|
+
"Observability",
|
|
120
|
+
"On this page",
|
|
121
|
+
"SDK",
|
|
122
|
+
"Security & Governance",
|
|
123
|
+
"Team Management",
|
|
124
|
+
"VS Code Extension",
|
|
125
|
+
"\u200bAgent Core (SDK)",
|
|
126
|
+
"\u200bApplications",
|
|
127
|
+
"\u200bEnterprise Solutions",
|
|
128
|
+
"\u200bOther IDE Supports",
|
|
124
129
|
"\u200bWhat is Cline?"
|
|
125
130
|
],
|
|
126
131
|
"markers": {
|
|
@@ -137,7 +142,7 @@
|
|
|
137
142
|
}
|
|
138
143
|
},
|
|
139
144
|
"codex-cli": {
|
|
140
|
-
"docs_hash": "
|
|
145
|
+
"docs_hash": "70f184b6cdd1f765",
|
|
141
146
|
"headings": [
|
|
142
147
|
"About",
|
|
143
148
|
"Contributing",
|
|
@@ -156,7 +161,7 @@
|
|
|
156
161
|
"Packages 0",
|
|
157
162
|
"Provide feedback",
|
|
158
163
|
"Quickstart",
|
|
159
|
-
"Releases
|
|
164
|
+
"Releases 795",
|
|
160
165
|
"Repository files navigation",
|
|
161
166
|
"Resources",
|
|
162
167
|
"Saved searches",
|
|
@@ -186,10 +191,10 @@
|
|
|
186
191
|
"mcp_servers": false,
|
|
187
192
|
"sandbox": true
|
|
188
193
|
},
|
|
189
|
-
"version": "codex-cli 0.
|
|
194
|
+
"version": "codex-cli 0.132.0"
|
|
190
195
|
},
|
|
191
196
|
"cursor": {
|
|
192
|
-
"docs_hash": "
|
|
197
|
+
"docs_hash": "60edb30c9ddcdadd",
|
|
193
198
|
"headings": [],
|
|
194
199
|
"markers": {
|
|
195
200
|
".cursor/rules": false,
|
|
@@ -205,7 +210,7 @@
|
|
|
205
210
|
}
|
|
206
211
|
},
|
|
207
212
|
"gemini-cli": {
|
|
208
|
-
"docs_hash": "
|
|
213
|
+
"docs_hash": "c6aab087667f7717",
|
|
209
214
|
"headings": [
|
|
210
215
|
"Breadcrumbs",
|
|
211
216
|
"Directory actions",
|
|
@@ -244,16 +249,16 @@
|
|
|
244
249
|
}
|
|
245
250
|
},
|
|
246
251
|
"github-copilot": {
|
|
247
|
-
"docs_hash": "
|
|
252
|
+
"docs_hash": "ab274112faeec90f",
|
|
248
253
|
"headings": [
|
|
249
254
|
"About Copilot auto model selection",
|
|
250
255
|
"About Copilot integrations",
|
|
251
256
|
"About agent management",
|
|
252
257
|
"About agent skills",
|
|
253
|
-
"About agentic memory for GitHub Copilot",
|
|
254
258
|
"About billing for GitHub Copilot in organizations and enterprises",
|
|
255
259
|
"About billing for individual GitHub Copilot plans",
|
|
256
260
|
"About custom agents",
|
|
261
|
+
"About customizing GitHub Copilot responses",
|
|
257
262
|
"Articles",
|
|
258
263
|
"Did you find what you needed?",
|
|
259
264
|
"GitHub Copilot",
|
|
@@ -276,7 +281,7 @@
|
|
|
276
281
|
}
|
|
277
282
|
},
|
|
278
283
|
"google-antigravity": {
|
|
279
|
-
"docs_hash": "
|
|
284
|
+
"docs_hash": "2c48c702b9e592a7",
|
|
280
285
|
"headings": [],
|
|
281
286
|
"markers": {
|
|
282
287
|
"AGENTS.md": false,
|
|
@@ -292,7 +297,7 @@
|
|
|
292
297
|
}
|
|
293
298
|
},
|
|
294
299
|
"opencode": {
|
|
295
|
-
"docs_hash": "
|
|
300
|
+
"docs_hash": "5277673e235393f7",
|
|
296
301
|
"headings": [
|
|
297
302
|
"Add features",
|
|
298
303
|
"Ask questions",
|
|
@@ -331,20 +336,19 @@
|
|
|
331
336
|
}
|
|
332
337
|
},
|
|
333
338
|
"roo-code": {
|
|
334
|
-
"docs_hash": "
|
|
339
|
+
"docs_hash": "54908ae23136b646",
|
|
335
340
|
"headings": [
|
|
336
341
|
"A mindset for success\u200b",
|
|
337
342
|
"Additional Resources\u200b",
|
|
338
343
|
"Getting Started\u200b",
|
|
339
344
|
"Roo Code Docs",
|
|
340
|
-
"Tutorial & Feature Videos\u200b",
|
|
341
345
|
"What can Roo Code do for you\u200b"
|
|
342
346
|
],
|
|
343
347
|
"markers": {
|
|
344
348
|
"Code Actions": false,
|
|
345
349
|
"MCP": true,
|
|
346
350
|
"Orchestrator mode": false,
|
|
347
|
-
"custom modes":
|
|
351
|
+
"custom modes": false,
|
|
348
352
|
"description": true,
|
|
349
353
|
"groups": false,
|
|
350
354
|
"roleDefinition": false,
|
|
@@ -353,7 +357,7 @@
|
|
|
353
357
|
}
|
|
354
358
|
},
|
|
355
359
|
"windsurf": {
|
|
356
|
-
"docs_hash": "
|
|
360
|
+
"docs_hash": "fa29ba621f291d81",
|
|
357
361
|
"headings": [
|
|
358
362
|
"Advanced",
|
|
359
363
|
"App Deploys",
|
|
@@ -362,6 +366,7 @@
|
|
|
362
366
|
"Documentation Index",
|
|
363
367
|
"MCP",
|
|
364
368
|
"Memories",
|
|
369
|
+
"On this page",
|
|
365
370
|
"Recommended Plugins",
|
|
366
371
|
"Terminal",
|
|
367
372
|
"Usage",
|
|
@@ -3,10 +3,10 @@ title: "SOP: Release Verification"
|
|
|
3
3
|
category: procedures
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [sop, verification, release, smoke-test, install, update, qa, provenance, sarif]
|
|
6
|
-
version: "1.4.
|
|
6
|
+
version: "1.4.3"
|
|
7
7
|
created: "2026-04-08"
|
|
8
|
-
last_updated: "2026-
|
|
9
|
-
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard. v1.3.0 added the single-run npm test discipline; v1.4.0 adds v3.0.0 deep-coverage checks (--profile full, --codex-skills, breaking-change surfaces, idempotence, registry drift, live-JSON parse) and refreshes stale thresholds. v1.4.2 makes the Phase 9.4 idempotence check deterministic by sorting file paths before hashing."
|
|
8
|
+
last_updated: "2026-05-19"
|
|
9
|
+
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard. v1.3.0 added the single-run npm test discipline; v1.4.0 adds v3.0.0 deep-coverage checks (--profile full, --codex-skills, breaking-change surfaces, idempotence, registry drift, live-JSON parse) and refreshes stale thresholds. v1.4.2 makes the Phase 9.4 idempotence check deterministic by sorting file paths before hashing. v1.4.3 tightens the Phase 8.4 URL pin check so the success-message count includes only entries with a `url:` field, not local `path:` entries, and documents the `sources.json` envelope shape."
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# SOP: Release Verification
|
|
@@ -303,11 +303,13 @@ python3 scripts/audit_skills.py --permissions | head -40
|
|
|
303
303
|
|
|
304
304
|
### 8.4 URL-sourced rules/hooks are checksum-pinned
|
|
305
305
|
|
|
306
|
+
`sources.json` is an envelope of the form `{"schema_version": 1, "rules"|"hooks": {...}}`, so the jq filter must pick the nested map before piping into the pin assertion. The URL-count in the success message ignores local `path:`-only entries — only entries with a `url:` field are pinned and counted.
|
|
307
|
+
|
|
306
308
|
```bash
|
|
307
309
|
jq '.rules // .hooks // {}' ~/.softspark/ai-toolkit/rules/sources.json 2>/dev/null \
|
|
308
|
-
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; print(f'RULE PIN OK: {
|
|
310
|
+
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; url_n=sum(1 for v in d.values() if v.get('url')); print(f'RULE PIN OK: {url_n} URL rules, all with sha256')"
|
|
309
311
|
jq '.hooks // {}' ~/.softspark/ai-toolkit/hooks/external/sources.json 2>/dev/null \
|
|
310
|
-
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; print(f'HOOK PIN OK: {
|
|
312
|
+
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; url_n=sum(1 for v in d.values() if v.get('url')); print(f'HOOK PIN OK: {url_n} URL hooks, all with sha256')"
|
|
311
313
|
```
|
|
312
314
|
|
|
313
315
|
**Verify:**
|
|
@@ -3,9 +3,9 @@ title: "AI Toolkit - Codex CLI Compatibility"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [codex, compatibility, install, skills, hooks]
|
|
6
|
-
version: "1.0.
|
|
6
|
+
version: "1.0.2"
|
|
7
7
|
created: "2026-04-12"
|
|
8
|
-
last_updated: "2026-
|
|
8
|
+
last_updated: "2026-05-21"
|
|
9
9
|
description: "Reference for how ai-toolkit maps Claude-oriented skills, hooks, and plugin packs to Codex CLI."
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -110,6 +110,15 @@ This means Claude-only events such as `TaskCompleted`, `TeammateIdle`,
|
|
|
110
110
|
`~/.codex/hooks.json` (global layer). Non-Codex events are silently skipped.
|
|
111
111
|
`remove-hook` cleans both Claude and Codex targets.
|
|
112
112
|
|
|
113
|
+
Generated Codex hook commands include `AI_TOOLKIT_HOOK_QUIET=1`. This keeps
|
|
114
|
+
non-blocking reminders and startup context out of the visible Codex hook output
|
|
115
|
+
while preserving hook side effects and blocking decisions such as search-first
|
|
116
|
+
Stop enforcement.
|
|
117
|
+
|
|
118
|
+
Plain-text informational hook context is also silent by default in the shared
|
|
119
|
+
hook helper. Set `AI_TOOLKIT_HOOK_VERBOSE=1` only when debugging hook output
|
|
120
|
+
outside the Codex UI.
|
|
121
|
+
|
|
113
122
|
## Behavioral Limits
|
|
114
123
|
|
|
115
124
|
Codex wrappers preserve workflow intent, but not every Claude runtime behavior
|
|
@@ -120,6 +129,10 @@ Known limits:
|
|
|
120
129
|
- No native Codex equivalent of tmux-backed Agent Teams lifecycle events
|
|
121
130
|
- No separate task object model equivalent to Claude `Task*` APIs
|
|
122
131
|
- Hook event coverage is narrower than Claude Code
|
|
132
|
+
- MCP search tool calls may not fire the shared `PostToolUse` search tracker,
|
|
133
|
+
so `stop-search-check.sh` also checks `~/.codex/log/codex-tui.log` for
|
|
134
|
+
`smart_query`, `hybrid_search_kb`, `crag_search`, `multi_hop_search`, and
|
|
135
|
+
`verify_answer` calls after the search-first flag timestamp before blocking.
|
|
123
136
|
|
|
124
137
|
These are runtime platform limits, not installation defects.
|
|
125
138
|
|
|
@@ -3,9 +3,9 @@ title: "Hooks Catalog"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [hooks, quality, safety, enforcement, settings.json]
|
|
6
|
-
version: "1.5.
|
|
6
|
+
version: "1.5.3"
|
|
7
7
|
created: "2026-03-27"
|
|
8
|
-
last_updated: "2026-05-
|
|
8
|
+
last_updated: "2026-05-21"
|
|
9
9
|
description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -50,6 +50,11 @@ ai-toolkit update # re-copies scripts, re-merges (idempotent)
|
|
|
50
50
|
3. Loads session context from `.claude/session-context.md` (if exists)
|
|
51
51
|
4. Loads active instincts from `.claude/instincts/*.md` (if any)
|
|
52
52
|
|
|
53
|
+
By default the hook performs session-state reset, stale search-flag cleanup, and
|
|
54
|
+
update notification side effects without printing informational stdout. Set
|
|
55
|
+
`AI_TOOLKIT_HOOK_VERBOSE=1` to print the startup reminders and loaded context
|
|
56
|
+
for debugging; `AI_TOOLKIT_HOOK_QUIET=1` keeps it silent explicitly.
|
|
57
|
+
|
|
53
58
|
### Notification — `notify-waiting.sh`
|
|
54
59
|
|
|
55
60
|
| Field | Value |
|
|
@@ -102,9 +107,16 @@ ai-toolkit update # re-copies scripts, re-merges (idempotent)
|
|
|
102
107
|
| Script | `~/.softspark/ai-toolkit/hooks/user-prompt-submit.sh` |
|
|
103
108
|
| Fires | Before Claude starts working on a submitted prompt |
|
|
104
109
|
|
|
105
|
-
**Action:**
|
|
110
|
+
**Action:** Maintains the per-session search-first flag used by Stop enforcement
|
|
111
|
+
and can provide a lightweight governance reminder: plan mode for architectural
|
|
112
|
+
work, evidence-first debugging, KB-first research, and validation expectations.
|
|
106
113
|
|
|
107
|
-
Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
114
|
+
Skipped when `TOOLKIT_HOOK_PROFILE=minimal`. The bundled `app/hooks.json`
|
|
115
|
+
registers this command with `AI_TOOLKIT_HOOK_QUIET=1`, so it still arms or
|
|
116
|
+
clears the per-session search-first flag but suppresses the informational
|
|
117
|
+
reminder output. In plain-text mode, informational reminders are silent by
|
|
118
|
+
default and require `AI_TOOLKIT_HOOK_VERBOSE=1`; JSON hook output remains
|
|
119
|
+
available with `AI_TOOLKIT_HOOK_FORMAT=json`.
|
|
108
120
|
|
|
109
121
|
### UserPromptSubmit (usage tracking) — `track-usage.sh`
|
|
110
122
|
|
|
@@ -377,11 +389,11 @@ First-match-wins per file. Built-in runners: `bats`, `pytest`, `vitest`, `jest`.
|
|
|
377
389
|
| Field | Value |
|
|
378
390
|
|-------|-------|
|
|
379
391
|
| Event | `PostToolUse` |
|
|
380
|
-
| Matcher | `
|
|
392
|
+
| Matcher | `mcp__.*__(smart_query\|hybrid_search_kb\|crag_search\|multi_hop_search\|verify_answer)\|WebSearch\|WebFetch` |
|
|
381
393
|
| Script | `~/.softspark/ai-toolkit/hooks/search-tracker.sh` |
|
|
382
394
|
| Fires | After any search-style tool call |
|
|
383
395
|
|
|
384
|
-
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required-<session_id>.flag` (per-session, keyed by `session_id` from the hook stdin payload, falling back to `transcript_path` basename, then `default`). Pairs with `user-prompt-submit.sh` (sets the flag on long technical prompts only when a search provider is detected or strict mode is enabled) and `stop-search-check.sh` (blocks Stop if the calling session's flag is still set). Together
|
|
396
|
+
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required-<session_id>.flag` (per-session, keyed by `session_id` from the hook stdin payload, falling back to `transcript_path` basename, then `default`). Pairs with `user-prompt-submit.sh` (sets the flag on long technical prompts only when a search provider is detected or strict mode is enabled) and `stop-search-check.sh` (blocks Stop if the calling session's flag is still set). Search provider detection parses actual MCP server names from `mcpServers`, `mcp_servers`, or `mcp` config blocks; hook matchers and permission allowlists do not count as providers. Together the hooks enforce the global CLAUDE.md GOLDEN RULE without breaking offline/no-RAG installs and without cross-session interference when multiple Claude Code windows run in parallel.
|
|
385
397
|
|
|
386
398
|
Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
387
399
|
|
|
@@ -394,7 +406,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
394
406
|
| Script | `~/.softspark/ai-toolkit/hooks/stop-search-check.sh` |
|
|
395
407
|
| Fires | When Claude finishes a response |
|
|
396
408
|
|
|
397
|
-
**Action:** If `search-required-<session_id>.flag` for the calling session is still present (no search tool ran during this turn) and a search provider is still detectable, emits `{"decision":"block","reason":"..."}` to continue the conversation with a search-first reminder. If no RAG/Web provider is detected, it clears the stale flag and exits 0, so offline/no-MCP users are not blocked. Flags are scoped by `session_id` from the hook stdin payload so a Stop in session B never consumes session A's flag (and vice versa). Stale per-session flags older than 60 minutes are GC'd on the next `SessionStart`.
|
|
409
|
+
**Action:** If `search-required-<session_id>.flag` for the calling session is still present (no search tool ran during this turn) and a search provider is still detectable, emits `{"decision":"block","reason":"..."}` to continue the conversation with a search-first reminder. If no RAG/Web provider is detected, it clears the stale flag and exits 0, so offline/no-MCP users are not blocked. On Codex, where MCP search tools may not trigger the shared `PostToolUse` tracker, the hook also checks `~/.codex/log/codex-tui.log` for search tool calls after the flag timestamp before blocking. Flags are scoped by `session_id` from the hook stdin payload so a Stop in session B never consumes session A's flag (and vice versa). Stale per-session flags older than 60 minutes are GC'd on the next `SessionStart`.
|
|
398
410
|
|
|
399
411
|
**Overrides:** `CLAUDE_SKIP_SEARCH_FIRST=1`, `AI_TOOLKIT_SEARCH_FIRST=off`, or `AI_TOOLKIT_SEARCH_FIRST=strict` to force enforcement. Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
400
412
|
|
|
@@ -432,7 +444,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
432
444
|
| `scripts/test_cohesion.py` | Resolves changed paths → test commands via cohesion map. First-match-wins. Stdlib-only. |
|
|
433
445
|
| `app/hooks/test-cohesion-map.json` | Toolkit-default path → tests mapping (used when no project map exists). |
|
|
434
446
|
| `app/hooks/_locate-toolkit.sh` | Shared bash helper that exports `$TOOLKIT_DIR` for hooks needing scripts/. |
|
|
435
|
-
| `app/hooks/_hook-io.sh` | Shared bash helper that normalizes hook payloads across Claude, Augment, Gemini, Windsurf, and Cursor-style JSON. |
|
|
447
|
+
| `app/hooks/_hook-io.sh` | Shared bash helper that normalizes hook payloads across Claude, Augment, Gemini, Windsurf, and Cursor-style JSON. Honors `AI_TOOLKIT_HOOK_QUIET=1` and `AI_TOOLKIT_HOOK_VERBOSE=1` for non-blocking context output. |
|
|
436
448
|
| `app/hooks/_search-capability.sh` | Shared bash helper that enables search-first blocking only when RAG/Web is configured or strict mode is requested. |
|
|
437
449
|
|
|
438
450
|
## Runtime Profiles
|
|
@@ -449,6 +461,12 @@ Set in `.claude/settings.local.json`:
|
|
|
449
461
|
| `standard` | All hooks (default) |
|
|
450
462
|
| `strict` | Standard + mypy --strict on task completion |
|
|
451
463
|
|
|
464
|
+
Non-blocking informational context is silent in plain-text mode by default while
|
|
465
|
+
side effects and blocking decisions still run. Set `AI_TOOLKIT_HOOK_VERBOSE=1`
|
|
466
|
+
only when debugging hook context locally. `AI_TOOLKIT_HOOK_QUIET=1` keeps hook
|
|
467
|
+
commands explicitly silent, and Codex-generated hooks plus Claude's bundled
|
|
468
|
+
`UserPromptSubmit` entry use it to avoid visible prompt hook context.
|
|
469
|
+
|
|
452
470
|
## Architecture
|
|
453
471
|
|
|
454
472
|
```
|
package/llms-full.txt
CHANGED
|
@@ -5250,10 +5250,10 @@ title: "SOP: Release Verification"
|
|
|
5250
5250
|
category: procedures
|
|
5251
5251
|
service: ai-toolkit
|
|
5252
5252
|
tags: [sop, verification, release, smoke-test, install, update, qa, provenance, sarif]
|
|
5253
|
-
version: "1.4.
|
|
5253
|
+
version: "1.4.3"
|
|
5254
5254
|
created: "2026-04-08"
|
|
5255
|
-
last_updated: "2026-
|
|
5256
|
-
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard. v1.3.0 added the single-run npm test discipline; v1.4.0 adds v3.0.0 deep-coverage checks (--profile full, --codex-skills, breaking-change surfaces, idempotence, registry drift, live-JSON parse) and refreshes stale thresholds. v1.4.2 makes the Phase 9.4 idempotence check deterministic by sorting file paths before hashing."
|
|
5255
|
+
last_updated: "2026-05-19"
|
|
5256
|
+
description: "End-to-end smoke test after installing or updating @softspark/ai-toolkit — verifies CLI, install, doctor, validation, tests, eject, npm provenance attestation, SARIF audit, and per-skill permissions. Reflects the v2.8.0 supply-chain standard. v1.3.0 added the single-run npm test discipline; v1.4.0 adds v3.0.0 deep-coverage checks (--profile full, --codex-skills, breaking-change surfaces, idempotence, registry drift, live-JSON parse) and refreshes stale thresholds. v1.4.2 makes the Phase 9.4 idempotence check deterministic by sorting file paths before hashing. v1.4.3 tightens the Phase 8.4 URL pin check so the success-message count includes only entries with a `url:` field, not local `path:` entries, and documents the `sources.json` envelope shape."
|
|
5257
5257
|
---
|
|
5258
5258
|
|
|
5259
5259
|
# SOP: Release Verification
|
|
@@ -5550,11 +5550,13 @@ python3 scripts/audit_skills.py --permissions | head -40
|
|
|
5550
5550
|
|
|
5551
5551
|
### 8.4 URL-sourced rules/hooks are checksum-pinned
|
|
5552
5552
|
|
|
5553
|
+
`sources.json` is an envelope of the form `{"schema_version": 1, "rules"|"hooks": {...}}`, so the jq filter must pick the nested map before piping into the pin assertion. The URL-count in the success message ignores local `path:`-only entries — only entries with a `url:` field are pinned and counted.
|
|
5554
|
+
|
|
5553
5555
|
```bash
|
|
5554
5556
|
jq '.rules // .hooks // {}' ~/.softspark/ai-toolkit/rules/sources.json 2>/dev/null \
|
|
5555
|
-
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; print(f'RULE PIN OK: {
|
|
5557
|
+
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; url_n=sum(1 for v in d.values() if v.get('url')); print(f'RULE PIN OK: {url_n} URL rules, all with sha256')"
|
|
5556
5558
|
jq '.hooks // {}' ~/.softspark/ai-toolkit/hooks/external/sources.json 2>/dev/null \
|
|
5557
|
-
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; print(f'HOOK PIN OK: {
|
|
5559
|
+
| python3 -c "import json, sys; d=json.load(sys.stdin) or {}; bad=[n for n,v in d.items() if v.get('url') and not v.get('sha256')]; assert not bad, f'UNPINNED: {bad}'; url_n=sum(1 for v in d.values() if v.get('url')); print(f'HOOK PIN OK: {url_n} URL hooks, all with sha256')"
|
|
5558
5560
|
```
|
|
5559
5561
|
|
|
5560
5562
|
**Verify:**
|
|
@@ -6926,9 +6928,9 @@ title: "AI Toolkit - Codex CLI Compatibility"
|
|
|
6926
6928
|
category: reference
|
|
6927
6929
|
service: ai-toolkit
|
|
6928
6930
|
tags: [codex, compatibility, install, skills, hooks]
|
|
6929
|
-
version: "1.0.
|
|
6931
|
+
version: "1.0.2"
|
|
6930
6932
|
created: "2026-04-12"
|
|
6931
|
-
last_updated: "2026-
|
|
6933
|
+
last_updated: "2026-05-21"
|
|
6932
6934
|
description: "Reference for how ai-toolkit maps Claude-oriented skills, hooks, and plugin packs to Codex CLI."
|
|
6933
6935
|
---
|
|
6934
6936
|
|
|
@@ -7033,6 +7035,15 @@ This means Claude-only events such as `TaskCompleted`, `TeammateIdle`,
|
|
|
7033
7035
|
`~/.codex/hooks.json` (global layer). Non-Codex events are silently skipped.
|
|
7034
7036
|
`remove-hook` cleans both Claude and Codex targets.
|
|
7035
7037
|
|
|
7038
|
+
Generated Codex hook commands include `AI_TOOLKIT_HOOK_QUIET=1`. This keeps
|
|
7039
|
+
non-blocking reminders and startup context out of the visible Codex hook output
|
|
7040
|
+
while preserving hook side effects and blocking decisions such as search-first
|
|
7041
|
+
Stop enforcement.
|
|
7042
|
+
|
|
7043
|
+
Plain-text informational hook context is also silent by default in the shared
|
|
7044
|
+
hook helper. Set `AI_TOOLKIT_HOOK_VERBOSE=1` only when debugging hook output
|
|
7045
|
+
outside the Codex UI.
|
|
7046
|
+
|
|
7036
7047
|
## Behavioral Limits
|
|
7037
7048
|
|
|
7038
7049
|
Codex wrappers preserve workflow intent, but not every Claude runtime behavior
|
|
@@ -7043,6 +7054,10 @@ Known limits:
|
|
|
7043
7054
|
- No native Codex equivalent of tmux-backed Agent Teams lifecycle events
|
|
7044
7055
|
- No separate task object model equivalent to Claude `Task*` APIs
|
|
7045
7056
|
- Hook event coverage is narrower than Claude Code
|
|
7057
|
+
- MCP search tool calls may not fire the shared `PostToolUse` search tracker,
|
|
7058
|
+
so `stop-search-check.sh` also checks `~/.codex/log/codex-tui.log` for
|
|
7059
|
+
`smart_query`, `hybrid_search_kb`, `crag_search`, `multi_hop_search`, and
|
|
7060
|
+
`verify_answer` calls after the search-first flag timestamp before blocking.
|
|
7046
7061
|
|
|
7047
7062
|
These are runtime platform limits, not installation defects.
|
|
7048
7063
|
|
|
@@ -8824,9 +8839,9 @@ title: "Hooks Catalog"
|
|
|
8824
8839
|
category: reference
|
|
8825
8840
|
service: ai-toolkit
|
|
8826
8841
|
tags: [hooks, quality, safety, enforcement, settings.json]
|
|
8827
|
-
version: "1.5.
|
|
8842
|
+
version: "1.5.3"
|
|
8828
8843
|
created: "2026-03-27"
|
|
8829
|
-
last_updated: "2026-05-
|
|
8844
|
+
last_updated: "2026-05-21"
|
|
8830
8845
|
description: "Complete reference of all ai-toolkit hooks: events, scripts, installation, and runtime behavior."
|
|
8831
8846
|
---
|
|
8832
8847
|
|
|
@@ -8871,6 +8886,11 @@ ai-toolkit update # re-copies scripts, re-merges (idempotent)
|
|
|
8871
8886
|
3. Loads session context from `.claude/session-context.md` (if exists)
|
|
8872
8887
|
4. Loads active instincts from `.claude/instincts/*.md` (if any)
|
|
8873
8888
|
|
|
8889
|
+
By default the hook performs session-state reset, stale search-flag cleanup, and
|
|
8890
|
+
update notification side effects without printing informational stdout. Set
|
|
8891
|
+
`AI_TOOLKIT_HOOK_VERBOSE=1` to print the startup reminders and loaded context
|
|
8892
|
+
for debugging; `AI_TOOLKIT_HOOK_QUIET=1` keeps it silent explicitly.
|
|
8893
|
+
|
|
8874
8894
|
### Notification — `notify-waiting.sh`
|
|
8875
8895
|
|
|
8876
8896
|
| Field | Value |
|
|
@@ -8923,9 +8943,16 @@ ai-toolkit update # re-copies scripts, re-merges (idempotent)
|
|
|
8923
8943
|
| Script | `~/.softspark/ai-toolkit/hooks/user-prompt-submit.sh` |
|
|
8924
8944
|
| Fires | Before Claude starts working on a submitted prompt |
|
|
8925
8945
|
|
|
8926
|
-
**Action:**
|
|
8946
|
+
**Action:** Maintains the per-session search-first flag used by Stop enforcement
|
|
8947
|
+
and can provide a lightweight governance reminder: plan mode for architectural
|
|
8948
|
+
work, evidence-first debugging, KB-first research, and validation expectations.
|
|
8927
8949
|
|
|
8928
|
-
Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
8950
|
+
Skipped when `TOOLKIT_HOOK_PROFILE=minimal`. The bundled `app/hooks.json`
|
|
8951
|
+
registers this command with `AI_TOOLKIT_HOOK_QUIET=1`, so it still arms or
|
|
8952
|
+
clears the per-session search-first flag but suppresses the informational
|
|
8953
|
+
reminder output. In plain-text mode, informational reminders are silent by
|
|
8954
|
+
default and require `AI_TOOLKIT_HOOK_VERBOSE=1`; JSON hook output remains
|
|
8955
|
+
available with `AI_TOOLKIT_HOOK_FORMAT=json`.
|
|
8929
8956
|
|
|
8930
8957
|
### UserPromptSubmit (usage tracking) — `track-usage.sh`
|
|
8931
8958
|
|
|
@@ -9198,11 +9225,11 @@ First-match-wins per file. Built-in runners: `bats`, `pytest`, `vitest`, `jest`.
|
|
|
9198
9225
|
| Field | Value |
|
|
9199
9226
|
|-------|-------|
|
|
9200
9227
|
| Event | `PostToolUse` |
|
|
9201
|
-
| Matcher | `
|
|
9228
|
+
| Matcher | `mcp__.*__(smart_query\|hybrid_search_kb\|crag_search\|multi_hop_search\|verify_answer)\|WebSearch\|WebFetch` |
|
|
9202
9229
|
| Script | `~/.softspark/ai-toolkit/hooks/search-tracker.sh` |
|
|
9203
9230
|
| Fires | After any search-style tool call |
|
|
9204
9231
|
|
|
9205
|
-
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required-<session_id>.flag` (per-session, keyed by `session_id` from the hook stdin payload, falling back to `transcript_path` basename, then `default`). Pairs with `user-prompt-submit.sh` (sets the flag on long technical prompts only when a search provider is detected or strict mode is enabled) and `stop-search-check.sh` (blocks Stop if the calling session's flag is still set). Together
|
|
9232
|
+
**Action:** Clears `~/.softspark/ai-toolkit/state/search-required-<session_id>.flag` (per-session, keyed by `session_id` from the hook stdin payload, falling back to `transcript_path` basename, then `default`). Pairs with `user-prompt-submit.sh` (sets the flag on long technical prompts only when a search provider is detected or strict mode is enabled) and `stop-search-check.sh` (blocks Stop if the calling session's flag is still set). Search provider detection parses actual MCP server names from `mcpServers`, `mcp_servers`, or `mcp` config blocks; hook matchers and permission allowlists do not count as providers. Together the hooks enforce the global CLAUDE.md GOLDEN RULE without breaking offline/no-RAG installs and without cross-session interference when multiple Claude Code windows run in parallel.
|
|
9206
9233
|
|
|
9207
9234
|
Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
9208
9235
|
|
|
@@ -9215,7 +9242,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
9215
9242
|
| Script | `~/.softspark/ai-toolkit/hooks/stop-search-check.sh` |
|
|
9216
9243
|
| Fires | When Claude finishes a response |
|
|
9217
9244
|
|
|
9218
|
-
**Action:** If `search-required-<session_id>.flag` for the calling session is still present (no search tool ran during this turn) and a search provider is still detectable, emits `{"decision":"block","reason":"..."}` to continue the conversation with a search-first reminder. If no RAG/Web provider is detected, it clears the stale flag and exits 0, so offline/no-MCP users are not blocked. Flags are scoped by `session_id` from the hook stdin payload so a Stop in session B never consumes session A's flag (and vice versa). Stale per-session flags older than 60 minutes are GC'd on the next `SessionStart`.
|
|
9245
|
+
**Action:** If `search-required-<session_id>.flag` for the calling session is still present (no search tool ran during this turn) and a search provider is still detectable, emits `{"decision":"block","reason":"..."}` to continue the conversation with a search-first reminder. If no RAG/Web provider is detected, it clears the stale flag and exits 0, so offline/no-MCP users are not blocked. On Codex, where MCP search tools may not trigger the shared `PostToolUse` tracker, the hook also checks `~/.codex/log/codex-tui.log` for search tool calls after the flag timestamp before blocking. Flags are scoped by `session_id` from the hook stdin payload so a Stop in session B never consumes session A's flag (and vice versa). Stale per-session flags older than 60 minutes are GC'd on the next `SessionStart`.
|
|
9219
9246
|
|
|
9220
9247
|
**Overrides:** `CLAUDE_SKIP_SEARCH_FIRST=1`, `AI_TOOLKIT_SEARCH_FIRST=off`, or `AI_TOOLKIT_SEARCH_FIRST=strict` to force enforcement. Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
9221
9248
|
|
|
@@ -9253,7 +9280,7 @@ Non-blocking (exit 0). Skipped when `TOOLKIT_HOOK_PROFILE=minimal`.
|
|
|
9253
9280
|
| `scripts/test_cohesion.py` | Resolves changed paths → test commands via cohesion map. First-match-wins. Stdlib-only. |
|
|
9254
9281
|
| `app/hooks/test-cohesion-map.json` | Toolkit-default path → tests mapping (used when no project map exists). |
|
|
9255
9282
|
| `app/hooks/_locate-toolkit.sh` | Shared bash helper that exports `$TOOLKIT_DIR` for hooks needing scripts/. |
|
|
9256
|
-
| `app/hooks/_hook-io.sh` | Shared bash helper that normalizes hook payloads across Claude, Augment, Gemini, Windsurf, and Cursor-style JSON. |
|
|
9283
|
+
| `app/hooks/_hook-io.sh` | Shared bash helper that normalizes hook payloads across Claude, Augment, Gemini, Windsurf, and Cursor-style JSON. Honors `AI_TOOLKIT_HOOK_QUIET=1` and `AI_TOOLKIT_HOOK_VERBOSE=1` for non-blocking context output. |
|
|
9257
9284
|
| `app/hooks/_search-capability.sh` | Shared bash helper that enables search-first blocking only when RAG/Web is configured or strict mode is requested. |
|
|
9258
9285
|
|
|
9259
9286
|
## Runtime Profiles
|
|
@@ -9270,6 +9297,12 @@ Set in `.claude/settings.local.json`:
|
|
|
9270
9297
|
| `standard` | All hooks (default) |
|
|
9271
9298
|
| `strict` | Standard + mypy --strict on task completion |
|
|
9272
9299
|
|
|
9300
|
+
Non-blocking informational context is silent in plain-text mode by default while
|
|
9301
|
+
side effects and blocking decisions still run. Set `AI_TOOLKIT_HOOK_VERBOSE=1`
|
|
9302
|
+
only when debugging hook context locally. `AI_TOOLKIT_HOOK_QUIET=1` keeps hook
|
|
9303
|
+
commands explicitly silent, and Codex-generated hooks plus Claude's bundled
|
|
9304
|
+
`UserPromptSubmit` entry use it to avoid visible prompt hook context.
|
|
9305
|
+
|
|
9273
9306
|
## Architecture
|
|
9274
9307
|
|
|
9275
9308
|
```
|
package/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.3.
|
|
2
|
+
"version": "4.3.3",
|
|
3
3
|
"components": {
|
|
4
4
|
"agents": {
|
|
5
5
|
"description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"tags": ["core", "skills", "commands"]
|
|
17
17
|
},
|
|
18
18
|
"hooks": {
|
|
19
|
-
"description": "
|
|
19
|
+
"description": "28 hook entries across 14 lifecycle events",
|
|
20
20
|
"path": "app/hooks.json + app/hooks/*.sh",
|
|
21
21
|
"target": "~/.claude/settings.json (merge) + ~/.softspark/ai-toolkit/hooks/ (copy)",
|
|
22
22
|
"type": "merge+copy",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3",
|
|
4
4
|
"description": "AI coding toolkit: 107 skills, 44 agents, 12-editor write-through (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo, Aider, Augment, Antigravity, Codex, opencode), machine-enforced safety constitution, SARIF audit, signed npm provenance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -24,7 +24,7 @@ import sys
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
HOOKS_PREFIX = '"$HOME/.softspark/ai-toolkit/hooks/'
|
|
27
|
+
HOOKS_PREFIX = 'AI_TOOLKIT_HOOK_QUIET=1 "$HOME/.softspark/ai-toolkit/hooks/'
|
|
28
28
|
|
|
29
29
|
# Hooks compatible with Codex, grouped by event.
|
|
30
30
|
# Format: (matcher, script_name)
|