@pennyfarthing/core 7.6.0 → 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 +37 -2
- 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 +25 -8
- 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 +393 -0
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +20 -0
- 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/tests/question-reflector.test.mjs +545 -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/deploy.sh +13 -1
- 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
|
@@ -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
|
|
@@ -223,6 +223,7 @@ if $DRY_RUN; then
|
|
|
223
223
|
log_dry "git tag -a $TAG_NAME -m 'Release $NEW_VERSION'"
|
|
224
224
|
log_dry "git push origin develop main --tags"
|
|
225
225
|
log_dry "gh release create $TAG_NAME --title 'Release $NEW_VERSION' --notes-from-tag --verify-tag"
|
|
226
|
+
log_dry "source .env && npm publish --access public"
|
|
226
227
|
log_dry "git checkout develop"
|
|
227
228
|
else
|
|
228
229
|
log_info "Merging develop to main..."
|
|
@@ -261,7 +262,16 @@ else
|
|
|
261
262
|
log_warn "Create release manually at: https://github.com/1898andCo/pennyfarthing/releases/new"
|
|
262
263
|
fi
|
|
263
264
|
|
|
264
|
-
# Step 8:
|
|
265
|
+
# Step 8: Publish to npm
|
|
266
|
+
log_info "Publishing to npm..."
|
|
267
|
+
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
|
268
|
+
source "$PROJECT_ROOT/.env"
|
|
269
|
+
npm config set //registry.npmjs.org/:_authToken "$NPM_TOKEN"
|
|
270
|
+
fi
|
|
271
|
+
(cd "$PROJECT_ROOT" && npm publish --access public)
|
|
272
|
+
log_info "Published @pennyfarthing/core@$NEW_VERSION to npm"
|
|
273
|
+
|
|
274
|
+
# Step 9: Return to develop
|
|
265
275
|
log_info "Returning to develop..."
|
|
266
276
|
git -C "$PROJECT_ROOT" checkout develop
|
|
267
277
|
fi
|
|
@@ -273,12 +283,14 @@ if $DRY_RUN; then
|
|
|
273
283
|
echo " Would release version: $NEW_VERSION"
|
|
274
284
|
echo " Would create tag: $TAG_NAME"
|
|
275
285
|
echo " Would create GitHub release: $TAG_NAME"
|
|
286
|
+
echo " Would publish: @pennyfarthing/core@$NEW_VERSION"
|
|
276
287
|
else
|
|
277
288
|
log_info "Deploy complete!"
|
|
278
289
|
echo ""
|
|
279
290
|
echo " Version: $NEW_VERSION"
|
|
280
291
|
echo " Tag: $TAG_NAME"
|
|
281
292
|
echo " GitHub release: https://github.com/1898andCo/pennyfarthing/releases/tag/$TAG_NAME"
|
|
293
|
+
echo " npm: @pennyfarthing/core@$NEW_VERSION"
|
|
282
294
|
echo " Branches pushed: develop, main"
|
|
283
295
|
fi
|
|
284
296
|
echo ""
|
|
@@ -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
|