@spardutti/claude-skills 2.56.3 → 2.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/hooks/gate-config.sh +19 -0
- package/hooks/ship-gate-projects.sh +19 -0
- package/hooks/ship-gate.sh +3 -4
- package/hooks/ship-log.mjs +2 -2
- package/hooks/ship-prep.sh +156 -0
- package/hooks/skill-flags-record.sh +3 -0
- package/hooks/skill-gate.sh +6 -16
- package/lib/setup-hook.mjs +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
package/hooks/gate-config.sh
CHANGED
|
@@ -18,6 +18,25 @@ load_gauntlet_conf() { # load_gauntlet_conf <project conf> <fallback conf>
|
|
|
18
18
|
if [ -f "$conf" ]; then . "$conf"; fi
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
skill_field() { # skill_field <SKILL.md> <key pattern> — its first value, quotes stripped
|
|
22
|
+
sed -n "s/^$2:[[:space:]]*//p" "$1" | head -1 | sed "s/^[\"']//; s/[\"']\$//"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Claude Code writes gate-paths gitignore-style. A case glob has no **, but * already crosses /
|
|
26
|
+
# here, so dropping the **/ prefix is equivalent. set -f keeps the globs from matching real files.
|
|
27
|
+
skill_hits() { # skill_hits <gate-paths> <files...> — the files those globs match
|
|
28
|
+
local g t globs
|
|
29
|
+
set -f
|
|
30
|
+
globs=$(printf '%s' "$1" | tr ',' ' ' | sed 's!\*\*/!!g')
|
|
31
|
+
for t in "${@:2}"; do
|
|
32
|
+
for g in $globs; do
|
|
33
|
+
case "$g" in \**|/*) ;; *) g="*$g" ;; esac
|
|
34
|
+
case "$t" in $g) printf '%s\n' "$t"; break ;; esac
|
|
35
|
+
done
|
|
36
|
+
done
|
|
37
|
+
set +f
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
json_field() { # json_field <json> <key> — a flat string value; \" is unescaped, \n is kept as two characters
|
|
22
41
|
printf '%s' "$1" | grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"([^\"\\\\]|\\\\.)*\"" | head -1 \
|
|
23
42
|
| sed -E 's/^[^:]*:[[:space:]]*"//; s/"$//; s/\\"/"/g'
|
|
@@ -89,6 +89,25 @@ changed_files() {
|
|
|
89
89
|
git ls-files --others --exclude-standard 2>/dev/null; } | sort -u | grep -vE '(^|/)\.claude/'
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
file_hunks() { # file_hunks <base> <file> — its new-side line ranges, one "start-end" a line
|
|
93
|
+
local h
|
|
94
|
+
h=$(git diff -U0 "$1" -- "$2" 2>/dev/null | grep -oE '^@@ -[0-9,]+ \+[0-9]+(,[0-9]+)?' | sed 's/.*+//' \
|
|
95
|
+
| while IFS=, read -r s l; do l=${l:-1}; [ "$l" -gt 0 ] && echo "$s-$((s+l-1))"; done)
|
|
96
|
+
if [ -n "$h" ]; then printf '%s\n' "$h"; else echo "1-$(wc -l < "$2" | tr -d ' ')"; fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# The audit's scope: every changed text file, minus generated, vendored and lock files. Tab-separated: paths may hold spaces.
|
|
100
|
+
scope_report() {
|
|
101
|
+
local f r new
|
|
102
|
+
echo "base $BASE"
|
|
103
|
+
printf '%s\n' "$CHANGED" | grep -vE '(^|/)(package-lock\.json|pnpm-lock\.yaml|[^/]*\.lock)$' | while IFS= read -r f; do
|
|
104
|
+
[ -f "$f" ] && ! is_ignored "$f" && grep -qI . "$f" || continue
|
|
105
|
+
r=$(file_hunks "$BASE" "$f" | sed -E 's/^([0-9]+)-\1$/\1/' | paste -sd, -)
|
|
106
|
+
new=""; git cat-file -e "$BASE:$f" 2>/dev/null || new=" (new)"
|
|
107
|
+
printf '%s\t%s%s\n' "$f" "$r" "$new"
|
|
108
|
+
done
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
# Ignored extensions are left out; see the comment where ship-gate.sh first calls this.
|
|
93
112
|
receipt_key() {
|
|
94
113
|
{ printf '%s\n' "$PROJECT_DIR"
|
package/hooks/ship-gate.sh
CHANGED
|
@@ -52,6 +52,7 @@ HERE=$(cd "$(dirname "$0")" && pwd)
|
|
|
52
52
|
MODE=""
|
|
53
53
|
case "${1:-}" in
|
|
54
54
|
--key) MODE=key; shift ;;
|
|
55
|
+
--scope) MODE=scope; shift ;;
|
|
55
56
|
--force) MODE=force; shift ;;
|
|
56
57
|
--baseline) MODE=baseline; shift ;;
|
|
57
58
|
esac
|
|
@@ -81,6 +82,7 @@ for B in ${GAUNTLET_BASE:-} develop dev main; do [ -n "$BASE" ] || BASE=$(git me
|
|
|
81
82
|
[ -n "$BASE" ] || BASE=$(git rev-list --max-parents=0 HEAD | head -1)
|
|
82
83
|
|
|
83
84
|
CHANGED=$(changed_files)
|
|
85
|
+
[ "$MODE" = scope ] && { scope_report; exit 0; }
|
|
84
86
|
|
|
85
87
|
# Only the files this gate can actually read go into the key. It used to be every
|
|
86
88
|
# changed file, so a version bump in package.json or a line in a changelog threw
|
|
@@ -205,10 +207,7 @@ for owner in $OWNERS; do
|
|
|
205
207
|
FLAGS=""
|
|
206
208
|
GLOBBED=","
|
|
207
209
|
while IFS= read -r f; do
|
|
208
|
-
hunks=$(
|
|
209
|
-
| grep -oE '^@@ -[0-9,]+ \+[0-9]+(,[0-9]+)?' | sed 's/.*+//' \
|
|
210
|
-
| while IFS=, read -r s l; do l=${l:-1}; [ "$l" -gt 0 ] && echo "$s-$((s+l-1))"; done)
|
|
211
|
-
[ -z "$hunks" ] && hunks="1-$(wc -l < "$f")"
|
|
210
|
+
hunks=$(file_hunks "$BASE" "$f")
|
|
212
211
|
rel=${f#$owner/}
|
|
213
212
|
# A Next.js route folder — [id], [[...slug]] — is a glob expression to
|
|
214
213
|
# Stryker, which refuses "Cannot combine a glob expression with a mutation
|
package/hooks/ship-log.mjs
CHANGED
|
@@ -145,8 +145,8 @@ function run(mode, args) {
|
|
|
145
145
|
if (todo.length) console.log(todo.map((t) => ` - ${t}`).join("\n"));
|
|
146
146
|
return todo.length ? 1 : 0;
|
|
147
147
|
}
|
|
148
|
-
// A gate run
|
|
149
|
-
if (mode === "gate" && !existsSync(path)) return 0;
|
|
148
|
+
// A gate run or a review outside /ship is not a ship; only /ship's start opens one.
|
|
149
|
+
if ((mode === "gate" || mode === "step") && !existsSync(path)) return 0;
|
|
150
150
|
if (mode === "gate") addGateRun(record, args);
|
|
151
151
|
else if (mode === "finding") addFinding(record, args[0]);
|
|
152
152
|
else if (mode === "step") addStep(record, args);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
3
|
+
. "$HERE/gate-config.sh" || { echo "ship-prep: cannot read $HERE/gate-config.sh"; exit 1; }
|
|
4
|
+
cd "${CLAUDE_PROJECT_DIR:-$PWD}" 2>/dev/null || { echo "ship-prep: cannot read ${CLAUDE_PROJECT_DIR:-$PWD}"; exit 1; }
|
|
5
|
+
|
|
6
|
+
stop() { echo "ship-prep: STOP — $1"; exit 1; }
|
|
7
|
+
|
|
8
|
+
first_remote() { # first_remote <branch...> — the first one origin has
|
|
9
|
+
local b
|
|
10
|
+
for b in "$@"; do git rev-parse -q --verify "origin/$b" >/dev/null && { echo "$b"; return; }; done
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
base_branch() { local dev; dev=$(first_remote develop development dev); echo "${dev:-$(first_remote main master)}"; }
|
|
14
|
+
|
|
15
|
+
start() {
|
|
16
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || stop "not a git repo."
|
|
17
|
+
local branch base dirty behind ahead
|
|
18
|
+
branch=$(git branch --show-current)
|
|
19
|
+
[ -n "$branch" ] || stop "Detached HEAD — check out a branch first."
|
|
20
|
+
if git remote | grep -qx origin; then git fetch -q origin 2>/dev/null; else echo "remote none: pr, merge and release cannot run"; fi
|
|
21
|
+
base=$(base_branch)
|
|
22
|
+
echo "branch $branch"
|
|
23
|
+
echo "base ${base:-none} (main $(first_remote main master))"
|
|
24
|
+
if ! git rev-parse -q --verify HEAD >/dev/null; then echo "commits none"
|
|
25
|
+
elif [ -n "$base" ]; then
|
|
26
|
+
read -r behind ahead < <(git rev-list --left-right --count "origin/$base...HEAD")
|
|
27
|
+
echo "ahead $ahead, behind $behind of origin/$base"
|
|
28
|
+
fi
|
|
29
|
+
dirty=$(git status --porcelain)
|
|
30
|
+
echo "uncommitted $(printf '%s' "$dirty" | grep -c .)"
|
|
31
|
+
[ -n "$dirty" ] && printf '%s\n' "$dirty" | sed 's/^/ /'
|
|
32
|
+
if gh auth status >/dev/null 2>&1; then
|
|
33
|
+
echo "gh logged in"
|
|
34
|
+
echo "pr $(gh pr view --json state,number,url -q '.state+" #"+(.number|tostring)+" "+.url' 2>/dev/null || echo none)"
|
|
35
|
+
else
|
|
36
|
+
echo "gh NOT logged in: pr, merge and release cannot run"
|
|
37
|
+
fi
|
|
38
|
+
bash "$HERE/ship-mark.sh" begin || stop "ship-mark.sh begin failed."
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
skills_report() { # skills_report <files...>
|
|
42
|
+
local f name paths hits
|
|
43
|
+
for f in "$(diary_dir "$HERE")"/skills/*/SKILL.md; do
|
|
44
|
+
[ -f "$f" ] || continue
|
|
45
|
+
name=$(skill_field "$f" name); paths=$(skill_field "$f" '[[:space:]]*gate-paths')
|
|
46
|
+
if [ -z "$paths" ]; then echo "skill $name: decide from its description"; continue; fi
|
|
47
|
+
hits=$(skill_hits "$paths" "$@" | paste -sd' ' -)
|
|
48
|
+
if [ -n "$hits" ]; then echo "skill $name: audit $hits"; else echo "skill $name: skip, no changed file matches $paths"; fi
|
|
49
|
+
done
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
scope() {
|
|
53
|
+
local branch scope base files=() diff f
|
|
54
|
+
cd "$(git rev-parse --show-toplevel)" || stop "cannot find the repo root."
|
|
55
|
+
branch=$(git branch --show-current)
|
|
56
|
+
scope=$(bash "$HERE/ship-gate.sh" --scope) || stop "ship-gate.sh --scope failed: $scope"
|
|
57
|
+
printf '%s\n' "$scope"
|
|
58
|
+
base=$(printf '%s\n' "$scope" | sed -n '1s/^base //p')
|
|
59
|
+
while IFS=$'\t' read -r f _; do [ -n "$f" ] && files+=("$f"); done < <(printf '%s\n' "$scope" | sed '1d')
|
|
60
|
+
skills_report "${files[@]}"
|
|
61
|
+
diff="/tmp/claude-ship-$(printf '%s %s' "$PWD" "$branch" | cksum | cut -d' ' -f1).diff"
|
|
62
|
+
: > "$diff"
|
|
63
|
+
if [ ${#files[@]} -gt 0 ]; then
|
|
64
|
+
git diff "$base" -- "${files[@]}" >> "$diff"
|
|
65
|
+
for f in "${files[@]}"; do
|
|
66
|
+
git ls-files --error-unmatch "$f" >/dev/null 2>&1 || git diff --no-index /dev/null "$f" >> "$diff"
|
|
67
|
+
done
|
|
68
|
+
fi
|
|
69
|
+
echo "diff $diff"
|
|
70
|
+
echo "review /code-review medium origin/$(base_branch)...$branch"
|
|
71
|
+
node "$HERE/ship-log.mjs" step audit start >/dev/null 2>&1
|
|
72
|
+
node "$HERE/ship-log.mjs" step review start >/dev/null 2>&1
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
secrets() {
|
|
77
|
+
local branch names hits
|
|
78
|
+
branch=$(git branch --show-current)
|
|
79
|
+
case "$branch" in main|master|develop|development|dev) stop "on protected branch $branch — commit on a feature or hotfix/<desc> branch." ;; esac
|
|
80
|
+
git status --short
|
|
81
|
+
names=$({ git diff HEAD --name-only --diff-filter=d; git ls-files --others --exclude-standard; } 2>/dev/null \
|
|
82
|
+
| grep -iE '(^|/)(\.env(\..*)?|[^/]*\.pem|[^/]*credentials[^/]*|[^/]*\.key)$' | grep -viE '\.(example|sample|template)$')
|
|
83
|
+
hits=$({ git diff HEAD; git ls-files --others --exclude-standard -z | xargs -0 -r -I{} git diff --no-index /dev/null {}; } 2>/dev/null \
|
|
84
|
+
| grep -E '^\+' | grep -E 'AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----|xox[abprs]-[A-Za-z0-9-]{10,}|[Bb]earer [A-Za-z0-9._~+/-]{20,}' | cut -c1-120)
|
|
85
|
+
[ -z "$names$hits" ] && { echo "secrets none"; return 0; }
|
|
86
|
+
echo "secrets FOUND — STOP and tell the user; suggest gitleaks or git-secrets as a pre-commit guard:"
|
|
87
|
+
[ -n "$names" ] && printf '%s\n' "$names" | sed 's/^/ file /'
|
|
88
|
+
[ -n "$hits" ] && printf '%s\n' "$hits" | sed 's/^/ line /'
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
CHECKS_FAILED='[.statusCheckRollup[]? | select(((.conclusion // .state) // "") | test("FAILURE|ERROR|CANCELLED|TIMED_OUT|ACTION_REQUIRED")) | (.name // .context)] | join(",")'
|
|
93
|
+
CHECKS_RUNNING='[.statusCheckRollup[]? | select((.status // .state // "") | test("IN_PROGRESS|QUEUED|PENDING|WAITING")) | (.name // .context)] | join(",")'
|
|
94
|
+
PR_JQ="[.number, .isDraft, .mergeable, (.reviewDecision // \"\"), ($CHECKS_FAILED), ($CHECKS_RUNNING), .baseRefName, .headRefName] | map(tostring) | join(\"|\")"
|
|
95
|
+
|
|
96
|
+
merge_check() { # merge_check [pr]; sets PR_BASE and PR_HEAD
|
|
97
|
+
local line n draft mergeable review failing pending why=""
|
|
98
|
+
line=$(gh pr view ${1:+"$1"} --json number,isDraft,mergeable,reviewDecision,statusCheckRollup,baseRefName,headRefName -q "$PR_JQ") || stop "no open PR found."
|
|
99
|
+
IFS='|' read -r n draft mergeable review failing pending PR_BASE PR_HEAD <<< "$line"
|
|
100
|
+
[ "$draft" = true ] && why="$why draft;"
|
|
101
|
+
[ "$mergeable" = CONFLICTING ] && why="$why conflicts with the base;"
|
|
102
|
+
[ -n "$failing" ] && why="$why CI failing: $failing;"
|
|
103
|
+
case "$review" in REVIEW_REQUIRED|CHANGES_REQUESTED) why="$why review: $review;" ;; esac
|
|
104
|
+
[ -n "$pending" ] && why="$why CI still running: $pending;"
|
|
105
|
+
[ -z "$why" ] && { echo "merge ok #$n"; return 0; }
|
|
106
|
+
echo "merge STOP #$n:$why"; return 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
land() { # land <pr> [squash|merge]
|
|
110
|
+
local worktree=""
|
|
111
|
+
[ -n "${1:-}" ] || stop "usage: ship-prep.sh land <pr> [squash|merge]"
|
|
112
|
+
merge_check "$1" >/dev/null || { merge_check "$1"; exit 1; }
|
|
113
|
+
[ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ] && worktree=1
|
|
114
|
+
if [ "${2:-squash}" = merge ]; then gh pr merge "$1" --merge || stop "gh pr merge failed."
|
|
115
|
+
elif [ -n "$worktree" ]; then
|
|
116
|
+
# gh --delete-branch switches the local checkout, which a worktree cannot; delete the branch on origin instead.
|
|
117
|
+
gh pr merge "$1" --squash || stop "gh pr merge failed."
|
|
118
|
+
git push -q origin --delete "$PR_HEAD" 2>/dev/null
|
|
119
|
+
else gh pr merge "$1" --squash --delete-branch || stop "gh pr merge failed."; fi
|
|
120
|
+
if [ -n "$worktree" ]; then
|
|
121
|
+
git fetch -q origin 2>/dev/null
|
|
122
|
+
echo "landed #$1; this is a worktree, so it stays on its branch — close the session to remove it"
|
|
123
|
+
else
|
|
124
|
+
git checkout -q "$PR_BASE" && git pull -q --ff-only origin "$PR_BASE" 2>/dev/null
|
|
125
|
+
echo "landed #$1; now on $(git branch --show-current) at $(git rev-parse --short HEAD)"
|
|
126
|
+
fi
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
release() {
|
|
130
|
+
local tag v major minor patch commits breaking feat next
|
|
131
|
+
git fetch -q --tags origin 2>/dev/null
|
|
132
|
+
tag=$(git tag --sort=-v:refname | head -1)
|
|
133
|
+
[ -n "$tag" ] || { echo "tag none; suggest v0.1.0 (confirm with the user)"; return 0; }
|
|
134
|
+
commits=$(git log "$tag"..HEAD --format='%s')
|
|
135
|
+
[ -n "$commits" ] || stop "no commits since $tag — nothing to release."
|
|
136
|
+
v=${tag##*[!0-9.]}; IFS=. read -r major minor patch <<< "$v"
|
|
137
|
+
breaking=$(( $(printf '%s\n' "$commits" | grep -cE '^[a-z]+(\([^)]*\))?!:') + $(git log "$tag"..HEAD --format=%B | grep -c 'BREAKING CHANGE') ))
|
|
138
|
+
feat=$(printf '%s\n' "$commits" | grep -cE '^feat(\([^)]*\))?!?:')
|
|
139
|
+
if [ "$major" -ge 1 ]; then
|
|
140
|
+
if [ "$breaking" -gt 0 ]; then next="$((major+1)).0.0"; elif [ "$feat" -gt 0 ]; then next="$major.$((minor+1)).0"; else next="$major.$minor.$((patch+1))"; fi
|
|
141
|
+
elif [ "$breaking" -gt 0 ]; then next="0.$((minor+1)).0"
|
|
142
|
+
else next="0.$minor.$((patch+1))"; fi
|
|
143
|
+
echo "tag $tag"
|
|
144
|
+
echo "suggest ${tag%"$v"}$next (confirm with the user)"
|
|
145
|
+
printf '%s\n' "$commits" | sed 's/^/ /'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case "${1:-}" in
|
|
149
|
+
start) start ;;
|
|
150
|
+
scope) scope ;;
|
|
151
|
+
secrets) secrets ;;
|
|
152
|
+
merge) merge_check "${2:-}" ;;
|
|
153
|
+
land) land "${2:-}" "${3:-}" ;;
|
|
154
|
+
release) release ;;
|
|
155
|
+
*) echo "usage: ship-prep.sh start | scope | secrets | merge [pr] | land <pr> [squash|merge] | release" >&2; exit 2 ;;
|
|
156
|
+
esac
|
|
@@ -26,4 +26,7 @@ BRANCH=$(git -C "${CWD:-$PROJECT_DIR}" branch --show-current 2>/dev/null | json_
|
|
|
26
26
|
printf '{"ts":"%s","branch":"%s","model":"%s","reply":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$BRANCH" "$MODEL" "$REPLY" \
|
|
27
27
|
>> "$(diary_dir "$SCRIPT_DIR")/$LOG"
|
|
28
28
|
|
|
29
|
+
case "$LOG" in skill-flags.jsonl) STEP=audit ;; *) STEP=review ;; esac
|
|
30
|
+
SHIP_LOG_ROOT="${CWD:-$PROJECT_DIR}" node "$SCRIPT_DIR/ship-log.mjs" step "$STEP" end >/dev/null 2>&1
|
|
31
|
+
|
|
29
32
|
exit 0
|
package/hooks/skill-gate.sh
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
INPUT=$(cat)
|
|
5
5
|
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
. "$SCRIPT_DIR/gate-config.sh"
|
|
7
8
|
PROJECT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
8
9
|
|
|
9
10
|
if ! find "$PROJECT_DIR" -path '*/.claude/skills/*/SKILL.md' 2>/dev/null | grep -q .; then
|
|
@@ -130,25 +131,14 @@ if [ -n "$TARGETS" ]; then
|
|
|
130
131
|
#
|
|
131
132
|
# The value must be quoted in YAML either way: a scalar opening with * is
|
|
132
133
|
# an alias indicator.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
SNAME=$(sed -n 's/^name:[[:space:]]*//p' "$SKILL_FILE" | head -1 | sed "$UNQUOTE")
|
|
134
|
+
SPATHS=$(skill_field "$SKILL_FILE" '[[:space:]]*gate-paths')
|
|
135
|
+
STRACKS=$(skill_field "$SKILL_FILE" tracks)
|
|
136
|
+
SNAME=$(skill_field "$SKILL_FILE" name)
|
|
137
137
|
[ -z "$SPATHS" ] && continue
|
|
138
138
|
[ -z "$SNAME" ] && continue
|
|
139
139
|
case "$INSTALLED" in *"\"$SNAME\""*) ;; *) continue ;; esac # automark marks only installed and custom skills
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
for G in $(printf '%s' "$SPATHS" | tr ',' ' '); do
|
|
143
|
-
# Claude Code writes these gitignore-style. A case glob has no **, but *
|
|
144
|
-
# already crosses / here, so dropping the **/ prefix is equivalent.
|
|
145
|
-
G=$(printf '%s' "$G" | sed 's!\*\*/!!g')
|
|
146
|
-
case "$G" in \**|/*) ;; *) G="*$G" ;; esac
|
|
147
|
-
for T in $TARGETS; do
|
|
148
|
-
case "$T" in $G) HIT=1 ;; esac
|
|
149
|
-
done
|
|
150
|
-
done
|
|
151
|
-
[ -z "$HIT" ] && continue
|
|
140
|
+
# shellcheck disable=SC2086
|
|
141
|
+
[ -n "$(skill_hits "$SPATHS" $TARGETS)" ] || continue
|
|
152
142
|
|
|
153
143
|
# A .tsx file is not proof of React — it could be Astro, Solid or Preact.
|
|
154
144
|
# The packages the skill tracks are the proof, so the manifests decide.
|
package/lib/setup-hook.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { homedir } from "node:os";
|
|
|
22
22
|
// ship-gate-projects.sh sourced by ship-gate.sh — project detection, reuse
|
|
23
23
|
// ship-gate-hook.sh PreToolUse on Bash — no commit without a receipt
|
|
24
24
|
// ship-mark.sh written by /ship — no PR on a branch /ship never ran on
|
|
25
|
+
// ship-prep.sh run by /ship — prechecks, scope and skills in one call
|
|
25
26
|
// version-check.sh SessionStart — a line when a newer catalog exists
|
|
26
27
|
// skill-flags-record.sh SubagentStop — logs each skills-audit verdict
|
|
27
28
|
// skill-report.mjs read by /skill-report — tallies that log
|
|
@@ -55,6 +56,7 @@ const SHIP_LOG_FILENAME = "ship-log.mjs";
|
|
|
55
56
|
const SHIP_LOG_PARSE_FILENAME = "ship-log-parse.mjs";
|
|
56
57
|
const SHIP_LOG_REPORT_FILENAME = "ship-log-report.mjs";
|
|
57
58
|
const SHIP_MARK_FILENAME = "ship-mark.sh";
|
|
59
|
+
const SHIP_PREP_FILENAME = "ship-prep.sh";
|
|
58
60
|
const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
|
|
59
61
|
const LEGACY_AUDIT_RUNNER_FILENAME = "skill-audit-runner.sh";
|
|
60
62
|
|
|
@@ -163,6 +165,7 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
163
165
|
SHIP_LOG_PARSE_FILENAME,
|
|
164
166
|
SHIP_LOG_REPORT_FILENAME,
|
|
165
167
|
SHIP_MARK_FILENAME,
|
|
168
|
+
SHIP_PREP_FILENAME,
|
|
166
169
|
]) {
|
|
167
170
|
const dest = join(hooksDir, name);
|
|
168
171
|
await writeFile(dest, await readHookSource(name), { mode: 0o755 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spardutti/claude-skills",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.57.0",
|
|
4
4
|
"description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"claude-skills": "bin/cli.mjs"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/gate-config.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh ../scripts/skill-flags-record.sh ../scripts/skill-report.mjs ../scripts/edit-check.sh ../scripts/line-limit.sh ../scripts/lint-ratchet.mjs ../scripts/text-rules.mjs ../scripts/ratchet-baseline.mjs ../scripts/diary-check.mjs ../scripts/diary-lib.mjs ../scripts/ship-log.mjs ../scripts/ship-log-parse.mjs ../scripts/ship-log-report.mjs ../scripts/ship-mark.sh hooks/",
|
|
14
|
+
"prepack": "cp ../README.md README.md && mkdir -p hooks && cp ../scripts/skill-gate.sh ../scripts/skill-gate-automark.sh ../scripts/skill-application-gate.sh ../scripts/gauntlet.sh ../scripts/ship-gate.sh ../scripts/gate-config.sh ../scripts/ship-gate-projects.sh ../scripts/ship-gate-structure.sh ../scripts/ship-gate-files.sh ../scripts/ship-gate-hook.sh ../scripts/version-check.sh ../scripts/skill-flags-record.sh ../scripts/skill-report.mjs ../scripts/edit-check.sh ../scripts/line-limit.sh ../scripts/lint-ratchet.mjs ../scripts/text-rules.mjs ../scripts/ratchet-baseline.mjs ../scripts/diary-check.mjs ../scripts/diary-lib.mjs ../scripts/ship-log.mjs ../scripts/ship-log-parse.mjs ../scripts/ship-log-report.mjs ../scripts/ship-mark.sh ../scripts/ship-prep.sh hooks/",
|
|
15
15
|
"postpack": "git checkout -- README.md && rm -rf hooks"
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|