@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.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts +3 -0
  4. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.js +134 -9
  6. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  7. package/pennyfarthing-dist/agents/sm-setup.md +37 -2
  8. package/pennyfarthing-dist/agents/sm.md +68 -22
  9. package/pennyfarthing-dist/agents/workflow-status-check.md +11 -1
  10. package/pennyfarthing-dist/commands/git-cleanup.md +43 -308
  11. package/pennyfarthing-dist/commands/solo.md +31 -0
  12. package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
  13. package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +83 -83
  14. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +11 -11
  15. package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -2
  16. package/pennyfarthing-dist/scripts/core/check-context.sh +3 -0
  17. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
  18. package/pennyfarthing-dist/scripts/core/prime.sh +3 -157
  19. package/pennyfarthing-dist/scripts/core/run.sh +9 -0
  20. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  21. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +117 -20
  22. package/pennyfarthing-dist/scripts/jira/README.md +10 -7
  23. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +13 -0
  24. package/pennyfarthing-dist/scripts/misc/add_short_names.py +226 -0
  25. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +6 -5
  26. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +319 -0
  27. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +6 -5
  28. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +270 -0
  29. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +59 -0
  30. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +8 -6
  31. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +402 -0
  32. package/pennyfarthing-dist/scripts/workflow/check.sh +3 -476
  33. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +61 -0
  34. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +13 -0
  35. package/pennyfarthing-dist/skills/judge/SKILL.md +57 -0
  36. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +4 -22
  37. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +83 -0
  38. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +116 -0
  39. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +210 -0
  40. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +88 -0
  41. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +71 -0
  42. package/pennyfarthing-dist/workflows/git-cleanup.yaml +59 -0
  43. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +0 -393
  44. package/pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs +0 -545
  45. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +0 -327
  46. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +0 -503
  47. package/pennyfarthing-dist/scripts/jira/jira-lib.mjs +0 -443
  48. package/pennyfarthing-dist/scripts/jira/jira-sync-story.mjs +0 -208
  49. package/pennyfarthing-dist/scripts/jira/jira-sync.mjs +0 -198
  50. package/pennyfarthing-dist/scripts/misc/add-short-names.mjs +0 -264
  51. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +0 -474
  52. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.mjs +0 -377
  53. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.js +0 -492
  54. /package/pennyfarthing-dist/guides/{AGENT-COORDINATION.md → agent-coordination.md} +0 -0
  55. /package/pennyfarthing-dist/guides/{HOOKS.md → hooks.md} +0 -0
  56. /package/pennyfarthing-dist/guides/{PROMPT-PATTERNS.md → prompt-patterns.md} +0 -0
  57. /package/pennyfarthing-dist/guides/{SESSION-ARTIFACTS.md → session-artifacts.md} +0 -0
  58. /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 -uo pipefail
20
+ set -euo pipefail
21
21
 
22
- # Parse arguments
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
- while [[ $# -gt 0 ]]; do
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
- # It wraps the existing jira-sync.mjs for better integration with /sprint skill.
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
- # Extract epic number for jira-sync.mjs compatibility
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
- # Run jira-sync.mjs
104
- exec node "$JIRA_SYNC_SCRIPT" "${SYNC_ARGS[@]}"
85
+ # Delegate to Python CLI
86
+ exec python3 -m pennyfarthing_scripts.jira sync "${SYNC_ARGS[@]}"