@pennyfarthing/core 7.6.1 → 7.7.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 +109 -201
- package/package.json +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +91 -0
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.js +31 -0
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/update.js +31 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/pennyfarthing-dist/agents/architect.md +48 -53
- package/pennyfarthing-dist/agents/dev.md +74 -164
- package/pennyfarthing-dist/agents/devops.md +44 -39
- package/pennyfarthing-dist/agents/handoff.md +46 -23
- package/pennyfarthing-dist/agents/orchestrator.md +84 -255
- package/pennyfarthing-dist/agents/pm.md +40 -50
- package/pennyfarthing-dist/agents/reviewer-preflight.md +58 -26
- package/pennyfarthing-dist/agents/reviewer.md +107 -298
- package/pennyfarthing-dist/agents/sm-file-summary.md +51 -30
- package/pennyfarthing-dist/agents/sm-finish.md +59 -38
- package/pennyfarthing-dist/agents/sm-handoff.md +40 -33
- package/pennyfarthing-dist/agents/sm-setup.md +89 -47
- package/pennyfarthing-dist/agents/sm.md +171 -558
- package/pennyfarthing-dist/agents/tea.md +77 -146
- package/pennyfarthing-dist/agents/tech-writer.md +43 -24
- package/pennyfarthing-dist/agents/testing-runner.md +73 -30
- package/pennyfarthing-dist/agents/ux-designer.md +39 -25
- package/pennyfarthing-dist/agents/workflow-status-check.md +34 -16
- package/pennyfarthing-dist/commands/benchmark.md +19 -1
- package/pennyfarthing-dist/commands/continue-session.md +1 -1
- package/pennyfarthing-dist/commands/solo.md +5 -0
- package/pennyfarthing-dist/commands/theme-maker.md +5 -5
- package/pennyfarthing-dist/commands/work.md +1 -1
- package/pennyfarthing-dist/guides/XML-TAGS.md +179 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +22 -9
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +432 -0
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +27 -7
- package/pennyfarthing-dist/guides/scale-levels.md +114 -0
- package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/star-trek-tos.yaml +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +13 -7
- package/pennyfarthing-dist/scripts/core/check-context.sh +6 -1
- package/pennyfarthing-dist/scripts/core/prime.sh +57 -32
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +45 -4
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +32 -7
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +30 -11
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +80 -23
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +66 -53
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +4 -4
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +402 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +7 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +94 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +10 -152
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +14 -4
- package/pennyfarthing-dist/scripts/jira/jira-sync.sh +12 -4
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +11 -99
- package/pennyfarthing-dist/scripts/lib/common.sh +55 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +97 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +27 -22
- package/pennyfarthing-dist/scripts/story/create-story.sh +14 -154
- package/pennyfarthing-dist/scripts/story/size-story.sh +12 -192
- package/pennyfarthing-dist/scripts/story/story-template.sh +12 -156
- package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +24 -93
- package/pennyfarthing-dist/scripts/test/swebench-judge.py +33 -59
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +575 -0
- package/pennyfarthing-dist/scripts/workflow/check.py +502 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +52 -16
- package/pennyfarthing-dist/skills/sprint/skill.md +1 -1
- package/pennyfarthing-dist/templates/settings.local.json.template +11 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Welcome Hook: Display a friendly welcome message on session start
|
|
3
|
+
#
|
|
4
|
+
# For CLI: Displays ASCII art of a penny-farthing bicycle
|
|
5
|
+
# For Cyclist: Sends WebSocket message to display logo and welcome
|
|
6
|
+
#
|
|
7
|
+
# Called by Claude Code SessionStart hook
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# Read and discard stdin (required by hook protocol)
|
|
12
|
+
cat > /dev/null
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"
|
|
16
|
+
|
|
17
|
+
# Once-per-session guard: only show welcome on first invocation
|
|
18
|
+
# Use session ID from environment or generate a unique one
|
|
19
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-$$}"
|
|
20
|
+
WELCOME_LOCK="$PROJECT_ROOT/.session/.welcome-shown-$SESSION_ID"
|
|
21
|
+
|
|
22
|
+
# Ensure .session directory exists
|
|
23
|
+
mkdir -p "$PROJECT_ROOT/.session"
|
|
24
|
+
|
|
25
|
+
# Check if welcome was already shown for this session
|
|
26
|
+
if [[ -f "$WELCOME_LOCK" ]]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Mark welcome as shown for this session
|
|
31
|
+
touch "$WELCOME_LOCK"
|
|
32
|
+
|
|
33
|
+
# Check if running in Cyclist (port file exists)
|
|
34
|
+
PORT_FILE="$PROJECT_ROOT/.cyclist-port"
|
|
35
|
+
IN_CYCLIST=false
|
|
36
|
+
if [[ -f "$PORT_FILE" ]]; then
|
|
37
|
+
CYCLIST_PORT=$(cat "$PORT_FILE" 2>/dev/null)
|
|
38
|
+
if [[ "$CYCLIST_PORT" =~ ^[0-9]+$ ]]; then
|
|
39
|
+
IN_CYCLIST=true
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Get project name from package.json or directory name
|
|
44
|
+
PROJECT_NAME=""
|
|
45
|
+
if [[ -f "$PROJECT_ROOT/package.json" ]]; then
|
|
46
|
+
PROJECT_NAME=$(jq -r '.name // empty' "$PROJECT_ROOT/package.json" 2>/dev/null || echo "")
|
|
47
|
+
fi
|
|
48
|
+
if [[ -z "$PROJECT_NAME" ]]; then
|
|
49
|
+
PROJECT_NAME=$(basename "$PROJECT_ROOT")
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Get current theme from config
|
|
53
|
+
THEME=""
|
|
54
|
+
if [[ -f "$PROJECT_ROOT/.pennyfarthing/config.local.yaml" ]]; then
|
|
55
|
+
THEME=$(grep -E '^theme:' "$PROJECT_ROOT/.pennyfarthing/config.local.yaml" 2>/dev/null | sed 's/theme:[[:space:]]*//' | tr -d '"' || echo "")
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [[ "$IN_CYCLIST" == "true" ]]; then
|
|
59
|
+
# Send welcome message via WebSocket API for Cyclist to display
|
|
60
|
+
# Cyclist will show the pennyfarthing logo image and welcome text
|
|
61
|
+
curl -s -X POST "http://localhost:$CYCLIST_PORT/api/welcome" \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
-d "{\"project\": \"$PROJECT_NAME\", \"theme\": \"$THEME\"}" \
|
|
64
|
+
>/dev/null 2>&1 || true
|
|
65
|
+
else
|
|
66
|
+
# CLI mode: Display ASCII art welcome
|
|
67
|
+
cat << 'EOF'
|
|
68
|
+
|
|
69
|
+
___
|
|
70
|
+
/ \
|
|
71
|
+
| | Welcome to
|
|
72
|
+
| | ╔═══════════════════════════════════╗
|
|
73
|
+
\___/ ║ ╔═╗╔═╗╔╗╔╔╗╔╦═╗╔═╗╔═╗╔═╗╦╔═╗ ║
|
|
74
|
+
║ ║ ╠═╝║╣ ║║║║║║ ╠╣ ╠═╣╠╦╝ ║ ╠═╣ ║
|
|
75
|
+
║ ║ ╩ ╚═╝╝╚╝╝╚╝╩ ╩ ╩╩╚═ ╩ ╩ ╩ ║
|
|
76
|
+
╔═╩═╗ ╚═══════════════════════════════════╝
|
|
77
|
+
/ \
|
|
78
|
+
│ O │ Agent-powered development with style
|
|
79
|
+
\ /
|
|
80
|
+
╚═══╝
|
|
81
|
+
|
|
82
|
+
EOF
|
|
83
|
+
|
|
84
|
+
# Add project-specific info
|
|
85
|
+
if [[ -n "$PROJECT_NAME" ]]; then
|
|
86
|
+
echo " Project: $PROJECT_NAME"
|
|
87
|
+
fi
|
|
88
|
+
if [[ -n "$THEME" ]]; then
|
|
89
|
+
echo " Theme: $THEME"
|
|
90
|
+
fi
|
|
91
|
+
echo ""
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
exit 0
|
|
@@ -1,164 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env zsh
|
|
2
|
-
# Check and claim a story in Jira
|
|
3
|
-
# Usage:
|
|
2
|
+
# Check and claim a story in Jira
|
|
3
|
+
# Usage: jira-claim-story.sh <issue-key> [--claim]
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Parameters:
|
|
8
|
-
# <story_key_or_jira_key> Either:
|
|
9
|
-
# - Story key format: 35-7-feedback-rule-delete-404
|
|
10
|
-
# - Jira issue key format: MSSCI-10991
|
|
11
|
-
#
|
|
12
|
-
# Actions:
|
|
13
|
-
# (default) Check if story is assigned and show status
|
|
14
|
-
# --claim Assign story to current user and move to "In Progress"
|
|
5
|
+
# Thin wrapper that delegates to Python CLI:
|
|
6
|
+
# python -m pennyfarthing_scripts.jira claim <issue-key> [--claim]
|
|
15
7
|
#
|
|
16
8
|
# Exit codes:
|
|
17
|
-
# 0 - Story is available
|
|
9
|
+
# 0 - Story is available or successfully claimed
|
|
18
10
|
# 1 - Story is assigned to someone else
|
|
19
|
-
# 2 - Story not found or not synced
|
|
20
|
-
# 3 - Error (
|
|
11
|
+
# 2 - Story not found or not synced
|
|
12
|
+
# 3 - Error (CLI not installed, Python not found, etc.)
|
|
21
13
|
|
|
22
14
|
set -e
|
|
23
15
|
|
|
24
|
-
# Source common functions
|
|
16
|
+
# Source common functions for Python discovery
|
|
25
17
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
26
18
|
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
27
19
|
source "${PARENT_DIR}/lib/common.sh"
|
|
28
|
-
source "${PARENT_DIR}/sprint/sprint-common.sh"
|
|
29
|
-
source "${SCRIPT_DIR}/jira-lib.sh"
|
|
30
|
-
|
|
31
|
-
# Check dependencies (using jira-lib's robust check)
|
|
32
|
-
check_jira_cli
|
|
33
|
-
|
|
34
|
-
# Check arguments
|
|
35
|
-
if [ -z "$1" ]; then
|
|
36
|
-
error "Error: Story key or Jira issue key required"
|
|
37
|
-
echo "Usage: $0 <story_key_or_jira_key> [--claim]"
|
|
38
|
-
echo ""
|
|
39
|
-
echo "Examples:"
|
|
40
|
-
echo " $0 35-7-feedback-rule-delete-404 # Using story key"
|
|
41
|
-
echo " $0 35-7-feedback-rule-delete-404 --claim # Claim using story key"
|
|
42
|
-
echo " $0 MSSCI-10991 # Using Jira issue key"
|
|
43
|
-
echo " $0 MSSCI-10991 --claim # Claim using Jira key"
|
|
44
|
-
exit 3
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
INPUT_KEY="$1"
|
|
48
|
-
CLAIM_MODE=false
|
|
49
|
-
[ "$2" = "--claim" ] && CLAIM_MODE=true
|
|
50
|
-
|
|
51
|
-
# Detect input format: Jira key (MSSCI-12345) vs Story key (35-7-name)
|
|
52
|
-
if [[ "$INPUT_KEY" =~ ^${JIRA_PROJECT}-[0-9]+$ ]]; then
|
|
53
|
-
# Input is Jira issue key format (e.g., MSSCI-10991)
|
|
54
|
-
JIRA_ISSUE_KEY="$INPUT_KEY"
|
|
55
|
-
info "🔍 Using Jira issue key: ${JIRA_ISSUE_KEY}"
|
|
56
|
-
|
|
57
|
-
# Skip story file lookup - we'll work directly with Jira
|
|
58
|
-
STORY_KEY=""
|
|
59
|
-
|
|
60
|
-
else
|
|
61
|
-
# Input is story key format (e.g., 35-7-feedback-rule-delete-404)
|
|
62
|
-
STORY_KEY="$INPUT_KEY"
|
|
63
|
-
|
|
64
|
-
# Extract epic number from story key
|
|
65
|
-
EPIC_NUM=$(echo "$STORY_KEY" | cut -d'-' -f1)
|
|
66
|
-
|
|
67
|
-
info "🔍 Checking story ${STORY_KEY} in Jira..."
|
|
68
|
-
|
|
69
|
-
# Find the story
|
|
70
|
-
STORY_FILE=$(find_story_file "$STORY_KEY")
|
|
71
|
-
if [ -z "$STORY_FILE" ]; then
|
|
72
|
-
error "Error: Story ${STORY_KEY} not found in sprint files"
|
|
73
|
-
exit 2
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# Get Jira link
|
|
77
|
-
STORY_JIRA_LINK=$(get_story_field "$STORY_KEY" "jira")
|
|
78
|
-
if [ "$STORY_JIRA_LINK" = "null" ] || [ -z "$STORY_JIRA_LINK" ]; then
|
|
79
|
-
warn "⚠️ Story ${STORY_KEY} not synced to Jira yet"
|
|
80
|
-
echo ""
|
|
81
|
-
echo "Proceeding without Jira sync. Story will be synced later."
|
|
82
|
-
echo "To sync now, run: ./scripts/sync-epic-to-jira.sh ${EPIC_NUM}"
|
|
83
|
-
exit 0
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
JIRA_ISSUE_KEY=$(extract_jira_key "$STORY_JIRA_LINK")
|
|
87
|
-
success "✅ Found Jira issue: ${JIRA_ISSUE_KEY}"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# Get current user
|
|
91
|
-
CURRENT_USER=$(jira me 2>/dev/null || echo "")
|
|
92
|
-
if [ -z "$CURRENT_USER" ]; then
|
|
93
|
-
error "Error: Could not get current Jira user. Run 'jira init' to configure."
|
|
94
|
-
exit 3
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
info "👤 Current user: ${CURRENT_USER}"
|
|
98
|
-
|
|
99
|
-
# Get issue details (using --raw for JSON parsing)
|
|
100
|
-
info "📋 Fetching issue details..."
|
|
101
|
-
ISSUE_JSON=$(jira issue view "$JIRA_ISSUE_KEY" --raw 2>/dev/null || echo "{}")
|
|
102
|
-
|
|
103
|
-
if [ "$ISSUE_JSON" = "{}" ]; then
|
|
104
|
-
error "Error: Could not fetch issue ${JIRA_ISSUE_KEY}"
|
|
105
|
-
exit 3
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
# Parse assignee (use printf to avoid zsh echo interpreting \n in JSON)
|
|
109
|
-
ASSIGNEE=$(printf '%s' "$ISSUE_JSON" | jq -r '.fields.assignee.displayName // "Unassigned"')
|
|
110
|
-
ASSIGNEE_EMAIL=$(printf '%s' "$ISSUE_JSON" | jq -r '.fields.assignee.emailAddress // ""')
|
|
111
|
-
STATUS=$(printf '%s' "$ISSUE_JSON" | jq -r '.fields.status.name // "Unknown"')
|
|
112
|
-
SUMMARY=$(printf '%s' "$ISSUE_JSON" | jq -r '.fields.summary // "No summary"')
|
|
113
|
-
|
|
114
|
-
echo ""
|
|
115
|
-
echo "📋 Issue: ${JIRA_ISSUE_KEY}"
|
|
116
|
-
echo " Summary: ${SUMMARY}"
|
|
117
|
-
echo " Status: ${STATUS}"
|
|
118
|
-
echo " Assignee: ${ASSIGNEE}"
|
|
119
|
-
|
|
120
|
-
# Check assignment status
|
|
121
|
-
if [ "$ASSIGNEE" = "Unassigned" ]; then
|
|
122
|
-
success "✅ Story is UNASSIGNED - available to claim"
|
|
123
|
-
|
|
124
|
-
if [ "$CLAIM_MODE" = true ]; then
|
|
125
|
-
info "🎯 Claiming story..."
|
|
126
|
-
|
|
127
|
-
# Assign to self
|
|
128
|
-
if jira issue assign "$JIRA_ISSUE_KEY" "$(jira me)" --project "$JIRA_PROJECT" 2>/dev/null; then
|
|
129
|
-
success "✅ Assigned to you"
|
|
130
|
-
else
|
|
131
|
-
error "Failed to assign issue"
|
|
132
|
-
exit 3
|
|
133
|
-
fi
|
|
134
20
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
info "📊 Moving to 'In Progress'..."
|
|
138
|
-
if jira issue move "$JIRA_ISSUE_KEY" "In Progress" --project "$JIRA_PROJECT" 2>/dev/null; then
|
|
139
|
-
success "✅ Moved to In Progress"
|
|
140
|
-
else
|
|
141
|
-
warn "⚠️ Could not transition to In Progress (may already be there or transition not available)"
|
|
142
|
-
fi
|
|
143
|
-
fi
|
|
144
|
-
|
|
145
|
-
echo ""
|
|
146
|
-
success "🎉 Story ${JIRA_ISSUE_KEY} claimed successfully!"
|
|
147
|
-
else
|
|
148
|
-
echo ""
|
|
149
|
-
info "To claim this story, run:"
|
|
150
|
-
echo " $0 $STORY_KEY --claim"
|
|
151
|
-
fi
|
|
152
|
-
exit 0
|
|
153
|
-
|
|
154
|
-
elif [ "$ASSIGNEE_EMAIL" = "$CURRENT_USER" ] || [ "$ASSIGNEE" = "$(jira me 2>/dev/null)" ]; then
|
|
155
|
-
success "✅ Story is assigned to YOU - proceed with work"
|
|
156
|
-
exit 0
|
|
157
|
-
|
|
158
|
-
else
|
|
159
|
-
warn "⚠️ Story is assigned to: ${ASSIGNEE}"
|
|
160
|
-
echo ""
|
|
161
|
-
echo "This story is already being worked on by someone else."
|
|
162
|
-
echo "Please choose a different story or coordinate with ${ASSIGNEE}."
|
|
163
|
-
exit 1
|
|
164
|
-
fi
|
|
21
|
+
# Delegate to Python CLI
|
|
22
|
+
run_python_module jira claim "$@"
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env zsh
|
|
2
|
-
#
|
|
3
|
-
# Usage:
|
|
2
|
+
# Sync a single story between sprint YAML and Jira
|
|
3
|
+
# Usage: jira-sync-story.sh <story_key> [--transition] [--points] [--comment "message"]
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# Thin wrapper that delegates to Python CLI:
|
|
6
|
+
# python -m pennyfarthing_scripts.jira create story <story_key> [options]
|
|
7
|
+
#
|
|
8
|
+
# Note: The subcommand is 'create story' but it handles sync, not creation.
|
|
9
|
+
|
|
10
|
+
set -e
|
|
6
11
|
|
|
12
|
+
# Source common functions for Python discovery
|
|
7
13
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
|
|
14
|
+
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
15
|
+
source "${PARENT_DIR}/lib/common.sh"
|
|
16
|
+
|
|
17
|
+
# Delegate to Python CLI
|
|
18
|
+
run_python_module jira create story "$@"
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env zsh
|
|
2
|
-
#
|
|
3
|
-
# Usage:
|
|
2
|
+
# Sync an epic and its stories to Jira
|
|
3
|
+
# Usage: jira-sync.sh <epic_number> [--dry-run] [--transition] [--points]
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# Thin wrapper that delegates to Python CLI:
|
|
6
|
+
# python -m pennyfarthing_scripts.jira sync <epic_number> [options]
|
|
6
7
|
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
# Source common functions for Python discovery
|
|
7
11
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
|
|
12
|
+
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
13
|
+
source "${PARENT_DIR}/lib/common.sh"
|
|
14
|
+
|
|
15
|
+
# Delegate to Python CLI
|
|
16
|
+
run_python_module jira sync "$@"
|
|
@@ -1,104 +1,16 @@
|
|
|
1
|
-
#!/bin/
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
2
|
# Sync an epic and its stories to Jira
|
|
3
|
-
# Usage:
|
|
3
|
+
# Usage: sync-epic-jira.sh <epic-id> [--dry-run] [--transition] [--points] [--all]
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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.
|
|
10
|
-
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
EPIC_ID="${1:-}"
|
|
14
|
-
|
|
15
|
-
if [[ -z "$EPIC_ID" ]]; then
|
|
16
|
-
echo "Usage: sync-epic-jira.sh <epic-id> [options]"
|
|
17
|
-
echo ""
|
|
18
|
-
echo "Syncs an epic and its stories from sprint YAML to Jira."
|
|
19
|
-
echo ""
|
|
20
|
-
echo "Options:"
|
|
21
|
-
echo " --dry-run Show what would be done without making changes"
|
|
22
|
-
echo " --transition Transition Jira issues to match sprint YAML status"
|
|
23
|
-
echo " --points Sync story points from sprint YAML to Jira"
|
|
24
|
-
echo " --all Equivalent to --transition --points"
|
|
25
|
-
echo ""
|
|
26
|
-
echo "Examples:"
|
|
27
|
-
echo " sync-epic-jira.sh MSSCI-11952 # Show sync status"
|
|
28
|
-
echo " sync-epic-jira.sh MSSCI-11952 --dry-run # Preview changes"
|
|
29
|
-
echo " sync-epic-jira.sh MSSCI-11952 --all # Sync status and points"
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# PROJECT_ROOT should be set by run.sh, but find it if not
|
|
34
|
-
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
|
35
|
-
d="$PWD"
|
|
36
|
-
while [[ ! -d "$d/.claude" ]] && [[ "$d" != "/" ]]; do
|
|
37
|
-
d="$(dirname "$d")"
|
|
38
|
-
done
|
|
39
|
-
PROJECT_ROOT="$d"
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
43
|
-
|
|
44
|
-
if [[ ! -f "$SPRINT_FILE" ]]; then
|
|
45
|
-
echo "Error: Sprint file not found at $SPRINT_FILE"
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
if ! command -v yq &> /dev/null; then
|
|
50
|
-
echo "Error: yq is required but not installed"
|
|
51
|
-
echo "Install with: brew install yq"
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# Check if epic exists in sprint YAML
|
|
56
|
-
EPIC_EXISTS=$(yq ".epics[] | select(.id == \"$EPIC_ID\") | .id" "$SPRINT_FILE" 2>/dev/null || echo "")
|
|
57
|
-
|
|
58
|
-
if [[ -z "$EPIC_EXISTS" ]]; then
|
|
59
|
-
echo "Error: Epic $EPIC_ID not found in $SPRINT_FILE"
|
|
60
|
-
echo ""
|
|
61
|
-
echo "Available epics:"
|
|
62
|
-
yq '.epics[].id' "$SPRINT_FILE" 2>/dev/null || echo " None found"
|
|
63
|
-
exit 1
|
|
64
|
-
fi
|
|
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
|
|
85
|
-
shift # Remove epic-id from arguments
|
|
86
|
-
SYNC_ARGS=("$EPIC_ID")
|
|
5
|
+
# Thin wrapper that delegates to Python CLI:
|
|
6
|
+
# python -m pennyfarthing_scripts.jira sync <epic-id> [options]
|
|
87
7
|
|
|
88
|
-
|
|
89
|
-
for arg in "$@"; do
|
|
90
|
-
case "$arg" in
|
|
91
|
-
--all)
|
|
92
|
-
SYNC_ARGS+=("--transition" "--points")
|
|
93
|
-
;;
|
|
94
|
-
*)
|
|
95
|
-
SYNC_ARGS+=("$arg")
|
|
96
|
-
;;
|
|
97
|
-
esac
|
|
98
|
-
done
|
|
8
|
+
set -e
|
|
99
9
|
|
|
100
|
-
|
|
101
|
-
|
|
10
|
+
# Source common functions for Python discovery
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
13
|
+
source "${PARENT_DIR}/lib/common.sh"
|
|
102
14
|
|
|
103
|
-
#
|
|
104
|
-
|
|
15
|
+
# Delegate to Python CLI
|
|
16
|
+
run_python_module jira sync "$@"
|
|
@@ -155,3 +155,58 @@ check_dependencies() {
|
|
|
155
155
|
fi
|
|
156
156
|
return 0
|
|
157
157
|
}
|
|
158
|
+
|
|
159
|
+
#############################################
|
|
160
|
+
# Python Execution
|
|
161
|
+
#############################################
|
|
162
|
+
|
|
163
|
+
# get_python
|
|
164
|
+
# Find the best Python interpreter (venv preferred, fallback to system)
|
|
165
|
+
# Sets PYTHON_CMD variable
|
|
166
|
+
#
|
|
167
|
+
# Priority:
|
|
168
|
+
# 1. PROJECT_ROOT/.venv/bin/python (project venv)
|
|
169
|
+
# 2. python3 (system)
|
|
170
|
+
# 3. python (legacy fallback)
|
|
171
|
+
#
|
|
172
|
+
# Usage:
|
|
173
|
+
# get_python
|
|
174
|
+
# $PYTHON_CMD -m pennyfarthing_scripts.jira view MSSCI-12345
|
|
175
|
+
#
|
|
176
|
+
get_python() {
|
|
177
|
+
# Find project root if not set
|
|
178
|
+
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
|
179
|
+
local d="$PWD"
|
|
180
|
+
while [[ ! -d "$d/.claude" ]] && [[ "$d" != "/" ]]; do
|
|
181
|
+
d="$(dirname "$d")"
|
|
182
|
+
done
|
|
183
|
+
PROJECT_ROOT="$d"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Check for project venv first
|
|
187
|
+
if [[ -x "${PROJECT_ROOT}/.venv/bin/python" ]]; then
|
|
188
|
+
PYTHON_CMD="${PROJECT_ROOT}/.venv/bin/python"
|
|
189
|
+
elif command -v python3 &> /dev/null; then
|
|
190
|
+
PYTHON_CMD="python3"
|
|
191
|
+
elif command -v python &> /dev/null; then
|
|
192
|
+
PYTHON_CMD="python"
|
|
193
|
+
else
|
|
194
|
+
error "Python not found. Install Python 3.11+ or create .venv"
|
|
195
|
+
return 1
|
|
196
|
+
fi
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# run_python_module MODULE [ARGS...]
|
|
200
|
+
# Run a pennyfarthing_scripts Python module with proper venv handling
|
|
201
|
+
#
|
|
202
|
+
# Usage:
|
|
203
|
+
# run_python_module jira view MSSCI-12345
|
|
204
|
+
# run_python_module sprint status
|
|
205
|
+
# run_python_module story size 3
|
|
206
|
+
#
|
|
207
|
+
run_python_module() {
|
|
208
|
+
get_python || return 1
|
|
209
|
+
local module="$1"
|
|
210
|
+
shift
|
|
211
|
+
exec $PYTHON_CMD -m "pennyfarthing_scripts.${module}" "$@"
|
|
212
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# sidecar-health.sh - Check sidecar files for bloat and staleness
|
|
3
|
+
#
|
|
4
|
+
# Usage: sidecar-health.sh [--fix]
|
|
5
|
+
# --fix Offer to archive bloated files
|
|
6
|
+
#
|
|
7
|
+
# Thresholds:
|
|
8
|
+
# gotchas.md: 50 lines max
|
|
9
|
+
# patterns.md: 50 lines max
|
|
10
|
+
# decisions.md: 40 lines max
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# Find project root
|
|
15
|
+
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
|
16
|
+
d="$PWD"
|
|
17
|
+
while [[ ! -d "$d/.claude" ]] && [[ "$d" != "/" ]]; do
|
|
18
|
+
d="$(dirname "$d")"
|
|
19
|
+
done
|
|
20
|
+
PROJECT_ROOT="$d"
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
SIDECAR_DIR="$PROJECT_ROOT/.pennyfarthing/sidecars"
|
|
24
|
+
FIX_MODE="${1:-}"
|
|
25
|
+
|
|
26
|
+
# Thresholds
|
|
27
|
+
GOTCHAS_MAX=50
|
|
28
|
+
PATTERNS_MAX=50
|
|
29
|
+
DECISIONS_MAX=40
|
|
30
|
+
|
|
31
|
+
# Colors
|
|
32
|
+
RED='\033[0;31m'
|
|
33
|
+
YELLOW='\033[1;33m'
|
|
34
|
+
GREEN='\033[0;32m'
|
|
35
|
+
NC='\033[0m' # No Color
|
|
36
|
+
|
|
37
|
+
echo "Sidecar Health Check"
|
|
38
|
+
echo "===================="
|
|
39
|
+
echo ""
|
|
40
|
+
|
|
41
|
+
issues_found=0
|
|
42
|
+
|
|
43
|
+
check_file() {
|
|
44
|
+
local file="$1"
|
|
45
|
+
local max_lines="$2"
|
|
46
|
+
local agent=$(basename "$(dirname "$file")")
|
|
47
|
+
local filename=$(basename "$file")
|
|
48
|
+
|
|
49
|
+
if [[ ! -f "$file" ]]; then
|
|
50
|
+
return
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
local lines=$(wc -l < "$file" | tr -d ' ')
|
|
54
|
+
|
|
55
|
+
if [[ $lines -gt $max_lines ]]; then
|
|
56
|
+
echo -e "${RED}BLOATED${NC}: $agent/$filename ($lines lines, max $max_lines)"
|
|
57
|
+
issues_found=$((issues_found + 1))
|
|
58
|
+
|
|
59
|
+
if [[ "$FIX_MODE" == "--fix" ]]; then
|
|
60
|
+
local archive_dir="$PROJECT_ROOT/.pennyfarthing/sidecars/.archive"
|
|
61
|
+
local timestamp=$(date +%Y%m%d)
|
|
62
|
+
mkdir -p "$archive_dir"
|
|
63
|
+
|
|
64
|
+
echo " → Archiving to .archive/${agent}-${filename%.md}-${timestamp}.md"
|
|
65
|
+
cp "$file" "$archive_dir/${agent}-${filename%.md}-${timestamp}.md"
|
|
66
|
+
echo " → Original preserved. Manually prune $file to <$max_lines lines."
|
|
67
|
+
fi
|
|
68
|
+
elif [[ $lines -gt $((max_lines * 80 / 100)) ]]; then
|
|
69
|
+
echo -e "${YELLOW}WARNING${NC}: $agent/$filename ($lines lines, approaching $max_lines limit)"
|
|
70
|
+
else
|
|
71
|
+
echo -e "${GREEN}OK${NC}: $agent/$filename ($lines lines)"
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Check all agent sidecars
|
|
76
|
+
for agent_dir in "$SIDECAR_DIR"/*/; do
|
|
77
|
+
if [[ -d "$agent_dir" ]]; then
|
|
78
|
+
agent=$(basename "$agent_dir")
|
|
79
|
+
[[ "$agent" == ".archive" ]] && continue
|
|
80
|
+
|
|
81
|
+
check_file "$agent_dir/gotchas.md" $GOTCHAS_MAX
|
|
82
|
+
check_file "$agent_dir/patterns.md" $PATTERNS_MAX
|
|
83
|
+
check_file "$agent_dir/decisions.md" $DECISIONS_MAX
|
|
84
|
+
fi
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
echo ""
|
|
88
|
+
if [[ $issues_found -gt 0 ]]; then
|
|
89
|
+
echo "Found $issues_found bloated file(s)."
|
|
90
|
+
if [[ "$FIX_MODE" != "--fix" ]]; then
|
|
91
|
+
echo "Run with --fix to archive and prepare for pruning."
|
|
92
|
+
fi
|
|
93
|
+
exit 1
|
|
94
|
+
else
|
|
95
|
+
echo "All sidecar files within limits."
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
@@ -90,8 +90,8 @@ elif [ -d "$PROJECT_ROOT/.session/agents" ]; then
|
|
|
90
90
|
fi
|
|
91
91
|
fi
|
|
92
92
|
|
|
93
|
-
# Get character name from
|
|
94
|
-
#
|
|
93
|
+
# Get character name from theme file (single source of truth)
|
|
94
|
+
# Priority: .pennyfarthing/config.local.yaml > .claude/persona-config.yaml for theme name
|
|
95
95
|
config_file=""
|
|
96
96
|
if [ -f "$PROJECT_ROOT/.pennyfarthing/config.local.yaml" ]; then
|
|
97
97
|
config_file="$PROJECT_ROOT/.pennyfarthing/config.local.yaml"
|
|
@@ -101,28 +101,33 @@ fi
|
|
|
101
101
|
|
|
102
102
|
character_display=""
|
|
103
103
|
if [ -n "$config_file" ] && [ -n "$agent_name" ]; then
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if [ -n "$
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
# Get theme name from config
|
|
105
|
+
theme=$(yq '.theme' "$config_file" 2>/dev/null)
|
|
106
|
+
|
|
107
|
+
if [ -n "$theme" ] && [ "$theme" != "null" ]; then
|
|
108
|
+
# Read character directly from theme file (matches agent-session.sh behavior)
|
|
109
|
+
theme_file="$PROJECT_ROOT/.pennyfarthing/personas/themes/${theme}.yaml"
|
|
110
|
+
if [ -f "$theme_file" ]; then
|
|
111
|
+
full_name=$(yq ".agents.${agent_name}.character" "$theme_file" 2>/dev/null)
|
|
112
|
+
|
|
113
|
+
if [ -n "$full_name" ] && [ "$full_name" != "null" ]; then
|
|
114
|
+
# Smart character name extraction:
|
|
115
|
+
# 1. Remove parenthetical content: "Breq (Justice of Toren)" → "Breq"
|
|
116
|
+
# 2. Strip common titles: "Captain Kirk" → "Kirk"
|
|
117
|
+
# 3. If single word remains, use it; otherwise take last word
|
|
118
|
+
clean_name=$(echo "$full_name" | sed 's/ *([^)]*)//g' | xargs)
|
|
119
|
+
clean_name=$(echo "$clean_name" | sed -E 's/^(Captain|Lieutenant|Dr\.|Doc|Mr\.|Mrs\.|Ms\.|Admiral|Commander|Chief|Ensign|Translator|Agent|Colonel|Major|Sergeant|Professor|Lord|Lady|Sir|The) +//i')
|
|
120
|
+
word_count=$(echo "$clean_name" | wc -w | tr -d ' ')
|
|
121
|
+
if [ "$word_count" -eq 1 ]; then
|
|
122
|
+
character_display="$clean_name"
|
|
123
|
+
else
|
|
124
|
+
character_display=$(echo "$clean_name" | awk '{print $NF}')
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
119
127
|
fi
|
|
120
|
-
fi
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
theme=$(yq '.theme' "$config_file" 2>/dev/null)
|
|
125
|
-
if [ -n "$theme" ] && [ "$theme" != "null" ]; then
|
|
129
|
+
# Fallback to theme name if no character found
|
|
130
|
+
if [ -z "$character_display" ]; then
|
|
126
131
|
character_display="$(echo "${theme:0:1}" | tr '[:lower:]' '[:upper:]')${theme:1}"
|
|
127
132
|
fi
|
|
128
133
|
fi
|