@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.
- package/CHANGELOG.md +48 -0
- package/README.md +10 -9
- package/action.yml +1 -1
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/hooks/_hook-io.sh +64 -0
- package/app/hooks/_locate-toolkit.sh +39 -0
- package/app/hooks/_search-capability.sh +46 -0
- package/app/hooks/commit-quality.sh +3 -1
- package/app/hooks/config-desync-guard.sh +95 -0
- package/app/hooks/governance-capture.sh +5 -3
- package/app/hooks/guard-config.sh +5 -3
- package/app/hooks/guard-destructive.sh +3 -1
- package/app/hooks/guard-path.sh +8 -1
- package/app/hooks/instructions-audit.sh +43 -0
- package/app/hooks/post-tool-use.sh +20 -8
- package/app/hooks/quality-gate.sh +47 -0
- package/app/hooks/revert-guard.sh +84 -0
- package/app/hooks/search-tracker.sh +18 -0
- package/app/hooks/session-start.sh +14 -2
- package/app/hooks/stop-search-check.sh +37 -0
- package/app/hooks/test-cohesion-map.json +77 -0
- package/app/hooks/test-cohesion.sh +93 -0
- package/app/hooks/track-usage.sh +3 -1
- package/app/hooks/user-prompt-submit.sh +33 -6
- package/app/hooks.json +65 -1
- package/app/skills/ci/SKILL.md +1 -1
- package/app/skills/ci/templates/github-actions-node.yml +2 -2
- package/app/skills/ci/templates/github-actions-python.yml +1 -1
- package/app/skills/ci-cd-patterns/SKILL.md +8 -8
- package/app/skills/csharp-patterns/SKILL.md +1 -1
- package/app/skills/docker-devops/SKILL.md +4 -4
- package/benchmarks/ecosystem-doctor-snapshot.json +8 -8
- package/kb/procedures/ecosystem-sync-sop.md +1 -1
- package/kb/procedures/release-verification-sop.md +2 -2
- package/kb/reference/architecture-overview.md +1 -1
- package/kb/reference/ci-integration.md +2 -2
- package/kb/reference/hooks-catalog.md +149 -24
- package/kb/reference/unique-features.md +11 -5
- package/llms-full.txt +166 -35
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/doctor.py +13 -0
- package/scripts/generate_augment_hooks.py +5 -1
- package/scripts/generate_codex_hooks.py +2 -0
- package/scripts/generate_cursor_hooks.py +6 -0
- package/scripts/generate_gemini_hooks.py +5 -1
- package/scripts/generate_windsurf_hooks.py +6 -0
- package/scripts/install_steps/hooks.py +5 -0
- package/scripts/merge-hooks.py +10 -1
- package/scripts/plugin_schema.py +4 -0
- package/scripts/session_state.py +150 -0
- 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
|
package/app/hooks/track-usage.sh
CHANGED
|
@@ -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
|
-
|
|
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=$(
|
|
17
|
+
PROMPT_TEXT=$(hook_prompt)
|
|
14
18
|
LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
|
|
15
19
|
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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": {
|
package/app/skills/ci/SKILL.md
CHANGED
|
@@ -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@
|
|
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@
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
18
|
|
|
19
19
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
-
uses: actions/setup-node@
|
|
20
|
+
uses: actions/setup-node@v6
|
|
21
21
|
with:
|
|
22
22
|
node-version: ${{ matrix.node-version }}
|
|
23
23
|
cache: 'npm'
|
|
@@ -23,8 +23,8 @@ jobs:
|
|
|
23
23
|
lint:
|
|
24
24
|
runs-on: ubuntu-latest
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
27
|
-
- uses: actions/setup-node@
|
|
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@
|
|
43
|
-
- uses: actions/setup-node@
|
|
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@
|
|
59
|
-
- uses: actions/setup-node@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
161
|
-
- uses: actions/setup-node@
|
|
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@
|
|
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@
|
|
195
|
+
- uses: actions/checkout@v6
|
|
196
196
|
- uses: actions/setup-python@v5
|
|
197
197
|
with:
|
|
198
198
|
python-version: '3.12'
|