@pennyfarthing/core 7.7.0 → 7.8.1
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/package.json +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts +3 -0
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +134 -9
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +37 -2
- package/pennyfarthing-dist/agents/sm.md +68 -22
- package/pennyfarthing-dist/agents/workflow-status-check.md +11 -1
- package/pennyfarthing-dist/commands/git-cleanup.md +43 -308
- package/pennyfarthing-dist/commands/solo.md +31 -0
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
- package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +83 -83
- package/pennyfarthing-dist/personas/themes/the-expanse.yaml +11 -11
- package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -2
- package/pennyfarthing-dist/scripts/core/check-context.sh +3 -0
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
- package/pennyfarthing-dist/scripts/core/prime.sh +3 -157
- package/pennyfarthing-dist/scripts/core/run.sh +9 -0
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +117 -20
- package/pennyfarthing-dist/scripts/jira/README.md +10 -7
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +13 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +226 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +6 -5
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +319 -0
- package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +6 -5
- package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +270 -0
- package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +59 -0
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +8 -6
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +402 -0
- package/pennyfarthing-dist/scripts/workflow/check.sh +3 -476
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +61 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +13 -0
- package/pennyfarthing-dist/skills/judge/SKILL.md +57 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +4 -22
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +83 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +116 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +210 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +88 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +71 -0
- package/pennyfarthing-dist/workflows/git-cleanup.yaml +59 -0
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +0 -393
- package/pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs +0 -545
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +0 -327
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +0 -503
- package/pennyfarthing-dist/scripts/jira/jira-lib.mjs +0 -443
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.mjs +0 -208
- package/pennyfarthing-dist/scripts/jira/jira-sync.mjs +0 -198
- package/pennyfarthing-dist/scripts/misc/add-short-names.mjs +0 -264
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +0 -474
- package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.mjs +0 -377
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.js +0 -492
- /package/pennyfarthing-dist/guides/{AGENT-COORDINATION.md → agent-coordination.md} +0 -0
- /package/pennyfarthing-dist/guides/{HOOKS.md → hooks.md} +0 -0
- /package/pennyfarthing-dist/guides/{PROMPT-PATTERNS.md → prompt-patterns.md} +0 -0
- /package/pennyfarthing-dist/guides/{SESSION-ARTIFACTS.md → session-artifacts.md} +0 -0
- /package/pennyfarthing-dist/guides/{XML-TAGS.md → xml-tags.md} +0 -0
|
@@ -17,481 +17,8 @@
|
|
|
17
17
|
# Runs lint, type check, and tests. Reports pass/fail status.
|
|
18
18
|
# Returns exit code 0 on all passing, non-zero on any failure.
|
|
19
19
|
|
|
20
|
-
set -
|
|
20
|
+
set -euo pipefail
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
SKIP_CHECK=false
|
|
24
|
-
TESTS_ONLY=false
|
|
25
|
-
TEST_FILTER=""
|
|
26
|
-
TARGET_REPO=""
|
|
27
|
-
NO_LINT=false
|
|
28
|
-
NO_TYPECHECK=false
|
|
29
|
-
FAST_MODE=false
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
case "$1" in
|
|
33
|
-
--skip-check)
|
|
34
|
-
SKIP_CHECK=true
|
|
35
|
-
shift
|
|
36
|
-
;;
|
|
37
|
-
--tests-only)
|
|
38
|
-
TESTS_ONLY=true
|
|
39
|
-
shift
|
|
40
|
-
;;
|
|
41
|
-
--filter)
|
|
42
|
-
TEST_FILTER="$2"
|
|
43
|
-
shift 2
|
|
44
|
-
;;
|
|
45
|
-
--repo)
|
|
46
|
-
TARGET_REPO="$2"
|
|
47
|
-
shift 2
|
|
48
|
-
;;
|
|
49
|
-
--no-lint)
|
|
50
|
-
NO_LINT=true
|
|
51
|
-
shift
|
|
52
|
-
;;
|
|
53
|
-
--no-typecheck)
|
|
54
|
-
NO_TYPECHECK=true
|
|
55
|
-
shift
|
|
56
|
-
;;
|
|
57
|
-
--fast)
|
|
58
|
-
FAST_MODE=true
|
|
59
|
-
shift
|
|
60
|
-
;;
|
|
61
|
-
*)
|
|
62
|
-
echo "Unknown option: $1"
|
|
63
|
-
echo "Usage: check.sh [--skip-check] [--tests-only] [--filter PATTERN] [--repo REPO] [--no-lint] [--no-typecheck] [--fast]"
|
|
64
|
-
exit 1
|
|
65
|
-
;;
|
|
66
|
-
esac
|
|
67
|
-
done
|
|
68
|
-
|
|
69
|
-
# Handle --skip-check flag
|
|
70
|
-
if $SKIP_CHECK; then
|
|
71
|
-
echo "Quality checks skipped by --skip-check flag"
|
|
72
|
-
echo ""
|
|
73
|
-
echo "WARNING: Skipping checks is for emergencies only."
|
|
74
|
-
echo "Ensure checks pass before merging PR."
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
# Handle --tests-only (implies no-lint and no-typecheck)
|
|
79
|
-
if $TESTS_ONLY; then
|
|
80
|
-
NO_LINT=true
|
|
81
|
-
NO_TYPECHECK=true
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
# Colors (if terminal supports them)
|
|
85
|
-
if [[ -t 1 ]]; then
|
|
86
|
-
RED='\033[0;31m'
|
|
87
|
-
GREEN='\033[0;32m'
|
|
88
|
-
YELLOW='\033[0;33m'
|
|
89
|
-
CYAN='\033[0;36m'
|
|
90
|
-
NC='\033[0m' # No Color
|
|
91
|
-
else
|
|
92
|
-
RED=''
|
|
93
|
-
GREEN=''
|
|
94
|
-
YELLOW=''
|
|
95
|
-
CYAN=''
|
|
96
|
-
NC=''
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
# Counters
|
|
100
|
-
CHECKS_RUN=0
|
|
101
|
-
CHECKS_PASSED=0
|
|
102
|
-
CHECKS_FAILED=0
|
|
103
|
-
CHECKS_SKIPPED=0
|
|
104
|
-
|
|
105
|
-
# Find project root
|
|
106
|
-
find_project_root() {
|
|
107
|
-
local dir="$PWD"
|
|
108
|
-
while [[ ! -d "$dir/.claude" ]] && [[ "$dir" != "/" ]]; do
|
|
109
|
-
dir="$(dirname "$dir")"
|
|
110
|
-
done
|
|
111
|
-
if [[ -d "$dir/.claude" ]]; then
|
|
112
|
-
echo "$dir"
|
|
113
|
-
else
|
|
114
|
-
echo "$PWD" # Fall back to current directory
|
|
115
|
-
fi
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
PROJECT_ROOT="$(find_project_root)"
|
|
119
|
-
|
|
120
|
-
# Load repo configuration if available (repo-utils.sh is zsh, call via zsh subshell)
|
|
121
|
-
REPO_UTILS="$PROJECT_ROOT/.pennyfarthing/scripts/repo-utils.sh"
|
|
122
|
-
REPO_CONFIG_AVAILABLE=false
|
|
123
|
-
if [[ -f "$REPO_UTILS" ]] && command -v zsh &>/dev/null; then
|
|
124
|
-
REPO_CONFIG_AVAILABLE=true
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
# Helper to call repo-utils functions via zsh
|
|
128
|
-
repo_util() {
|
|
129
|
-
local func="$1"
|
|
130
|
-
shift
|
|
131
|
-
if $REPO_CONFIG_AVAILABLE; then
|
|
132
|
-
zsh -c "source '$REPO_UTILS' && $func $*" 2>/dev/null || echo ""
|
|
133
|
-
else
|
|
134
|
-
echo ""
|
|
135
|
-
fi
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# Handle --repo: change to repo subdirectory and load config
|
|
139
|
-
if [[ -n "$TARGET_REPO" ]]; then
|
|
140
|
-
if $REPO_CONFIG_AVAILABLE; then
|
|
141
|
-
# Check if repo exists in config
|
|
142
|
-
REPO_EXISTS=$(repo_util "repo_exists '$TARGET_REPO' && echo yes || echo no")
|
|
143
|
-
if [[ "$REPO_EXISTS" == "yes" ]]; then
|
|
144
|
-
# Use repo config for path
|
|
145
|
-
REPO_PATH_FROM_CONFIG=$(repo_util "get_repo_path '$TARGET_REPO'")
|
|
146
|
-
if [[ "$REPO_PATH_FROM_CONFIG" == "." ]]; then
|
|
147
|
-
REPO_PATH="$PROJECT_ROOT"
|
|
148
|
-
else
|
|
149
|
-
REPO_PATH="$PROJECT_ROOT/$REPO_PATH_FROM_CONFIG"
|
|
150
|
-
fi
|
|
151
|
-
# Get commands from config
|
|
152
|
-
REPO_LINT_CMD=$(repo_util "get_lint_command '$TARGET_REPO'")
|
|
153
|
-
REPO_TEST_CMD=$(repo_util "get_test_command '$TARGET_REPO'")
|
|
154
|
-
REPO_TEST_FILTER_FLAG=$(repo_util "get_test_filter_flag '$TARGET_REPO'")
|
|
155
|
-
REPO_LANGUAGE=$(repo_util "get_repo_language '$TARGET_REPO'")
|
|
156
|
-
else
|
|
157
|
-
# Repo not in config, use directory-based approach
|
|
158
|
-
REPO_PATH="$PROJECT_ROOT/$TARGET_REPO"
|
|
159
|
-
REPO_LINT_CMD=""
|
|
160
|
-
REPO_TEST_CMD=""
|
|
161
|
-
REPO_TEST_FILTER_FLAG=""
|
|
162
|
-
REPO_LANGUAGE=""
|
|
163
|
-
fi
|
|
164
|
-
else
|
|
165
|
-
# No config available, use directory-based approach
|
|
166
|
-
REPO_PATH="$PROJECT_ROOT/$TARGET_REPO"
|
|
167
|
-
REPO_LINT_CMD=""
|
|
168
|
-
REPO_TEST_CMD=""
|
|
169
|
-
REPO_TEST_FILTER_FLAG=""
|
|
170
|
-
REPO_LANGUAGE=""
|
|
171
|
-
fi
|
|
172
|
-
|
|
173
|
-
if [[ ! -d "$REPO_PATH" ]]; then
|
|
174
|
-
echo "Error: Repo directory not found: $REPO_PATH"
|
|
175
|
-
exit 1
|
|
176
|
-
fi
|
|
177
|
-
cd "$REPO_PATH"
|
|
178
|
-
WORKING_DIR="$REPO_PATH"
|
|
179
|
-
else
|
|
180
|
-
cd "$PROJECT_ROOT"
|
|
181
|
-
WORKING_DIR="$PROJECT_ROOT"
|
|
182
|
-
REPO_LINT_CMD=""
|
|
183
|
-
REPO_TEST_CMD=""
|
|
184
|
-
REPO_TEST_FILTER_FLAG=""
|
|
185
|
-
REPO_LANGUAGE=""
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
# Output helpers
|
|
189
|
-
pass() {
|
|
190
|
-
echo -e " ${GREEN}[PASS]${NC} $1"
|
|
191
|
-
((CHECKS_PASSED++))
|
|
192
|
-
((CHECKS_RUN++))
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
fail() {
|
|
196
|
-
echo -e " ${RED}[FAIL]${NC} $1"
|
|
197
|
-
((CHECKS_FAILED++))
|
|
198
|
-
((CHECKS_RUN++))
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
skip() {
|
|
202
|
-
echo -e " ${YELLOW}[SKIP]${NC} $1"
|
|
203
|
-
((CHECKS_SKIPPED++))
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
section() {
|
|
207
|
-
echo ""
|
|
208
|
-
echo -e "${CYAN}$1${NC}"
|
|
209
|
-
echo "$(printf '=%.0s' {1..40})"
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
# Detect project type
|
|
213
|
-
detect_project_type() {
|
|
214
|
-
# Check for actual source files first (not just build config)
|
|
215
|
-
# This handles monorepos with multiple languages
|
|
216
|
-
if [[ -f "package.json" ]]; then
|
|
217
|
-
echo "node"
|
|
218
|
-
elif find . -maxdepth 1 -name "*.go" -type f 2>/dev/null | grep -q .; then
|
|
219
|
-
echo "go"
|
|
220
|
-
elif [[ -f "go.mod" ]]; then
|
|
221
|
-
echo "go"
|
|
222
|
-
else
|
|
223
|
-
echo "unknown"
|
|
224
|
-
fi
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# Check if justfile has a recipe
|
|
228
|
-
has_just_recipe() {
|
|
229
|
-
local recipe="$1"
|
|
230
|
-
if [[ -f "justfile" ]] && command -v just &>/dev/null; then
|
|
231
|
-
just --list 2>/dev/null | grep -q "^$recipe " && return 0
|
|
232
|
-
fi
|
|
233
|
-
return 1
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
# Check if npm script exists
|
|
237
|
-
has_npm_script() {
|
|
238
|
-
local script="$1"
|
|
239
|
-
if [[ -f "package.json" ]]; then
|
|
240
|
-
grep -q "\"$script\":" package.json && return 0
|
|
241
|
-
fi
|
|
242
|
-
return 1
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
# Run a check command and report result
|
|
246
|
-
run_check() {
|
|
247
|
-
local name="$1"
|
|
248
|
-
shift
|
|
249
|
-
local cmd="$*"
|
|
250
|
-
|
|
251
|
-
if eval "$cmd" >/dev/null 2>&1; then
|
|
252
|
-
pass "$name"
|
|
253
|
-
return 0
|
|
254
|
-
else
|
|
255
|
-
fail "$name"
|
|
256
|
-
return 1
|
|
257
|
-
fi
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
echo ""
|
|
261
|
-
echo "Quality Gate Check"
|
|
262
|
-
echo "=================="
|
|
263
|
-
echo "Project: $PROJECT_ROOT"
|
|
264
|
-
if [[ -n "$TARGET_REPO" ]]; then
|
|
265
|
-
echo "Repo: $TARGET_REPO"
|
|
266
|
-
fi
|
|
267
|
-
echo "Working dir: $WORKING_DIR"
|
|
268
|
-
if $FAST_MODE; then
|
|
269
|
-
echo -e "${YELLOW}Mode: FAST (skipping slow packages)${NC}"
|
|
270
|
-
fi
|
|
271
|
-
|
|
272
|
-
PROJECT_TYPE=$(detect_project_type)
|
|
273
|
-
|
|
274
|
-
# =============================================================================
|
|
275
|
-
# Lint Check
|
|
276
|
-
# =============================================================================
|
|
277
|
-
section "Lint"
|
|
278
|
-
|
|
279
|
-
LINT_RAN=false
|
|
280
|
-
|
|
281
|
-
if $NO_LINT; then
|
|
282
|
-
skip "Lint - skipped by --no-lint or --tests-only"
|
|
283
|
-
LINT_RAN=true # Mark as "ran" to avoid double skip message
|
|
284
|
-
# Use repo config lint command if available
|
|
285
|
-
elif [[ -n "$REPO_LINT_CMD" ]]; then
|
|
286
|
-
LINT_RAN=true
|
|
287
|
-
if eval "$REPO_LINT_CMD" >/dev/null 2>&1; then
|
|
288
|
-
pass "Lint ($REPO_LINT_CMD)"
|
|
289
|
-
else
|
|
290
|
-
fail "Lint ($REPO_LINT_CMD)"
|
|
291
|
-
fi
|
|
292
|
-
# Prefer justfile recipes
|
|
293
|
-
elif has_just_recipe "lint"; then
|
|
294
|
-
LINT_RAN=true
|
|
295
|
-
if just lint >/dev/null 2>&1; then
|
|
296
|
-
pass "Lint (just lint)"
|
|
297
|
-
else
|
|
298
|
-
fail "Lint (just lint)"
|
|
299
|
-
fi
|
|
300
|
-
elif [[ "$PROJECT_TYPE" == "node" ]] && has_npm_script "lint"; then
|
|
301
|
-
LINT_RAN=true
|
|
302
|
-
if npm run lint >/dev/null 2>&1; then
|
|
303
|
-
pass "Lint (npm run lint)"
|
|
304
|
-
else
|
|
305
|
-
fail "Lint (npm run lint)"
|
|
306
|
-
fi
|
|
307
|
-
elif [[ "$PROJECT_TYPE" == "go" ]] && command -v golangci-lint &>/dev/null; then
|
|
308
|
-
LINT_RAN=true
|
|
309
|
-
if golangci-lint run >/dev/null 2>&1; then
|
|
310
|
-
pass "Lint (golangci-lint)"
|
|
311
|
-
else
|
|
312
|
-
fail "Lint (golangci-lint)"
|
|
313
|
-
fi
|
|
314
|
-
elif [[ -f "node_modules/.bin/eslint" ]]; then
|
|
315
|
-
LINT_RAN=true
|
|
316
|
-
if ./node_modules/.bin/eslint . >/dev/null 2>&1; then
|
|
317
|
-
pass "Lint (eslint)"
|
|
318
|
-
else
|
|
319
|
-
fail "Lint (eslint)"
|
|
320
|
-
fi
|
|
321
|
-
fi
|
|
322
|
-
|
|
323
|
-
if ! $LINT_RAN; then
|
|
324
|
-
skip "Lint - no lint command configured"
|
|
325
|
-
fi
|
|
326
|
-
|
|
327
|
-
# =============================================================================
|
|
328
|
-
# Type Check
|
|
329
|
-
# =============================================================================
|
|
330
|
-
section "Type Check"
|
|
331
|
-
|
|
332
|
-
TYPECHECK_RAN=false
|
|
333
|
-
|
|
334
|
-
if $NO_TYPECHECK; then
|
|
335
|
-
skip "Type Check - skipped by --no-typecheck or --tests-only"
|
|
336
|
-
TYPECHECK_RAN=true # Mark as "ran" to avoid double skip message
|
|
337
|
-
# Prefer justfile recipes
|
|
338
|
-
elif has_just_recipe "typecheck"; then
|
|
339
|
-
TYPECHECK_RAN=true
|
|
340
|
-
if just typecheck >/dev/null 2>&1; then
|
|
341
|
-
pass "Type Check (just typecheck)"
|
|
342
|
-
else
|
|
343
|
-
fail "Type Check (just typecheck)"
|
|
344
|
-
fi
|
|
345
|
-
elif [[ -f "tsconfig.json" ]] && has_npm_script "typecheck"; then
|
|
346
|
-
TYPECHECK_RAN=true
|
|
347
|
-
if npm run typecheck >/dev/null 2>&1; then
|
|
348
|
-
pass "Type Check (npm run typecheck)"
|
|
349
|
-
else
|
|
350
|
-
fail "Type Check (npm run typecheck)"
|
|
351
|
-
fi
|
|
352
|
-
elif [[ -f "tsconfig.json" ]] && command -v tsc &>/dev/null; then
|
|
353
|
-
TYPECHECK_RAN=true
|
|
354
|
-
if tsc --noEmit >/dev/null 2>&1; then
|
|
355
|
-
pass "Type Check (tsc --noEmit)"
|
|
356
|
-
else
|
|
357
|
-
fail "Type Check (tsc --noEmit)"
|
|
358
|
-
fi
|
|
359
|
-
elif [[ -f "tsconfig.json" ]] && [[ -f "node_modules/.bin/tsc" ]]; then
|
|
360
|
-
TYPECHECK_RAN=true
|
|
361
|
-
if ./node_modules/.bin/tsc --noEmit >/dev/null 2>&1; then
|
|
362
|
-
pass "Type Check (./node_modules/.bin/tsc --noEmit)"
|
|
363
|
-
else
|
|
364
|
-
fail "Type Check (./node_modules/.bin/tsc --noEmit)"
|
|
365
|
-
fi
|
|
366
|
-
fi
|
|
367
|
-
|
|
368
|
-
if ! $TYPECHECK_RAN; then
|
|
369
|
-
if [[ -f "tsconfig.json" ]]; then
|
|
370
|
-
skip "Type Check - TypeScript found but no typecheck command"
|
|
371
|
-
else
|
|
372
|
-
skip "Type Check - not a TypeScript project"
|
|
373
|
-
fi
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
# =============================================================================
|
|
377
|
-
# Tests
|
|
378
|
-
# =============================================================================
|
|
379
|
-
section "Tests"
|
|
380
|
-
|
|
381
|
-
TESTS_RAN=false
|
|
382
|
-
|
|
383
|
-
# Show filter if provided
|
|
384
|
-
if [[ -n "$TEST_FILTER" ]]; then
|
|
385
|
-
echo " Filter: $TEST_FILTER"
|
|
386
|
-
fi
|
|
387
|
-
|
|
388
|
-
# Use repo config test command if available
|
|
389
|
-
if [[ -n "$REPO_TEST_CMD" ]]; then
|
|
390
|
-
TESTS_RAN=true
|
|
391
|
-
TEST_CMD="$REPO_TEST_CMD"
|
|
392
|
-
if [[ -n "$TEST_FILTER" ]] && [[ -n "$REPO_TEST_FILTER_FLAG" ]]; then
|
|
393
|
-
TEST_CMD="$REPO_TEST_CMD $REPO_TEST_FILTER_FLAG \"$TEST_FILTER\""
|
|
394
|
-
fi
|
|
395
|
-
if eval "$TEST_CMD" >/dev/null 2>&1; then
|
|
396
|
-
pass "Tests ($REPO_TEST_CMD${TEST_FILTER:+ $REPO_TEST_FILTER_FLAG $TEST_FILTER})"
|
|
397
|
-
else
|
|
398
|
-
fail "Tests ($REPO_TEST_CMD${TEST_FILTER:+ $REPO_TEST_FILTER_FLAG $TEST_FILTER})"
|
|
399
|
-
fi
|
|
400
|
-
# Prefer justfile recipes
|
|
401
|
-
elif has_just_recipe "test"; then
|
|
402
|
-
TESTS_RAN=true
|
|
403
|
-
TEST_CMD="just test"
|
|
404
|
-
if [[ -n "$TEST_FILTER" ]]; then
|
|
405
|
-
# Pass filter to just test (assumes it accepts a pattern arg)
|
|
406
|
-
TEST_CMD="just test $TEST_FILTER"
|
|
407
|
-
fi
|
|
408
|
-
if eval "$TEST_CMD" >/dev/null 2>&1; then
|
|
409
|
-
pass "Tests (just test${TEST_FILTER:+ $TEST_FILTER})"
|
|
410
|
-
else
|
|
411
|
-
fail "Tests (just test${TEST_FILTER:+ $TEST_FILTER})"
|
|
412
|
-
fi
|
|
413
|
-
elif [[ "$PROJECT_TYPE" == "node" ]] && has_npm_script "test"; then
|
|
414
|
-
TESTS_RAN=true
|
|
415
|
-
# Check for pnpm workspace (monorepo)
|
|
416
|
-
if [[ -f "pnpm-workspace.yaml" ]] && command -v pnpm &>/dev/null; then
|
|
417
|
-
if $FAST_MODE; then
|
|
418
|
-
# Skip slow packages (cyclist has Electron dependencies)
|
|
419
|
-
TEST_CMD="pnpm -r --filter '!@pennyfarthing/cyclist' test"
|
|
420
|
-
TEST_LABEL="pnpm test (fast mode - skipping cyclist)"
|
|
421
|
-
else
|
|
422
|
-
TEST_CMD="pnpm -r test"
|
|
423
|
-
TEST_LABEL="pnpm test"
|
|
424
|
-
fi
|
|
425
|
-
else
|
|
426
|
-
TEST_CMD="npm test"
|
|
427
|
-
TEST_LABEL="npm test"
|
|
428
|
-
fi
|
|
429
|
-
if [[ -n "$TEST_FILTER" ]]; then
|
|
430
|
-
# Pass filter to test runner (vitest/jest use -t for pattern)
|
|
431
|
-
TEST_CMD="$TEST_CMD -- -t \"$TEST_FILTER\""
|
|
432
|
-
TEST_LABEL="$TEST_LABEL -t $TEST_FILTER"
|
|
433
|
-
fi
|
|
434
|
-
if eval "$TEST_CMD" >/dev/null 2>&1; then
|
|
435
|
-
pass "Tests ($TEST_LABEL)"
|
|
436
|
-
else
|
|
437
|
-
fail "Tests ($TEST_LABEL)"
|
|
438
|
-
fi
|
|
439
|
-
elif [[ "$PROJECT_TYPE" == "go" ]]; then
|
|
440
|
-
TESTS_RAN=true
|
|
441
|
-
TEST_CMD="go test ./..."
|
|
442
|
-
if [[ -n "$TEST_FILTER" ]]; then
|
|
443
|
-
# Go uses -run for pattern matching
|
|
444
|
-
TEST_CMD="go test -run \"$TEST_FILTER\" ./..."
|
|
445
|
-
fi
|
|
446
|
-
if eval "$TEST_CMD" >/dev/null 2>&1; then
|
|
447
|
-
pass "Tests (go test${TEST_FILTER:+ -run $TEST_FILTER} ./...)"
|
|
448
|
-
else
|
|
449
|
-
fail "Tests (go test${TEST_FILTER:+ -run $TEST_FILTER} ./...)"
|
|
450
|
-
fi
|
|
451
|
-
elif [[ -f "node_modules/.bin/jest" ]]; then
|
|
452
|
-
TESTS_RAN=true
|
|
453
|
-
TEST_CMD="./node_modules/.bin/jest"
|
|
454
|
-
if [[ -n "$TEST_FILTER" ]]; then
|
|
455
|
-
TEST_CMD="./node_modules/.bin/jest -t \"$TEST_FILTER\""
|
|
456
|
-
fi
|
|
457
|
-
if eval "$TEST_CMD" >/dev/null 2>&1; then
|
|
458
|
-
pass "Tests (jest${TEST_FILTER:+ -t $TEST_FILTER})"
|
|
459
|
-
else
|
|
460
|
-
fail "Tests (jest${TEST_FILTER:+ -t $TEST_FILTER})"
|
|
461
|
-
fi
|
|
462
|
-
fi
|
|
463
|
-
|
|
464
|
-
if ! $TESTS_RAN; then
|
|
465
|
-
skip "Tests - no test command configured"
|
|
466
|
-
fi
|
|
467
|
-
|
|
468
|
-
# =============================================================================
|
|
469
|
-
# Summary
|
|
470
|
-
# =============================================================================
|
|
471
|
-
section "Summary"
|
|
472
|
-
|
|
473
|
-
echo ""
|
|
474
|
-
echo "Checks run: $CHECKS_RUN"
|
|
475
|
-
echo -e "Checks passed: ${GREEN}$CHECKS_PASSED${NC}"
|
|
476
|
-
if [[ $CHECKS_FAILED -gt 0 ]]; then
|
|
477
|
-
echo -e "Checks failed: ${RED}$CHECKS_FAILED${NC}"
|
|
478
|
-
else
|
|
479
|
-
echo "Checks failed: $CHECKS_FAILED"
|
|
480
|
-
fi
|
|
481
|
-
if [[ $CHECKS_SKIPPED -gt 0 ]]; then
|
|
482
|
-
echo -e "Checks skipped: ${YELLOW}$CHECKS_SKIPPED${NC}"
|
|
483
|
-
fi
|
|
484
|
-
echo ""
|
|
485
|
-
|
|
486
|
-
if [[ $CHECKS_FAILED -gt 0 ]]; then
|
|
487
|
-
echo -e "${RED}FAILED${NC} - $CHECKS_FAILED check(s) failed"
|
|
488
|
-
echo "Fix issues before handoff to Reviewer."
|
|
489
|
-
exit 1
|
|
490
|
-
elif [[ $CHECKS_RUN -eq 0 ]]; then
|
|
491
|
-
echo -e "${YELLOW}WARNING${NC} - No checks ran (project type: $PROJECT_TYPE)"
|
|
492
|
-
echo "Consider adding lint/test scripts to package.json or justfile."
|
|
493
|
-
exit 0
|
|
494
|
-
else
|
|
495
|
-
echo -e "${GREEN}PASSED${NC} - All checks passed"
|
|
496
|
-
exit 0
|
|
497
|
-
fi
|
|
24
|
+
exec python3 "$SCRIPT_DIR/check.py" "$@"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
get-workflow-type.py - Determine if a workflow is phased or stepped
|
|
4
|
+
|
|
5
|
+
Usage: python get-workflow-type.py <workflow-name>
|
|
6
|
+
|
|
7
|
+
Returns: "phased" or "stepped" (stdout)
|
|
8
|
+
Exit 1 if workflow not found
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import yaml
|
|
16
|
+
except ImportError:
|
|
17
|
+
print("Error: PyYAML required. Install with: pip install pyyaml", file=sys.stderr)
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_project_root() -> Path:
|
|
22
|
+
"""Find project root by looking for .claude directory."""
|
|
23
|
+
current = Path.cwd()
|
|
24
|
+
while current != current.parent:
|
|
25
|
+
if (current / ".claude").is_dir():
|
|
26
|
+
return current
|
|
27
|
+
current = current.parent
|
|
28
|
+
return Path.cwd()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_workflow_type(workflow_name: str) -> str:
|
|
32
|
+
"""Get workflow type from YAML definition."""
|
|
33
|
+
project_root = find_project_root()
|
|
34
|
+
workflows_dir = project_root / "pennyfarthing-dist" / "workflows"
|
|
35
|
+
workflow_file = workflows_dir / f"{workflow_name}.yaml"
|
|
36
|
+
|
|
37
|
+
if not workflow_file.exists():
|
|
38
|
+
print(f"Error: Workflow '{workflow_name}' not found", file=sys.stderr)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
with open(workflow_file) as f:
|
|
42
|
+
data = yaml.safe_load(f)
|
|
43
|
+
|
|
44
|
+
# Extract type, default to "phased" if not specified
|
|
45
|
+
workflow_type = data.get("workflow", {}).get("type", "phased")
|
|
46
|
+
return workflow_type
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main() -> int:
|
|
50
|
+
if len(sys.argv) != 2:
|
|
51
|
+
print("Usage: get-workflow-type.py <workflow-name>", file=sys.stderr)
|
|
52
|
+
return 1
|
|
53
|
+
|
|
54
|
+
workflow_name = sys.argv[1]
|
|
55
|
+
workflow_type = get_workflow_type(workflow_name)
|
|
56
|
+
print(workflow_type)
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
sys.exit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# get-workflow-type.sh - Determine if a workflow is phased or stepped
|
|
3
|
+
#
|
|
4
|
+
# Usage: .pennyfarthing/scripts/core/run.sh workflow/get-workflow-type.sh <workflow-name>
|
|
5
|
+
#
|
|
6
|
+
# Returns: "phased" or "stepped"
|
|
7
|
+
# Exit 1 if workflow not found
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
|
|
13
|
+
exec python3 "$SCRIPT_DIR/get-workflow-type.py" "$@"
|
|
@@ -21,6 +21,8 @@ Canonical evaluation of agent responses. All judging goes through this skill.
|
|
|
21
21
|
- `phase-dev` - Relay Dev phase rubric
|
|
22
22
|
- `phase-reviewer` - Relay Reviewer phase rubric
|
|
23
23
|
- `coherence` - Relay chain coherence rating
|
|
24
|
+
- `swebench` - Deterministic SWE-bench evaluation (Python script)
|
|
25
|
+
- `ground-truth` - Ground-truth patch comparison (Python script)
|
|
24
26
|
|
|
25
27
|
## Unified Rubric (solo/compare)
|
|
26
28
|
|
|
@@ -106,6 +108,8 @@ Extract:
|
|
|
106
108
|
| compare | `contestants[]` (each with spec, character, response), `challenge` | `baseline_issues`, `baseline_criteria` |
|
|
107
109
|
| phase-* | `team1`, `team2` (each with theme, response), `context` | |
|
|
108
110
|
| coherence | `theme`, `sm_response`, `tea_response`, `dev_response`, `reviewer_response` | |
|
|
111
|
+
| swebench | `scenario`, `response_file` | |
|
|
112
|
+
| ground-truth | `scenario`, `response_file` | |
|
|
109
113
|
|
|
110
114
|
**Note:** When checklist data is provided, solo mode uses checklist-based evaluation:
|
|
111
115
|
- `baseline_issues` → code-review, tea, dev scenarios (things to FIND)
|
|
@@ -470,6 +474,59 @@ Output ONLY valid JSON (no markdown, no extra text):
|
|
|
470
474
|
|
|
471
475
|
</details>
|
|
472
476
|
|
|
477
|
+
<details>
|
|
478
|
+
<summary><strong>SWE-bench Mode (Deterministic Python Evaluation)</strong></summary>
|
|
479
|
+
|
|
480
|
+
**For `swebench` and `ground-truth` modes, use Python scripts instead of LLM-as-judge.**
|
|
481
|
+
|
|
482
|
+
These modes use deterministic scoring based on ground-truth patches from the SWE-bench dataset.
|
|
483
|
+
|
|
484
|
+
**Prerequisites:**
|
|
485
|
+
```bash
|
|
486
|
+
# Ensure SWE-bench data is downloaded (one-time)
|
|
487
|
+
.pennyfarthing/scripts/run.sh test/ensure-swebench-data.sh
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**swebench mode:**
|
|
491
|
+
Uses structured rubric + ground truth validation. Scores:
|
|
492
|
+
- root_cause (30%): Bug location + explanation
|
|
493
|
+
- fix_quality (40%): Addresses issue + minimal + syntax correct
|
|
494
|
+
- completeness (20%): Edge cases + test coverage
|
|
495
|
+
- persona (10%): In-character delivery
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# Execute via Python script
|
|
499
|
+
python3 .pennyfarthing/scripts/test/swebench-judge.py <scenario_name> <response_file>
|
|
500
|
+
|
|
501
|
+
# Example
|
|
502
|
+
python3 .pennyfarthing/scripts/test/swebench-judge.py flask-5014 /tmp/run_1.json
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**ground-truth mode:**
|
|
506
|
+
Compares fix against actual SWE-bench patch. Scores:
|
|
507
|
+
- file_identification (20%): Correct files identified
|
|
508
|
+
- location_identification (20%): Correct functions/locations
|
|
509
|
+
- fix_logic_match (40%): Code matches ground truth
|
|
510
|
+
- completeness (20%): Has all elements of good fix
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
# Execute via Python script
|
|
514
|
+
python3 .pennyfarthing/scripts/test/ground-truth-judge.py <scenario_name> <response_file>
|
|
515
|
+
|
|
516
|
+
# Example
|
|
517
|
+
python3 .pennyfarthing/scripts/test/ground-truth-judge.py django-10554 /tmp/run_1.json
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Response file format:**
|
|
521
|
+
Both scripts expect JSON with either:
|
|
522
|
+
- `result`: The agent's response text
|
|
523
|
+
- `response_text`: Alternative field name
|
|
524
|
+
|
|
525
|
+
**Output:**
|
|
526
|
+
Scripts print scores to stdout and save detailed JSON to `{input_path.replace('run_', 'swebench_judge_')}` or `{input_path.replace('run_', 'gt_judge_')}`.
|
|
527
|
+
|
|
528
|
+
</details>
|
|
529
|
+
|
|
473
530
|
<details>
|
|
474
531
|
<summary><strong>Step 3: Execute Judge via CLI</strong></summary>
|
|
475
532
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Example: .pennyfarthing/scripts/core/run.sh sync-epic-jira.sh MSSCI-11952 --all
|
|
7
7
|
#
|
|
8
8
|
# This script syncs status and story points from sprint YAML to Jira.
|
|
9
|
-
#
|
|
9
|
+
# Thin wrapper that delegates to Python CLI.
|
|
10
10
|
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
|
|
@@ -63,25 +63,7 @@ if [[ -z "$EPIC_EXISTS" ]]; then
|
|
|
63
63
|
exit 1
|
|
64
64
|
fi
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
# jira-sync.mjs expects epic number (e.g., 35) not full ID
|
|
68
|
-
# But it also accepts Jira keys directly
|
|
69
|
-
# Let's check if this is a Jira key (MSSCI-XXXXX) or local ID
|
|
70
|
-
|
|
71
|
-
# Pass through to jira-sync.mjs with remaining arguments
|
|
72
|
-
JIRA_SYNC_SCRIPT="$PROJECT_ROOT/.pennyfarthing/scripts/utils/jira/jira-sync.mjs"
|
|
73
|
-
|
|
74
|
-
if [[ ! -f "$JIRA_SYNC_SCRIPT" ]]; then
|
|
75
|
-
# Try alternate location
|
|
76
|
-
JIRA_SYNC_SCRIPT="$PROJECT_ROOT/pennyfarthing-dist/scripts/utils/jira/jira-sync.mjs"
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
if [[ ! -f "$JIRA_SYNC_SCRIPT" ]]; then
|
|
80
|
-
echo "Error: jira-sync.mjs not found"
|
|
81
|
-
exit 1
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
# Build arguments for jira-sync.mjs
|
|
66
|
+
# Build arguments for Python CLI
|
|
85
67
|
shift # Remove epic-id from arguments
|
|
86
68
|
SYNC_ARGS=("$EPIC_ID")
|
|
87
69
|
|
|
@@ -100,5 +82,5 @@ done
|
|
|
100
82
|
echo "Syncing epic $EPIC_ID to Jira..."
|
|
101
83
|
echo ""
|
|
102
84
|
|
|
103
|
-
#
|
|
104
|
-
exec
|
|
85
|
+
# Delegate to Python CLI
|
|
86
|
+
exec python3 -m pennyfarthing_scripts.jira sync "${SYNC_ARGS[@]}"
|