@olegkoval/agent-skills 1.26.0 → 1.28.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/.claude-plugin/plugin.json +2 -1
- package/README.md +7 -3
- package/catalog/skills.json +18 -0
- package/package.json +5 -3
- package/packages/software-development/lekker-review/SKILL.md +519 -0
- package/packages/software-development/lekker-review/adapters/claude/plugin.json +5 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/SKILL.md +520 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/completeness-critic.md +21 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/conventions.md +124 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/fix-verifier.md +84 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/fixer.md +120 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/implementation.md +53 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/prover.md +135 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/quality.md +72 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/simplification.md +45 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/test-quality.md +170 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/triage-logic.md +27 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/triage-quality.md +41 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/agents/verifier.md +295 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/artifact-page.md +143 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/context-gathering.md +162 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/fix-mode.md +329 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/github-post.md +205 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/house-rules.md +76 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/references/output-format.md +232 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/scripts/changed-files.sh +77 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/scripts/setup-worktree.sh +337 -0
- package/packages/software-development/lekker-review/adapters/claude/skills/lekker-review/scripts/verify-fixes.sh +231 -0
- package/packages/software-development/lekker-review/fix-workflow.js +273 -0
- package/packages/software-development/lekker-review/references/agents/completeness-critic.md +21 -0
- package/packages/software-development/lekker-review/references/agents/conventions.md +124 -0
- package/packages/software-development/lekker-review/references/agents/fix-verifier.md +84 -0
- package/packages/software-development/lekker-review/references/agents/fixer.md +120 -0
- package/packages/software-development/lekker-review/references/agents/implementation.md +53 -0
- package/packages/software-development/lekker-review/references/agents/prover.md +135 -0
- package/packages/software-development/lekker-review/references/agents/quality.md +72 -0
- package/packages/software-development/lekker-review/references/agents/simplification.md +45 -0
- package/packages/software-development/lekker-review/references/agents/test-quality.md +170 -0
- package/packages/software-development/lekker-review/references/agents/triage-logic.md +27 -0
- package/packages/software-development/lekker-review/references/agents/triage-quality.md +41 -0
- package/packages/software-development/lekker-review/references/agents/verifier.md +295 -0
- package/packages/software-development/lekker-review/references/artifact-page.md +143 -0
- package/packages/software-development/lekker-review/references/context-gathering.md +162 -0
- package/packages/software-development/lekker-review/references/fix-mode.md +329 -0
- package/packages/software-development/lekker-review/references/github-post.md +205 -0
- package/packages/software-development/lekker-review/references/house-rules.md +76 -0
- package/packages/software-development/lekker-review/references/output-format.md +232 -0
- package/packages/software-development/lekker-review/scripts/changed-files.sh +77 -0
- package/packages/software-development/lekker-review/scripts/setup-worktree.sh +337 -0
- package/packages/software-development/lekker-review/scripts/verify-fixes.sh +231 -0
- package/packages/software-development/lekker-review/workflow.js +602 -0
- package/site/assets/paperbag.css +707 -0
- package/site/assets/paperbag.js +218 -0
- package/site/build.mjs +380 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup-worktree.sh -- lekker-review helper
|
|
3
|
+
# Usage: setup-worktree.sh REPO_SLUG PR_BRANCH OUT_JSON [PREV_SHA]
|
|
4
|
+
# Emits a JSON result file at OUT_JSON and prints "OK <OUT_JSON>" on success.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
REPO_SLUG="${1:?REPO_SLUG required}"
|
|
9
|
+
PR_BRANCH="${2:?PR_BRANCH required}"
|
|
10
|
+
OUT_JSON="${3:?OUT_JSON required}"
|
|
11
|
+
PREV_SHA="${4:-}"
|
|
12
|
+
|
|
13
|
+
CACHE_DIR="$HOME/.cache/lekker-review"
|
|
14
|
+
CACHE_FILE="$CACHE_DIR/repo-map.tsv"
|
|
15
|
+
NOTES=""
|
|
16
|
+
WORKTREE_PATH="/tmp/lekker-review-$$"
|
|
17
|
+
|
|
18
|
+
mkdir -p "$CACHE_DIR"
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Helper: normalise a remote URL to an org/repo comparable key.
|
|
22
|
+
# Strips protocol, host, and trailing .git so "github.com/org/repo" ->
|
|
23
|
+
# "org/repo".
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
normalize_url() {
|
|
26
|
+
local url
|
|
27
|
+
url="$1"
|
|
28
|
+
url="${url%.git}"
|
|
29
|
+
# Strip https://host/ prefix
|
|
30
|
+
url="${url#*://*/}"
|
|
31
|
+
# For SCP-style git@host:org/repo - strip up to and including ':'
|
|
32
|
+
url="${url#*:}"
|
|
33
|
+
printf '%s' "$url"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
SLUG_NORM="$(normalize_url "https://github.com/${REPO_SLUG}.git")"
|
|
37
|
+
|
|
38
|
+
REPO_ROOT=""
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# 1a. Try the cache
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
44
|
+
while IFS=$'\t' read -r cached_slug cached_path; do
|
|
45
|
+
[[ "$cached_slug" == "$REPO_SLUG" ]] || continue
|
|
46
|
+
if [[ -d "$cached_path" ]]; then
|
|
47
|
+
local_url="$(git -C "$cached_path" remote get-url origin 2>/dev/null || true)"
|
|
48
|
+
local_norm="$(normalize_url "$local_url")"
|
|
49
|
+
if [[ "$local_norm" == "$SLUG_NORM" ]]; then
|
|
50
|
+
REPO_ROOT="$cached_path"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
break
|
|
54
|
+
done < "$CACHE_FILE"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# 1b. Try $PWD
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
if [[ -z "$REPO_ROOT" ]] && git -C "$PWD" remote get-url origin >/dev/null 2>&1; then
|
|
61
|
+
pwd_url="$(git -C "$PWD" remote get-url origin 2>/dev/null || true)"
|
|
62
|
+
pwd_norm="$(normalize_url "$pwd_url")"
|
|
63
|
+
if [[ "$pwd_norm" == "$SLUG_NORM" ]]; then
|
|
64
|
+
REPO_ROOT="$PWD"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# 1c. Scan ~/Work (maxdepth 4)
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
if [[ -z "$REPO_ROOT" ]]; then
|
|
72
|
+
while IFS= read -r git_dir; do
|
|
73
|
+
candidate="${git_dir%/.git}"
|
|
74
|
+
[[ -d "$candidate" ]] || continue
|
|
75
|
+
cand_url="$(git -C "$candidate" remote get-url origin 2>/dev/null || true)"
|
|
76
|
+
[[ -n "$cand_url" ]] || continue
|
|
77
|
+
cand_norm="$(normalize_url "$cand_url")"
|
|
78
|
+
if [[ "$cand_norm" == "$SLUG_NORM" ]]; then
|
|
79
|
+
REPO_ROOT="$candidate"
|
|
80
|
+
break
|
|
81
|
+
fi
|
|
82
|
+
done < <(find "$HOME/Work" -maxdepth 4 -name ".git" -type d 2>/dev/null)
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# 1d. Shallow clone as last resort (not cached)
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
if [[ -z "$REPO_ROOT" ]]; then
|
|
89
|
+
CLONE_DIR="/tmp/lekker-clone-$$"
|
|
90
|
+
git clone --depth=100 "https://github.com/${REPO_SLUG}.git" "$CLONE_DIR"
|
|
91
|
+
REPO_ROOT="$CLONE_DIR"
|
|
92
|
+
NOTES="${NOTES}cloned-shallow;"
|
|
93
|
+
else
|
|
94
|
+
# Refresh cache entry: remove stale line and append fresh one
|
|
95
|
+
if [[ -f "$CACHE_FILE" ]]; then
|
|
96
|
+
grep -v "^${REPO_SLUG} " "$CACHE_FILE" > "${CACHE_FILE}.tmp" 2>/dev/null || true
|
|
97
|
+
mv "${CACHE_FILE}.tmp" "$CACHE_FILE"
|
|
98
|
+
fi
|
|
99
|
+
printf '%s\t%s\n' "$REPO_SLUG" "$REPO_ROOT" >> "$CACHE_FILE"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# 2. Fetch + worktree
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
git -C "$REPO_ROOT" fetch origin "$PR_BRANCH" 2>/dev/null || true
|
|
106
|
+
|
|
107
|
+
if ! git -C "$REPO_ROOT" worktree add "$WORKTREE_PATH" "origin/$PR_BRANCH" 2>/dev/null; then
|
|
108
|
+
if ! git -C "$REPO_ROOT" worktree add "$WORKTREE_PATH" FETCH_HEAD 2>/dev/null; then
|
|
109
|
+
printf 'ERROR: could not create worktree at %s\n' "$WORKTREE_PATH" >&2
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
NOTES="${NOTES}worktree-used-FETCH_HEAD;"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# 3. node_modules symlink
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
NM_LINKED=false
|
|
119
|
+
if [[ -d "$REPO_ROOT/node_modules" ]]; then
|
|
120
|
+
ln -s "$REPO_ROOT/node_modules" "$WORKTREE_PATH/node_modules"
|
|
121
|
+
NM_LINKED=true
|
|
122
|
+
else
|
|
123
|
+
NOTES="${NOTES}no-node_modules-static-checks-skipped;"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# 3b. Determine changed files (to scope tsc/eslint to this PR's diff instead
|
|
128
|
+
# of the whole repo -- see scripts/changed-files.sh).
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
CHANGED_FILES_FILE="/tmp/lekker-changed-$$.txt"
|
|
131
|
+
BASE_REF_NOTE="$("$(dirname "$0")/changed-files.sh" "$WORKTREE_PATH" 2>&1 >"$CHANGED_FILES_FILE" | sed -n 's/^base-ref: //p')" || true
|
|
132
|
+
|
|
133
|
+
# A blank line here would make `grep -F -f` match EVERY tsc error line, turning
|
|
134
|
+
# the repo's standing type debt into "errors this PR introduced". Strip them.
|
|
135
|
+
if [[ -f "$CHANGED_FILES_FILE" ]]; then
|
|
136
|
+
grep -v '^[[:space:]]*$' "$CHANGED_FILES_FILE" > "${CHANGED_FILES_FILE}.clean" 2>/dev/null || true
|
|
137
|
+
mv "${CHANGED_FILES_FILE}.clean" "$CHANGED_FILES_FILE" 2>/dev/null || true
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
BASE_REF_VAL="null"
|
|
141
|
+
if [[ -n "$BASE_REF_NOTE" ]] && [[ "$BASE_REF_NOTE" != "unknown" ]]; then
|
|
142
|
+
BASE_REF_VAL="$BASE_REF_NOTE"
|
|
143
|
+
else
|
|
144
|
+
NOTES="${NOTES}static-checks-repo-wide-no-base-ref;"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Lintable, still-on-disk changed files (a deleted file must not go to eslint).
|
|
148
|
+
ESLINT_TARGETS=()
|
|
149
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
150
|
+
while IFS= read -r cf; do
|
|
151
|
+
[[ -z "$cf" ]] && continue
|
|
152
|
+
case "$cf" in
|
|
153
|
+
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs) ;;
|
|
154
|
+
*) continue ;;
|
|
155
|
+
esac
|
|
156
|
+
[[ -f "$WORKTREE_PATH/$cf" ]] || continue
|
|
157
|
+
ESLINT_TARGETS+=("$cf")
|
|
158
|
+
done < "$CHANGED_FILES_FILE"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# 4. Concurrent tsc + eslint (never fail the script)
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
TSC_TAIL_FILE="/tmp/lekker-tsc-$$.txt"
|
|
165
|
+
TSC_FULL_FILE="/tmp/lekker-tsc-full-$$.txt"
|
|
166
|
+
TSC_CHANGED_FILE="/tmp/lekker-tsc-changed-$$.txt"
|
|
167
|
+
TSC_COUNT_FILE="/tmp/lekker-tsc-count-$$.txt"
|
|
168
|
+
ESLINT_TAIL_FILE="/tmp/lekker-eslint-$$.txt"
|
|
169
|
+
ESLINT_SCOPE_FILE="/tmp/lekker-eslint-scope-$$.txt"
|
|
170
|
+
printf '' > "$TSC_TAIL_FILE"
|
|
171
|
+
printf '' > "$TSC_CHANGED_FILE"
|
|
172
|
+
printf '0' > "$TSC_COUNT_FILE"
|
|
173
|
+
printf '' > "$ESLINT_TAIL_FILE"
|
|
174
|
+
printf 'skipped' > "$ESLINT_SCOPE_FILE"
|
|
175
|
+
|
|
176
|
+
run_tsc() {
|
|
177
|
+
# tsc needs the whole program to resolve types, so it always runs
|
|
178
|
+
# repo-wide; only the reported OUTPUT is split by changed-file scope.
|
|
179
|
+
if [[ -f "$WORKTREE_PATH/tsconfig.json" ]] && [[ "$NM_LINKED" == "true" ]]; then
|
|
180
|
+
(cd "$WORKTREE_PATH" && npx tsc --noEmit) > "$TSC_FULL_FILE" 2>&1 || true
|
|
181
|
+
tail -40 "$TSC_FULL_FILE" >> "$TSC_TAIL_FILE" || true
|
|
182
|
+
grep -c 'error TS' "$TSC_FULL_FILE" > "$TSC_COUNT_FILE" 2>/dev/null || printf '0' > "$TSC_COUNT_FILE"
|
|
183
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
184
|
+
grep -F -f "$CHANGED_FILES_FILE" "$TSC_FULL_FILE" 2>/dev/null | tail -40 > "$TSC_CHANGED_FILE" || true
|
|
185
|
+
fi
|
|
186
|
+
rm -f "$TSC_FULL_FILE"
|
|
187
|
+
else
|
|
188
|
+
printf 'skipped\n' > "$TSC_TAIL_FILE"
|
|
189
|
+
printf '0' > "$TSC_COUNT_FILE"
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
run_eslint() {
|
|
194
|
+
local has_config
|
|
195
|
+
has_config=false
|
|
196
|
+
if [[ "$NM_LINKED" == "true" ]]; then
|
|
197
|
+
(cd "$WORKTREE_PATH" && compgen -G '.eslintrc*' >/dev/null 2>&1) && has_config=true
|
|
198
|
+
if [[ "$has_config" == "false" ]]; then
|
|
199
|
+
(cd "$WORKTREE_PATH" && compgen -G 'eslint.config*' >/dev/null 2>&1) && has_config=true
|
|
200
|
+
fi
|
|
201
|
+
if [[ "$has_config" == "true" ]]; then
|
|
202
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
203
|
+
if [[ ${#ESLINT_TARGETS[@]} -gt 0 ]]; then
|
|
204
|
+
# Default (stylish) formatter: ESLint 9 removed the bundled "compact" formatter
|
|
205
|
+
(cd "$WORKTREE_PATH" && npx eslint --max-warnings=0 "${ESLINT_TARGETS[@]}" 2>&1 | tail -40 >> "$ESLINT_TAIL_FILE") || true
|
|
206
|
+
printf 'changed-files' > "$ESLINT_SCOPE_FILE"
|
|
207
|
+
else
|
|
208
|
+
printf 'no-changed-lintable-files\n' > "$ESLINT_TAIL_FILE"
|
|
209
|
+
printf 'none' > "$ESLINT_SCOPE_FILE"
|
|
210
|
+
fi
|
|
211
|
+
else
|
|
212
|
+
# Base ref could not be resolved -- fall back to the old repo-wide run.
|
|
213
|
+
(cd "$WORKTREE_PATH" && npx eslint . --max-warnings=0 2>&1 | tail -40 >> "$ESLINT_TAIL_FILE") || true
|
|
214
|
+
printf 'full-fallback' > "$ESLINT_SCOPE_FILE"
|
|
215
|
+
fi
|
|
216
|
+
else
|
|
217
|
+
printf 'no-eslint-config\n' > "$ESLINT_TAIL_FILE"
|
|
218
|
+
printf 'no-eslint-config' > "$ESLINT_SCOPE_FILE"
|
|
219
|
+
fi
|
|
220
|
+
else
|
|
221
|
+
printf 'skipped\n' > "$ESLINT_TAIL_FILE"
|
|
222
|
+
printf 'skipped' > "$ESLINT_SCOPE_FILE"
|
|
223
|
+
fi
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
run_tsc &
|
|
227
|
+
TSC_PID=$!
|
|
228
|
+
run_eslint &
|
|
229
|
+
ESLINT_PID=$!
|
|
230
|
+
wait "$TSC_PID" || true
|
|
231
|
+
wait "$ESLINT_PID" || true
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# 5. Collect PROJECT_RULES from CLAUDE.md files (depth <= 3, skip node_modules)
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
PROJECT_RULES_FILE="/tmp/lekker-rules-$$.txt"
|
|
237
|
+
printf '' > "$PROJECT_RULES_FILE"
|
|
238
|
+
|
|
239
|
+
while IFS= read -r claude_md; do
|
|
240
|
+
printf '=== %s ===\n' "$claude_md" >> "$PROJECT_RULES_FILE"
|
|
241
|
+
cat "$claude_md" >> "$PROJECT_RULES_FILE"
|
|
242
|
+
printf '\n' >> "$PROJECT_RULES_FILE"
|
|
243
|
+
done < <(find "$WORKTREE_PATH" -maxdepth 3 -name 'CLAUDE.md' \
|
|
244
|
+
-not -path '*/node_modules/*' 2>/dev/null)
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# 6. HEAD SHA
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
HEAD_SHA="$(git -C "$WORKTREE_PATH" rev-parse HEAD)"
|
|
250
|
+
HEAD_SHA_SHORT="$(git -C "$WORKTREE_PATH" rev-parse --short HEAD)"
|
|
251
|
+
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
# 7. Delta diff (PREV_SHA)
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
DELTA_FILE_VAL="null"
|
|
256
|
+
if [[ -n "$PREV_SHA" ]]; then
|
|
257
|
+
if git -C "$WORKTREE_PATH" cat-file -e "$PREV_SHA" 2>/dev/null; then
|
|
258
|
+
DELTA_FILE_PATH="/tmp/lekker-delta-$$.diff"
|
|
259
|
+
git -C "$WORKTREE_PATH" diff "${PREV_SHA}..HEAD" > "$DELTA_FILE_PATH"
|
|
260
|
+
DELTA_FILE_VAL="$DELTA_FILE_PATH"
|
|
261
|
+
else
|
|
262
|
+
NOTES="${NOTES}prev-sha-not-found-full-review;"
|
|
263
|
+
fi
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# 8. Emit JSON via python3 (safe escaping via json.dump)
|
|
268
|
+
# All values passed as argv to avoid any shell-escaping in the heredoc.
|
|
269
|
+
# ---------------------------------------------------------------------------
|
|
270
|
+
python3 - \
|
|
271
|
+
"$WORKTREE_PATH" \
|
|
272
|
+
"$REPO_ROOT" \
|
|
273
|
+
"$HEAD_SHA" \
|
|
274
|
+
"$HEAD_SHA_SHORT" \
|
|
275
|
+
"$TSC_TAIL_FILE" \
|
|
276
|
+
"$ESLINT_TAIL_FILE" \
|
|
277
|
+
"$PROJECT_RULES_FILE" \
|
|
278
|
+
"$DELTA_FILE_VAL" \
|
|
279
|
+
"$NOTES" \
|
|
280
|
+
"$CHANGED_FILES_FILE" \
|
|
281
|
+
"$BASE_REF_VAL" \
|
|
282
|
+
"$ESLINT_SCOPE_FILE" \
|
|
283
|
+
"$TSC_CHANGED_FILE" \
|
|
284
|
+
"$TSC_COUNT_FILE" \
|
|
285
|
+
"$OUT_JSON" <<'PYEOF'
|
|
286
|
+
import json, sys, os
|
|
287
|
+
|
|
288
|
+
(worktree_path, repo_root, head_sha, head_sha_short,
|
|
289
|
+
tsc_file, eslint_file, rules_file, delta_file, notes,
|
|
290
|
+
changed_files_file, base_ref_val, eslint_scope_file,
|
|
291
|
+
tsc_changed_file, tsc_count_file,
|
|
292
|
+
out_json) = sys.argv[1:16]
|
|
293
|
+
|
|
294
|
+
def read_file(path):
|
|
295
|
+
if not os.path.exists(path):
|
|
296
|
+
return ""
|
|
297
|
+
with open(path, "r", errors="replace") as f:
|
|
298
|
+
return f.read()
|
|
299
|
+
|
|
300
|
+
changed_files = [l for l in read_file(changed_files_file).splitlines() if l.strip()]
|
|
301
|
+
|
|
302
|
+
eslint_scope = read_file(eslint_scope_file).strip() or "skipped"
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
tsc_error_count = int(read_file(tsc_count_file).strip() or "0")
|
|
306
|
+
except ValueError:
|
|
307
|
+
tsc_error_count = 0
|
|
308
|
+
|
|
309
|
+
data = {
|
|
310
|
+
"worktreePath": worktree_path,
|
|
311
|
+
"repoRoot": repo_root,
|
|
312
|
+
"headSha": head_sha,
|
|
313
|
+
"headShaShort": head_sha_short,
|
|
314
|
+
"tscTail": read_file(tsc_file),
|
|
315
|
+
"eslintTail": read_file(eslint_file),
|
|
316
|
+
"projectRules": read_file(rules_file),
|
|
317
|
+
"deltaFile": None if delta_file == "null" else delta_file,
|
|
318
|
+
"notes": notes,
|
|
319
|
+
"changedFiles": changed_files,
|
|
320
|
+
"baseRef": None if base_ref_val == "null" else base_ref_val,
|
|
321
|
+
"eslintScope": eslint_scope,
|
|
322
|
+
"tscChangedTail": read_file(tsc_changed_file),
|
|
323
|
+
"tscErrorCount": tsc_error_count,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
for p in (tsc_file, eslint_file, rules_file, changed_files_file,
|
|
327
|
+
eslint_scope_file, tsc_changed_file, tsc_count_file):
|
|
328
|
+
try:
|
|
329
|
+
os.remove(p)
|
|
330
|
+
except OSError:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
with open(out_json, "w") as f:
|
|
334
|
+
json.dump(data, f, indent=2)
|
|
335
|
+
|
|
336
|
+
print("OK " + out_json)
|
|
337
|
+
PYEOF
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# verify-fixes.sh -- lekker-review fix-mode helper
|
|
3
|
+
# Runs static checks + (optionally) the test suite against the worktree AFTER
|
|
4
|
+
# fix agents have edited it, and emits a JSON result file.
|
|
5
|
+
#
|
|
6
|
+
# Usage: verify-fixes.sh WORKTREE_PATH OUT_JSON [RUN_TESTS]
|
|
7
|
+
# RUN_TESTS: "tests" to run the package test script, anything else to skip.
|
|
8
|
+
#
|
|
9
|
+
# Never fails on a failing check -- a failing check is data, not an error.
|
|
10
|
+
# Exit code is non-zero only when the worktree itself is unusable.
|
|
11
|
+
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
WORKTREE_PATH="${1:?WORKTREE_PATH required}"
|
|
15
|
+
OUT_JSON="${2:?OUT_JSON required}"
|
|
16
|
+
RUN_TESTS="${3:-no-tests}"
|
|
17
|
+
|
|
18
|
+
if [[ ! -d "$WORKTREE_PATH/.git" ]] && [[ ! -f "$WORKTREE_PATH/.git" ]]; then
|
|
19
|
+
printf 'ERROR: %s is not a git worktree\n' "$WORKTREE_PATH" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
TSC_FILE="/tmp/lekker-fix-tsc-$$.txt"
|
|
24
|
+
TSC_FULL_FILE="/tmp/lekker-fix-tsc-full-$$.txt"
|
|
25
|
+
TSC_CHANGED_FILE="/tmp/lekker-fix-tsc-changed-$$.txt"
|
|
26
|
+
TSC_COUNT_FILE="/tmp/lekker-fix-tsc-count-$$.txt"
|
|
27
|
+
ESLINT_FILE="/tmp/lekker-fix-eslint-$$.txt"
|
|
28
|
+
ESLINT_SCOPE_FILE="/tmp/lekker-fix-eslint-scope-$$.txt"
|
|
29
|
+
TEST_FILE="/tmp/lekker-fix-test-$$.txt"
|
|
30
|
+
STAT_FILE="/tmp/lekker-fix-stat-$$.txt"
|
|
31
|
+
printf '' > "$TSC_FILE"
|
|
32
|
+
printf '' > "$TSC_CHANGED_FILE"
|
|
33
|
+
printf '0' > "$TSC_COUNT_FILE"
|
|
34
|
+
printf '' > "$ESLINT_FILE"
|
|
35
|
+
printf 'skipped' > "$ESLINT_SCOPE_FILE"
|
|
36
|
+
printf '' > "$TEST_FILE"
|
|
37
|
+
|
|
38
|
+
HAS_NM=false
|
|
39
|
+
[[ -d "$WORKTREE_PATH/node_modules" || -L "$WORKTREE_PATH/node_modules" ]] && HAS_NM=true
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Uncommitted change footprint (what the fix agents actually wrote)
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
git -C "$WORKTREE_PATH" diff --stat > "$STAT_FILE" 2>&1 || true
|
|
45
|
+
DIRTY="$(git -C "$WORKTREE_PATH" status --porcelain 2>/dev/null || true)"
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Determine changed files (to scope tsc/eslint identically to setup-worktree
|
|
49
|
+
# so fix-mode's post-fix numbers compare like-for-like against the baseline).
|
|
50
|
+
# --include-uncommitted also picks up what the fix agents just wrote, even if
|
|
51
|
+
# it wasn't part of the original PR diff.
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
CHANGED_FILES_FILE="/tmp/lekker-fix-changed-$$.txt"
|
|
54
|
+
BASE_REF_NOTE="$("$(dirname "$0")/changed-files.sh" "$WORKTREE_PATH" --include-uncommitted 2>&1 >"$CHANGED_FILES_FILE" | sed -n 's/^base-ref: //p')" || true
|
|
55
|
+
|
|
56
|
+
# A blank line here would make `grep -F -f` match EVERY tsc error line, turning
|
|
57
|
+
# the repo's standing type debt into "errors the fixes introduced". Strip them.
|
|
58
|
+
if [[ -f "$CHANGED_FILES_FILE" ]]; then
|
|
59
|
+
grep -v '^[[:space:]]*$' "$CHANGED_FILES_FILE" > "${CHANGED_FILES_FILE}.clean" 2>/dev/null || true
|
|
60
|
+
mv "${CHANGED_FILES_FILE}.clean" "$CHANGED_FILES_FILE" 2>/dev/null || true
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
BASE_REF_VAL="null"
|
|
64
|
+
if [[ -n "$BASE_REF_NOTE" ]] && [[ "$BASE_REF_NOTE" != "unknown" ]]; then
|
|
65
|
+
BASE_REF_VAL="$BASE_REF_NOTE"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Lintable, still-on-disk changed files (a deleted file must not go to eslint).
|
|
69
|
+
ESLINT_TARGETS=()
|
|
70
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
71
|
+
while IFS= read -r cf; do
|
|
72
|
+
[[ -z "$cf" ]] && continue
|
|
73
|
+
case "$cf" in
|
|
74
|
+
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs) ;;
|
|
75
|
+
*) continue ;;
|
|
76
|
+
esac
|
|
77
|
+
[[ -f "$WORKTREE_PATH/$cf" ]] || continue
|
|
78
|
+
ESLINT_TARGETS+=("$cf")
|
|
79
|
+
done < "$CHANGED_FILES_FILE"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# tsc + eslint (concurrent)
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
run_tsc() {
|
|
86
|
+
# tsc needs the whole program to resolve types, so it always runs
|
|
87
|
+
# repo-wide; only the reported OUTPUT is split by changed-file scope.
|
|
88
|
+
if [[ -f "$WORKTREE_PATH/tsconfig.json" ]] && [[ "$HAS_NM" == "true" ]]; then
|
|
89
|
+
(cd "$WORKTREE_PATH" && npx tsc --noEmit) > "$TSC_FULL_FILE" 2>&1 || true
|
|
90
|
+
tail -40 "$TSC_FULL_FILE" >> "$TSC_FILE" || true
|
|
91
|
+
grep -c 'error TS' "$TSC_FULL_FILE" > "$TSC_COUNT_FILE" 2>/dev/null || printf '0' > "$TSC_COUNT_FILE"
|
|
92
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
93
|
+
grep -F -f "$CHANGED_FILES_FILE" "$TSC_FULL_FILE" 2>/dev/null | tail -40 > "$TSC_CHANGED_FILE" || true
|
|
94
|
+
fi
|
|
95
|
+
rm -f "$TSC_FULL_FILE"
|
|
96
|
+
else
|
|
97
|
+
printf 'skipped\n' > "$TSC_FILE"
|
|
98
|
+
printf '0' > "$TSC_COUNT_FILE"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
run_eslint() {
|
|
103
|
+
if [[ "$HAS_NM" != "true" ]]; then
|
|
104
|
+
printf 'skipped\n' > "$ESLINT_FILE"
|
|
105
|
+
printf 'skipped' > "$ESLINT_SCOPE_FILE"
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
local has_config
|
|
109
|
+
has_config=false
|
|
110
|
+
(cd "$WORKTREE_PATH" && compgen -G '.eslintrc*' >/dev/null 2>&1) && has_config=true
|
|
111
|
+
if [[ "$has_config" == "false" ]]; then
|
|
112
|
+
(cd "$WORKTREE_PATH" && compgen -G 'eslint.config*' >/dev/null 2>&1) && has_config=true
|
|
113
|
+
fi
|
|
114
|
+
if [[ "$has_config" == "true" ]]; then
|
|
115
|
+
if [[ -s "$CHANGED_FILES_FILE" ]]; then
|
|
116
|
+
if [[ ${#ESLINT_TARGETS[@]} -gt 0 ]]; then
|
|
117
|
+
(cd "$WORKTREE_PATH" && npx eslint --max-warnings=0 "${ESLINT_TARGETS[@]}" 2>&1 | tail -40 >> "$ESLINT_FILE") || true
|
|
118
|
+
printf 'changed-files' > "$ESLINT_SCOPE_FILE"
|
|
119
|
+
else
|
|
120
|
+
printf 'no-changed-lintable-files\n' > "$ESLINT_FILE"
|
|
121
|
+
printf 'none' > "$ESLINT_SCOPE_FILE"
|
|
122
|
+
fi
|
|
123
|
+
else
|
|
124
|
+
# Base ref could not be resolved -- fall back to the old repo-wide run.
|
|
125
|
+
(cd "$WORKTREE_PATH" && npx eslint . --max-warnings=0 2>&1 | tail -40 >> "$ESLINT_FILE") || true
|
|
126
|
+
printf 'full-fallback' > "$ESLINT_SCOPE_FILE"
|
|
127
|
+
fi
|
|
128
|
+
else
|
|
129
|
+
printf 'no-eslint-config\n' > "$ESLINT_FILE"
|
|
130
|
+
printf 'no-eslint-config' > "$ESLINT_SCOPE_FILE"
|
|
131
|
+
fi
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
run_tsc &
|
|
135
|
+
TSC_PID=$!
|
|
136
|
+
run_eslint &
|
|
137
|
+
ESLINT_PID=$!
|
|
138
|
+
wait "$TSC_PID" || true
|
|
139
|
+
wait "$ESLINT_PID" || true
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Test suite (sequential, opt-in, hard timeout)
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
TEST_RAN=false
|
|
145
|
+
TEST_EXIT="null"
|
|
146
|
+
if [[ "$RUN_TESTS" == "tests" ]] && [[ "$HAS_NM" == "true" ]] \
|
|
147
|
+
&& [[ -f "$WORKTREE_PATH/package.json" ]] \
|
|
148
|
+
&& grep -q '"test"[[:space:]]*:' "$WORKTREE_PATH/package.json"; then
|
|
149
|
+
TEST_RAN=true
|
|
150
|
+
# `set -o pipefail` is active, so the subshell's exit status already
|
|
151
|
+
# reflects a failing `npm test` even though tail is last in the pipeline.
|
|
152
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
153
|
+
(cd "$WORKTREE_PATH" && timeout 600 npm test --silent 2>&1 | tail -60 >> "$TEST_FILE")
|
|
154
|
+
else
|
|
155
|
+
(cd "$WORKTREE_PATH" && npm test --silent 2>&1 | tail -60 >> "$TEST_FILE")
|
|
156
|
+
fi
|
|
157
|
+
TEST_EXIT=$?
|
|
158
|
+
else
|
|
159
|
+
printf 'skipped\n' > "$TEST_FILE"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
HEAD_SHA_SHORT="$(git -C "$WORKTREE_PATH" rev-parse --short HEAD 2>/dev/null || printf 'unknown')"
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# Emit JSON
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
python3 - \
|
|
168
|
+
"$TSC_FILE" \
|
|
169
|
+
"$ESLINT_FILE" \
|
|
170
|
+
"$TEST_FILE" \
|
|
171
|
+
"$STAT_FILE" \
|
|
172
|
+
"$TEST_RAN" \
|
|
173
|
+
"$TEST_EXIT" \
|
|
174
|
+
"$HEAD_SHA_SHORT" \
|
|
175
|
+
"$DIRTY" \
|
|
176
|
+
"$CHANGED_FILES_FILE" \
|
|
177
|
+
"$BASE_REF_VAL" \
|
|
178
|
+
"$ESLINT_SCOPE_FILE" \
|
|
179
|
+
"$TSC_CHANGED_FILE" \
|
|
180
|
+
"$TSC_COUNT_FILE" \
|
|
181
|
+
"$OUT_JSON" <<'PYEOF'
|
|
182
|
+
import json, sys, os
|
|
183
|
+
|
|
184
|
+
(tsc_file, eslint_file, test_file, stat_file, test_ran, test_exit,
|
|
185
|
+
head_sha_short, dirty, changed_files_file, base_ref_val,
|
|
186
|
+
eslint_scope_file, tsc_changed_file, tsc_count_file,
|
|
187
|
+
out_json) = sys.argv[1:15]
|
|
188
|
+
|
|
189
|
+
def read_file(path):
|
|
190
|
+
if not os.path.exists(path):
|
|
191
|
+
return ""
|
|
192
|
+
with open(path, "r", errors="replace") as f:
|
|
193
|
+
return f.read()
|
|
194
|
+
|
|
195
|
+
changed_files = [l for l in read_file(changed_files_file).splitlines() if l.strip()]
|
|
196
|
+
|
|
197
|
+
eslint_scope = read_file(eslint_scope_file).strip() or "skipped"
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
tsc_error_count = int(read_file(tsc_count_file).strip() or "0")
|
|
201
|
+
except ValueError:
|
|
202
|
+
tsc_error_count = 0
|
|
203
|
+
|
|
204
|
+
data = {
|
|
205
|
+
"tscTail": read_file(tsc_file),
|
|
206
|
+
"eslintTail": read_file(eslint_file),
|
|
207
|
+
"testTail": read_file(test_file),
|
|
208
|
+
"testRan": test_ran == "true",
|
|
209
|
+
"testExitCode": None if test_exit in ("null", "") else int(test_exit),
|
|
210
|
+
"diffStat": read_file(stat_file),
|
|
211
|
+
"dirtyPaths": [l for l in dirty.splitlines() if l.strip()],
|
|
212
|
+
"headShaShort": head_sha_short,
|
|
213
|
+
"changedFiles": changed_files,
|
|
214
|
+
"baseRef": None if base_ref_val == "null" else base_ref_val,
|
|
215
|
+
"eslintScope": eslint_scope,
|
|
216
|
+
"tscChangedTail": read_file(tsc_changed_file),
|
|
217
|
+
"tscErrorCount": tsc_error_count,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
for p in (tsc_file, eslint_file, test_file, stat_file, changed_files_file,
|
|
221
|
+
eslint_scope_file, tsc_changed_file, tsc_count_file):
|
|
222
|
+
try:
|
|
223
|
+
os.remove(p)
|
|
224
|
+
except OSError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
with open(out_json, "w") as f:
|
|
228
|
+
json.dump(data, f, indent=2)
|
|
229
|
+
|
|
230
|
+
print("OK " + out_json)
|
|
231
|
+
PYEOF
|