@softspark/ai-toolkit 4.2.3 → 4.2.5

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 (52) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +10 -9
  3. package/action.yml +1 -1
  4. package/app/.claude-plugin/plugin.json +1 -1
  5. package/app/hooks/_hook-io.sh +64 -0
  6. package/app/hooks/_locate-toolkit.sh +39 -0
  7. package/app/hooks/_search-capability.sh +46 -0
  8. package/app/hooks/commit-quality.sh +3 -1
  9. package/app/hooks/config-desync-guard.sh +95 -0
  10. package/app/hooks/governance-capture.sh +5 -3
  11. package/app/hooks/guard-config.sh +5 -3
  12. package/app/hooks/guard-destructive.sh +3 -1
  13. package/app/hooks/guard-path.sh +8 -1
  14. package/app/hooks/instructions-audit.sh +43 -0
  15. package/app/hooks/post-tool-use.sh +20 -8
  16. package/app/hooks/quality-gate.sh +47 -0
  17. package/app/hooks/revert-guard.sh +84 -0
  18. package/app/hooks/search-tracker.sh +18 -0
  19. package/app/hooks/session-start.sh +14 -2
  20. package/app/hooks/stop-search-check.sh +37 -0
  21. package/app/hooks/test-cohesion-map.json +77 -0
  22. package/app/hooks/test-cohesion.sh +93 -0
  23. package/app/hooks/track-usage.sh +3 -1
  24. package/app/hooks/user-prompt-submit.sh +33 -6
  25. package/app/hooks.json +65 -1
  26. package/app/skills/ci/SKILL.md +1 -1
  27. package/app/skills/ci/templates/github-actions-node.yml +2 -2
  28. package/app/skills/ci/templates/github-actions-python.yml +1 -1
  29. package/app/skills/ci-cd-patterns/SKILL.md +8 -8
  30. package/app/skills/csharp-patterns/SKILL.md +1 -1
  31. package/app/skills/docker-devops/SKILL.md +4 -4
  32. package/benchmarks/ecosystem-doctor-snapshot.json +8 -8
  33. package/kb/procedures/ecosystem-sync-sop.md +1 -1
  34. package/kb/procedures/release-verification-sop.md +2 -2
  35. package/kb/reference/architecture-overview.md +1 -1
  36. package/kb/reference/ci-integration.md +2 -2
  37. package/kb/reference/hooks-catalog.md +149 -24
  38. package/kb/reference/unique-features.md +11 -5
  39. package/llms-full.txt +166 -35
  40. package/manifest.json +1 -1
  41. package/package.json +1 -1
  42. package/scripts/doctor.py +13 -0
  43. package/scripts/generate_augment_hooks.py +5 -1
  44. package/scripts/generate_codex_hooks.py +2 -0
  45. package/scripts/generate_cursor_hooks.py +6 -0
  46. package/scripts/generate_gemini_hooks.py +5 -1
  47. package/scripts/generate_windsurf_hooks.py +6 -0
  48. package/scripts/install_steps/hooks.py +5 -0
  49. package/scripts/merge-hooks.py +10 -1
  50. package/scripts/plugin_schema.py +4 -0
  51. package/scripts/session_state.py +150 -0
  52. package/scripts/test_cohesion.py +133 -0
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # revert-guard.sh — Block git revert/restore on files edited in this session.
3
+ #
4
+ # Fires on: PreToolUse
5
+ # Matcher: Bash
6
+ # Skipped when TOOLKIT_HOOK_PROFILE=minimal.
7
+ #
8
+ # Constitution Art. VI.2 ("Fix Every Found Bug"): when a session has produced
9
+ # edits, a `git checkout/restore/reset --hard/clean -fd` on those files is
10
+ # almost always discarding work-in-progress instead of fixing the root cause.
11
+ # Block it unless the user explicitly opted in.
12
+ #
13
+ # Escape hatch: set CLAUDE_REVERT_OK=1 (per-call) to bypass.
14
+ # Exit codes:
15
+ # 0 allow command
16
+ # 2 block (per Claude Code hooks spec)
17
+
18
+ # shellcheck source=_profile-check.sh
19
+ source "$(dirname "$0")/_profile-check.sh"
20
+ # shellcheck source=_locate-toolkit.sh
21
+ source "$(dirname "$0")/_locate-toolkit.sh"
22
+ # shellcheck source=_hook-io.sh
23
+ source "$(dirname "$0")/_hook-io.sh"
24
+
25
+ INPUT=$(cat)
26
+ COMMAND=$(hook_command)
27
+
28
+ [ -z "$COMMAND" ] && exit 0
29
+ [ "${CLAUDE_REVERT_OK:-0}" = "1" ] && exit 0
30
+ [ -z "$TOOLKIT_DIR" ] && exit 0
31
+ command -v python3 >/dev/null 2>&1 || exit 0
32
+
33
+ # Strip leading words so we can match the git verb.
34
+ # Only act on commands whose first token is `git`.
35
+ read -r FIRST _rest <<<"$COMMAND"
36
+ [ "$FIRST" = "git" ] || exit 0
37
+
38
+ # Helper: emit block message on stderr and exit 2.
39
+ _block() {
40
+ local reason="$1"
41
+ local detail="$2"
42
+ cat >&2 <<EOF
43
+ revert-guard: BLOCKED — $reason
44
+ Constitution Art. VI.2: do not revert work-in-progress. Fix the root cause instead.
45
+
46
+ Affected: $detail
47
+
48
+ If you really need to discard changes, re-run with:
49
+ CLAUDE_REVERT_OK=1 $COMMAND
50
+ EOF
51
+ exit 2
52
+ }
53
+
54
+ # git reset --hard / git clean -fd: clobber-style, scope = all session edits.
55
+ if printf '%s' "$COMMAND" | grep -Eq 'reset[[:space:]]+(-+[A-Za-z-]+[[:space:]]+)*--hard|clean[[:space:]]+-[A-Za-z]*[df]'; then
56
+ edited=$(python3 "$TOOLKIT_DIR/scripts/session_state.py" list 2>/dev/null | head -10)
57
+ if [ -n "$edited" ]; then
58
+ _block "destructive 'git reset --hard' / 'git clean' with session edits in tree" "$edited"
59
+ fi
60
+ exit 0
61
+ fi
62
+
63
+ # git checkout / git restore: only block when `--` separator is present
64
+ # (file-restore form). Bare `git checkout branch-name` is allowed.
65
+ if printf '%s' "$COMMAND" | grep -Eq '(checkout|restore)([[:space:]].*)?[[:space:]]--[[:space:]]'; then
66
+ files=$(printf '%s' "$COMMAND" | awk -F' -- ' '{print $2}')
67
+ [ -z "$files" ] && exit 0
68
+ blocked=""
69
+ for f in $files; do
70
+ # Resolve relative paths against $PWD.
71
+ case "$f" in
72
+ /*) abs="$f" ;;
73
+ *) abs="$PWD/$f" ;;
74
+ esac
75
+ if python3 "$TOOLKIT_DIR/scripts/session_state.py" was-edited "$abs" >/dev/null 2>&1; then
76
+ blocked="$blocked $f"
77
+ fi
78
+ done
79
+ if [ -n "$blocked" ]; then
80
+ _block "restoring file(s) edited in this session" "$blocked"
81
+ fi
82
+ fi
83
+
84
+ exit 0
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # search-tracker.sh — Clear search-required flag when a search tool runs.
3
+ #
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
6
+ # Non-blocking: always exits 0.
7
+ # Skipped when TOOLKIT_HOOK_PROFILE=minimal.
8
+ #
9
+ # Paired with user-prompt-submit.sh (sets the flag) and stop-search-check.sh
10
+ # (blocks Stop when flag still present). Together they implement the Step 0
11
+ # "search before answering" rule from global CLAUDE.md.
12
+
13
+ # shellcheck source=_profile-check.sh
14
+ source "$(dirname "$0")/_profile-check.sh"
15
+
16
+ FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
17
+ rm -f "$FLAG" 2>/dev/null
18
+ exit 0
@@ -8,9 +8,21 @@
8
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
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
10
 
11
+ # shellcheck source=_locate-toolkit.sh
12
+ source "$(dirname "$0")/_locate-toolkit.sh"
13
+
14
+ # 1a. Reset per-session edit state (used by revert-guard, test-cohesion, quality-gate)
15
+ SESSION_ID_INPUT=""
16
+ if [ ! -t 0 ]; then
17
+ STDIN_PAYLOAD="$(cat)"
18
+ SESSION_ID_INPUT="$(printf '%s' "$STDIN_PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)"
19
+ fi
20
+ if [ -n "$TOOLKIT_DIR" ] && command -v python3 >/dev/null 2>&1; then
21
+ python3 "$TOOLKIT_DIR/scripts/session_state.py" reset \
22
+ ${SESSION_ID_INPUT:+--session-id "$SESSION_ID_INPUT"} >/dev/null 2>&1 || true
23
+ fi
24
+
11
25
  # 2. Check for updates (cached, max once per 24h, non-blocking)
12
- TOOLKIT_DIR="$(npm root -g 2>/dev/null)/@softspark/ai-toolkit"
13
- [ ! -d "$TOOLKIT_DIR" ] && TOOLKIT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
14
26
  VERSION_MSG=$(python3 "$TOOLKIT_DIR/scripts/version_check.py" 2>/dev/null)
15
27
  if [ -n "$VERSION_MSG" ]; then
16
28
  echo "$VERSION_MSG"
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # stop-search-check.sh — Block Stop when search-first rule was skipped.
3
+ #
4
+ # Fires on: Stop
5
+ # Matcher: all
6
+ # Skipped when TOOLKIT_HOOK_PROFILE=minimal.
7
+ #
8
+ # If user-prompt-submit.sh set the search-required flag and no search tool
9
+ # was invoked (search-tracker.sh would have cleared it), this hook continues
10
+ # the conversation with a JSON `decision: block` payload so Claude reads the
11
+ # reminder and calls smart_query() before the next response.
12
+ #
13
+ # Override (one-off): CLAUDE_SKIP_SEARCH_FIRST=1
14
+
15
+ # shellcheck source=_profile-check.sh
16
+ source "$(dirname "$0")/_profile-check.sh"
17
+ # shellcheck source=_hook-io.sh
18
+ source "$(dirname "$0")/_hook-io.sh"
19
+ # shellcheck source=_search-capability.sh
20
+ source "$(dirname "$0")/_search-capability.sh"
21
+
22
+ [ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" = "1" ] && exit 0
23
+
24
+ FLAG="$HOME/.softspark/ai-toolkit/state/search-required.flag"
25
+ [ -f "$FLAG" ] || exit 0
26
+
27
+ if ! ai_toolkit_has_search_provider; then
28
+ rm -f "$FLAG" 2>/dev/null
29
+ exit 0
30
+ fi
31
+
32
+ # Read the original prompt (line 2 of the flag file) for the reminder.
33
+ PROMPT_PREVIEW=$(sed -n '2p' "$FLAG" 2>/dev/null | head -c 200)
34
+ rm -f "$FLAG" 2>/dev/null # one-shot; do not loop forever
35
+
36
+ hook_emit_block "Stop" "Step 0 violated: you responded to a technical prompt without calling smart_query() or hybrid_search_kb() first. Per global CLAUDE.md GOLDEN RULE: search KB BEFORE answering. Prompt was: \"${PROMPT_PREVIEW}...\". Now: call the search tool, then continue your reply. Override (one-off): CLAUDE_SKIP_SEARCH_FIRST=1."
37
+ exit 0
@@ -0,0 +1,77 @@
1
+ [
2
+ {
3
+ "match": "app/hooks/quality-gate.sh",
4
+ "tests": ["tests/test_hooks.bats"],
5
+ "runner": "bats"
6
+ },
7
+ {
8
+ "match": "app/hooks/quality-check.sh",
9
+ "tests": ["tests/test_hooks.bats"],
10
+ "runner": "bats"
11
+ },
12
+ {
13
+ "match": "app/hooks/revert-guard.sh",
14
+ "tests": ["tests/test_revert_guard_hook.bats"],
15
+ "runner": "bats"
16
+ },
17
+ {
18
+ "match": "app/hooks/test-cohesion.sh",
19
+ "tests": ["tests/test_test_cohesion_hook.bats"],
20
+ "runner": "bats"
21
+ },
22
+ {
23
+ "match": "app/hooks/config-desync-guard.sh",
24
+ "tests": ["tests/test_config_desync_guard.bats"],
25
+ "runner": "bats"
26
+ },
27
+ {
28
+ "match": "app/hooks/instructions-audit.sh",
29
+ "tests": ["tests/test_instructions_audit.bats"],
30
+ "runner": "bats"
31
+ },
32
+ {
33
+ "match": "app/hooks/user-prompt-submit.sh",
34
+ "tests": ["tests/test_hooks.bats"],
35
+ "runner": "bats"
36
+ },
37
+ {
38
+ "match": "app/hooks/*.sh",
39
+ "tests": ["tests/test_hooks.bats", "tests/test_hooks_per_editor.bats"],
40
+ "runner": "bats"
41
+ },
42
+ {
43
+ "match": "app/hooks.json",
44
+ "tests": ["tests/test_hooks.bats", "tests/test_merge_hooks_statusline.bats", "tests/test_hooks_per_editor.bats"],
45
+ "runner": "bats"
46
+ },
47
+ {
48
+ "match": "scripts/merge-hooks.py",
49
+ "tests": ["tests/test_merge_hooks_statusline.bats", "tests/test_inject_hook.bats"],
50
+ "runner": "bats"
51
+ },
52
+ {
53
+ "match": "scripts/session_state.py",
54
+ "tests": ["tests/test_session_state.bats"],
55
+ "runner": "bats"
56
+ },
57
+ {
58
+ "match": "scripts/test_cohesion.py",
59
+ "tests": ["tests/test_test_cohesion_resolver.bats"],
60
+ "runner": "bats"
61
+ },
62
+ {
63
+ "match": "scripts/inject_hook_cli.py",
64
+ "tests": ["tests/test_inject_hook.bats"],
65
+ "runner": "bats"
66
+ },
67
+ {
68
+ "match": "scripts/inject_rule_cli.py",
69
+ "tests": ["tests/test_inject.bats"],
70
+ "runner": "bats"
71
+ },
72
+ {
73
+ "match": "scripts/validate.py",
74
+ "tests": ["tests/test_validate.bats", "tests/test_validate_negative.bats"],
75
+ "runner": "bats"
76
+ }
77
+ ]
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+ # test-cohesion.sh — Run related tests after edits, block if they fail.
3
+ #
4
+ # Fires on: PostToolUse
5
+ # Matcher: Edit|MultiEdit|Write
6
+ # Skipped when TOOLKIT_HOOK_PROFILE=minimal or CLAUDE_HOOK_BOOTSTRAP=1.
7
+ #
8
+ # Constitution Art. VI.3 ("Tests and Docs Follow Behavior"): when source code
9
+ # changes, the matching tests must pass before the agent can continue. This
10
+ # hook resolves a per-project test-cohesion map and runs only the related
11
+ # test files (NOT the full suite).
12
+ #
13
+ # Map lookup (first found wins):
14
+ # 1. $PWD/.claude/test-cohesion-map.json (project-local)
15
+ # 2. $TOOLKIT_DIR/app/hooks/test-cohesion-map.json (toolkit default)
16
+ #
17
+ # Escape hatches:
18
+ # CLAUDE_HOOK_BOOTSTRAP=1 bypass (used when editing the hook itself)
19
+ # CLAUDE_SKIP_COHESION=1 one-off bypass
20
+ #
21
+ # Exit codes:
22
+ # 0 no map / no match / tests passed
23
+ # 2 related test command failed
24
+
25
+ # shellcheck source=_profile-check.sh
26
+ source "$(dirname "$0")/_profile-check.sh"
27
+ # shellcheck source=_locate-toolkit.sh
28
+ source "$(dirname "$0")/_locate-toolkit.sh"
29
+ # shellcheck source=_hook-io.sh
30
+ source "$(dirname "$0")/_hook-io.sh"
31
+
32
+ [ "${CLAUDE_HOOK_BOOTSTRAP:-0}" = "1" ] && exit 0
33
+ [ "${CLAUDE_SKIP_COHESION:-0}" = "1" ] && exit 0
34
+
35
+ INPUT=$(cat)
36
+ FILE_PATH=$(hook_file_path)
37
+
38
+ [ -z "$FILE_PATH" ] && exit 0
39
+ [ -z "$TOOLKIT_DIR" ] && exit 0
40
+ command -v python3 >/dev/null 2>&1 || exit 0
41
+
42
+ REPO_ROOT="$PWD"
43
+ # Prefer git repo root when available; fall back to PWD.
44
+ if command -v git >/dev/null 2>&1; then
45
+ git_root=$(git rev-parse --show-toplevel 2>/dev/null)
46
+ [ -n "$git_root" ] && REPO_ROOT="$git_root"
47
+ fi
48
+
49
+ # Resolve symlinks on both sides so the cohesion-map glob can match files
50
+ # under symlinked roots (e.g. /tmp -> /private/tmp on macOS).
51
+ REAL_FILE=$(python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$FILE_PATH" 2>/dev/null)
52
+ REAL_REPO=$(python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$REPO_ROOT" 2>/dev/null)
53
+ [ -z "$REAL_FILE" ] && REAL_FILE="$FILE_PATH"
54
+ [ -z "$REAL_REPO" ] && REAL_REPO="$REPO_ROOT"
55
+
56
+ COMMANDS=$(python3 "$TOOLKIT_DIR/scripts/test_cohesion.py" resolve \
57
+ --changed-paths "$REAL_FILE" --repo-root "$REAL_REPO" 2>/dev/null)
58
+ [ -z "$COMMANDS" ] && exit 0
59
+
60
+ # Run each resolved command. Block on first failure.
61
+ LOG_DIR="$HOME/.softspark/ai-toolkit/state"
62
+ mkdir -p "$LOG_DIR" 2>/dev/null
63
+ LOG_FILE="$LOG_DIR/test-cohesion-last.log"
64
+
65
+ cd "$REAL_REPO" || exit 0
66
+ FAILED=""
67
+ while IFS= read -r cmd; do
68
+ [ -z "$cmd" ] && continue
69
+ if ! bash -c "$cmd" >"$LOG_FILE" 2>&1; then
70
+ FAILED="$cmd"
71
+ break
72
+ fi
73
+ done <<<"$COMMANDS"
74
+
75
+ if [ -n "$FAILED" ]; then
76
+ cat >&2 <<EOF
77
+ test-cohesion: BLOCKED — tests for ${FILE_PATH} failed.
78
+ Constitution Art. VI.3: tests must pass before continuing.
79
+
80
+ Command:
81
+ $FAILED
82
+
83
+ Last log (truncated):
84
+ $(tail -20 "$LOG_FILE")
85
+
86
+ Fix the failing test OR update it to match the new behavior (Art. VI.3).
87
+ Override (one-off): CLAUDE_SKIP_COHESION=1
88
+ EOF
89
+ exit 2
90
+ fi
91
+
92
+ [ "${AI_TOOLKIT_HOOK_FORMAT:-}" = "json" ] || echo "test-cohesion: related tests passed for ${FILE_PATH}"
93
+ exit 0
@@ -11,7 +11,9 @@ STATS_FILE="${HOME}/.softspark/ai-toolkit/stats.json"
11
11
 
12
12
  # Read prompt from stdin (Claude Code passes JSON with .prompt field)
13
13
  INPUT=$(cat)
14
- PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
14
+ # shellcheck source=_hook-io.sh
15
+ source "$(dirname "$0")/_hook-io.sh"
16
+ PROMPT_TEXT=$(hook_prompt)
15
17
  [ -z "$PROMPT_TEXT" ] && exit 0
16
18
 
17
19
  # Only track if prompt starts with a slash command
@@ -7,21 +7,48 @@
7
7
 
8
8
  # shellcheck source=_profile-check.sh
9
9
  source "$(dirname "$0")/_profile-check.sh"
10
+ # shellcheck source=_hook-io.sh
11
+ source "$(dirname "$0")/_hook-io.sh"
12
+ # shellcheck source=_search-capability.sh
13
+ source "$(dirname "$0")/_search-capability.sh"
10
14
 
11
15
  # Read prompt from stdin (Claude Code passes JSON with .prompt field)
12
16
  INPUT=$(cat)
13
- PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
17
+ PROMPT_TEXT=$(hook_prompt)
14
18
  LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
15
19
 
16
- echo "STOP. Execute Step 0 before responding: check your CLAUDE.md for search-first rules. If search-first rules exist, call the required search tool NOW — before any other tool or text output. If you skip this step, the user will interrupt you."
20
+ # Heuristic search-required flag (paired with search-tracker.sh + Stop check).
21
+ # Set only when a search provider is detectable or strict mode is requested.
22
+ # Cleared by search-tracker.sh after smart_query / hybrid_search_kb / web runs.
23
+ PROMPT_LEN=${#PROMPT_TEXT}
24
+ STATE_DIR="$HOME/.softspark/ai-toolkit/state"
25
+ FLAG="$STATE_DIR/search-required.flag"
26
+ mkdir -p "$STATE_DIR" 2>/dev/null
27
+ if [ "$PROMPT_LEN" -gt 30 ] && \
28
+ [ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" != "1" ] && \
29
+ ai_toolkit_has_search_provider; then
30
+ printf '%s\n%s\n' "$(date -u +%s)" "$PROMPT_TEXT" > "$FLAG" 2>/dev/null
31
+ else
32
+ rm -f "$FLAG" 2>/dev/null
33
+ fi
34
+
35
+ if ai_toolkit_has_search_provider; then
36
+ CONTEXT_MSG="STOP. Execute Step 0 before responding: check your CLAUDE.md for search-first rules. If search-first rules exist and a search tool is available, call the required search tool NOW — before any other tool or text output."
37
+ else
38
+ CONTEXT_MSG="Search-first note: no ai-toolkit search provider was detected, so search-first enforcement is advisory only. Use available local evidence; do not call unavailable RAG/MCP tools."
39
+ fi
17
40
 
18
41
  if printf '%s' "$LOWERED" | grep -Eq 'architecture|design|migration|deploy|rollback|refactor|plugin|workflow'; then
19
- echo "UserPromptSubmit: task looks architectural or multi-step. Use plan mode, define success criteria, and validate before marking done."
42
+ CONTEXT_MSG="$CONTEXT_MSG
43
+ UserPromptSubmit: task looks architectural or multi-step. Use plan mode, define success criteria, and validate before marking done."
20
44
  elif printf '%s' "$LOWERED" | grep -Eq 'bug|error|fail|failing|incident|outage|debug'; then
21
- echo "UserPromptSubmit: debugging request detected. Gather evidence first, then propose the smallest safe fix and targeted tests."
45
+ CONTEXT_MSG="$CONTEXT_MSG
46
+ UserPromptSubmit: debugging request detected. Gather evidence first, then propose the smallest safe fix and targeted tests."
22
47
  else
23
- echo "UserPromptSubmit: apply KB-first research, keep changes minimal, and update tests/docs when behavior changes."
48
+ CONTEXT_MSG="$CONTEXT_MSG
49
+ UserPromptSubmit: apply KB-first research, keep changes minimal, and update tests/docs when behavior changes."
24
50
  fi
25
51
 
26
- exit 0
52
+ hook_emit_context "$CONTEXT_MSG"
27
53
 
54
+ exit 0
package/app/hooks.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "hooks": [
40
40
  {
41
41
  "type": "command",
42
- "command": "bash ~/.softspark/ai-toolkit/hooks/notify-waiting.sh"
42
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/notify-waiting.sh\""
43
43
  }
44
44
  ]
45
45
  }
@@ -84,6 +84,16 @@
84
84
  "command": "\"$HOME/.softspark/ai-toolkit/hooks/commit-quality.sh\""
85
85
  }
86
86
  ]
87
+ },
88
+ {
89
+ "_source": "ai-toolkit",
90
+ "matcher": "Bash",
91
+ "hooks": [
92
+ {
93
+ "type": "command",
94
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/revert-guard.sh\""
95
+ }
96
+ ]
87
97
  }
88
98
  ],
89
99
  "UserPromptSubmit": [
@@ -128,6 +138,26 @@
128
138
  "command": "\"$HOME/.softspark/ai-toolkit/hooks/governance-capture.sh\""
129
139
  }
130
140
  ]
141
+ },
142
+ {
143
+ "_source": "ai-toolkit",
144
+ "matcher": "Edit|MultiEdit|Write",
145
+ "hooks": [
146
+ {
147
+ "type": "command",
148
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/test-cohesion.sh\""
149
+ }
150
+ ]
151
+ },
152
+ {
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",
155
+ "hooks": [
156
+ {
157
+ "type": "command",
158
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/search-tracker.sh\""
159
+ }
160
+ ]
131
161
  }
132
162
  ],
133
163
  "Stop": [
@@ -160,6 +190,16 @@
160
190
  "command": "\"$HOME/.softspark/ai-toolkit/hooks/quality-gate.sh\""
161
191
  }
162
192
  ]
193
+ },
194
+ {
195
+ "_source": "ai-toolkit",
196
+ "matcher": "",
197
+ "hooks": [
198
+ {
199
+ "type": "command",
200
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/stop-search-check.sh\""
201
+ }
202
+ ]
163
203
  }
164
204
  ],
165
205
  "TaskCompleted": [
@@ -243,6 +283,30 @@
243
283
  }
244
284
  ]
245
285
  }
286
+ ],
287
+ "InstructionsLoaded": [
288
+ {
289
+ "_source": "ai-toolkit",
290
+ "matcher": "",
291
+ "hooks": [
292
+ {
293
+ "type": "command",
294
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/instructions-audit.sh\""
295
+ }
296
+ ]
297
+ }
298
+ ],
299
+ "ConfigChange": [
300
+ {
301
+ "_source": "ai-toolkit",
302
+ "matcher": "user_settings",
303
+ "hooks": [
304
+ {
305
+ "type": "command",
306
+ "command": "\"$HOME/.softspark/ai-toolkit/hooks/config-desync-guard.sh\""
307
+ }
308
+ ]
309
+ }
246
310
  ]
247
311
  },
248
312
  "statusLine": {
@@ -88,7 +88,7 @@ Use `ci-cd-patterns` skill for pipeline templates and best practices.
88
88
  - GitHub Actions YAML parses `on:` as a reserved word only when unquoted. Writing `"on":` (quoted) produces a valid-looking file whose workflow **never triggers**. YAML anchors in this field also silently break.
89
89
  - GitLab CI's `rules:` and `only:/except:` are mutually exclusive at the job level. Mixing them fails parse on pipeline run but not at `git push` time — test with `gitlab-ci-lint` before committing.
90
90
  - `secrets.*` in GitHub Actions is undefined in workflows triggered from **forked** PRs (security boundary). Jobs that need secrets must gate on `github.event.pull_request.head.repo.full_name == github.repository` or use `pull_request_target` carefully.
91
- - `actions/checkout@v4` defaults to `fetch-depth: 1` (shallow). Commands that need history (`git log`, `git describe`, conventional-commit tools) fail with misleading errors — set `fetch-depth: 0` for those jobs.
91
+ - `actions/checkout@v6` defaults to `fetch-depth: 1` (shallow). Commands that need history (`git log`, `git describe`, conventional-commit tools) fail with misleading errors — set `fetch-depth: 0` for those jobs.
92
92
 
93
93
  ## When NOT to Use
94
94
 
@@ -14,10 +14,10 @@ jobs:
14
14
  node-version: [18, 20, 22]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
19
  - name: Use Node.js ${{ matrix.node-version }}
20
- uses: actions/setup-node@v4
20
+ uses: actions/setup-node@v6
21
21
  with:
22
22
  node-version: ${{ matrix.node-version }}
23
23
  cache: 'npm'
@@ -14,7 +14,7 @@ jobs:
14
14
  python-version: ["3.11", "3.12", "3.13"]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
19
  - name: Set up Python ${{ matrix.python-version }}
20
20
  uses: actions/setup-python@v5
@@ -23,8 +23,8 @@ jobs:
23
23
  lint:
24
24
  runs-on: ubuntu-latest
25
25
  steps:
26
- - uses: actions/checkout@v4
27
- - uses: actions/setup-node@v4
26
+ - uses: actions/checkout@v6
27
+ - uses: actions/setup-node@v6
28
28
  with:
29
29
  node-version: 20
30
30
  cache: "npm"
@@ -39,8 +39,8 @@ jobs:
39
39
  matrix:
40
40
  node-version: [18, 20, 22]
41
41
  steps:
42
- - uses: actions/checkout@v4
43
- - uses: actions/setup-node@v4
42
+ - uses: actions/checkout@v6
43
+ - uses: actions/setup-node@v6
44
44
  with:
45
45
  node-version: ${{ matrix.node-version }}
46
46
  cache: "npm"
@@ -55,8 +55,8 @@ jobs:
55
55
  runs-on: ubuntu-latest
56
56
  needs: test
57
57
  steps:
58
- - uses: actions/checkout@v4
59
- - uses: actions/setup-node@v4
58
+ - uses: actions/checkout@v6
59
+ - uses: actions/setup-node@v6
60
60
  with:
61
61
  node-version: 20
62
62
  cache: "npm"
@@ -73,7 +73,7 @@ jobs:
73
73
  test:
74
74
  runs-on: ubuntu-latest
75
75
  steps:
76
- - uses: actions/checkout@v4
76
+ - uses: actions/checkout@v6
77
77
  - uses: actions/setup-python@v5
78
78
  with:
79
79
  python-version: "3.12"
@@ -90,7 +90,7 @@ jobs:
90
90
  runs-on: ubuntu-latest
91
91
  needs: test
92
92
  steps:
93
- - uses: actions/checkout@v4
93
+ - uses: actions/checkout@v6
94
94
  - uses: docker/setup-buildx-action@v3
95
95
  - uses: docker/login-action@v3
96
96
  with:
@@ -417,7 +417,7 @@ dotnet_diagnostic.CA1848.severity = warning
417
417
  ### CI (GitHub Actions)
418
418
  ```yaml
419
419
  steps:
420
- - uses: actions/checkout@v4
420
+ - uses: actions/checkout@v6
421
421
  - uses: actions/setup-dotnet@v4
422
422
  with: { dotnet-version: '9.0.x' }
423
423
  - run: dotnet restore
@@ -157,8 +157,8 @@ jobs:
157
157
  test:
158
158
  runs-on: ubuntu-latest
159
159
  steps:
160
- - uses: actions/checkout@v4
161
- - uses: actions/setup-node@v4
160
+ - uses: actions/checkout@v6
161
+ - uses: actions/setup-node@v6
162
162
  with:
163
163
  node-version: '20'
164
164
  cache: 'npm'
@@ -172,7 +172,7 @@ jobs:
172
172
  if: github.ref == 'refs/heads/main'
173
173
  runs-on: ubuntu-latest
174
174
  steps:
175
- - uses: actions/checkout@v4
175
+ - uses: actions/checkout@v6
176
176
  - name: Deploy to production
177
177
  run: |
178
178
  # Deploy commands
@@ -192,7 +192,7 @@ jobs:
192
192
  test:
193
193
  runs-on: ubuntu-latest
194
194
  steps:
195
- - uses: actions/checkout@v4
195
+ - uses: actions/checkout@v6
196
196
  - uses: actions/setup-python@v5
197
197
  with:
198
198
  python-version: '3.12'