@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
|
@@ -53,6 +53,7 @@ critical_threshold = $DEFAULT_CRITICAL_THRESHOLD
|
|
|
53
53
|
max_tokens = $DEFAULT_MAX_TOKENS
|
|
54
54
|
tirepump_threshold = $DEFAULT_TIREPUMP_THRESHOLD
|
|
55
55
|
permission_mode = 'manual' # Default to manual
|
|
56
|
+
relay_mode = False # MSSCI-12395: Independent auto-handoff toggle
|
|
56
57
|
|
|
57
58
|
# First try .pennyfarthing/config.local.yaml (preferred location)
|
|
58
59
|
try:
|
|
@@ -67,9 +68,13 @@ try:
|
|
|
67
68
|
critical_threshold = cb.get('critical_threshold', critical_threshold)
|
|
68
69
|
max_tokens = cb.get('max_tokens', max_tokens)
|
|
69
70
|
tirepump_threshold = cb.get('tirepump_threshold', tirepump_threshold)
|
|
70
|
-
# Read permission_mode from workflow section
|
|
71
|
-
if 'workflow' in config
|
|
72
|
-
permission_mode
|
|
71
|
+
# Read permission_mode and relay_mode from workflow section
|
|
72
|
+
if 'workflow' in config:
|
|
73
|
+
if 'permission_mode' in config['workflow']:
|
|
74
|
+
permission_mode = config['workflow']['permission_mode']
|
|
75
|
+
# MSSCI-12395: relay_mode for auto-handoff (independent of permission_mode)
|
|
76
|
+
if 'relay_mode' in config['workflow']:
|
|
77
|
+
relay_mode = config['workflow']['relay_mode'] == True
|
|
73
78
|
except:
|
|
74
79
|
# Fallback to settings.local.json (legacy location)
|
|
75
80
|
try:
|
|
@@ -82,8 +87,12 @@ except:
|
|
|
82
87
|
critical_threshold = cb.get('critical_threshold', critical_threshold)
|
|
83
88
|
max_tokens = cb.get('max_tokens', max_tokens)
|
|
84
89
|
tirepump_threshold = cb.get('tirepump_threshold', tirepump_threshold)
|
|
85
|
-
if 'workflow' in settings
|
|
86
|
-
permission_mode
|
|
90
|
+
if 'workflow' in settings:
|
|
91
|
+
if 'permission_mode' in settings['workflow']:
|
|
92
|
+
permission_mode = settings['workflow']['permission_mode']
|
|
93
|
+
# MSSCI-12395: relay_mode for auto-handoff (independent of permission_mode)
|
|
94
|
+
if 'relay_mode' in settings['workflow']:
|
|
95
|
+
relay_mode = settings['workflow']['relay_mode'] == True
|
|
87
96
|
except:
|
|
88
97
|
pass
|
|
89
98
|
|
|
@@ -93,6 +102,7 @@ print(f'CRITICAL_THRESHOLD={critical_threshold}')
|
|
|
93
102
|
print(f'MAX_TOKENS={max_tokens}')
|
|
94
103
|
print(f'TIREPUMP_THRESHOLD={tirepump_threshold}')
|
|
95
104
|
print(f'PERMISSION_MODE={permission_mode}')
|
|
105
|
+
print(f'RELAY_MODE={str(relay_mode).lower()}')
|
|
96
106
|
" 2>/dev/null)
|
|
97
107
|
|
|
98
108
|
# Apply config or use defaults
|
|
@@ -140,6 +150,7 @@ import json
|
|
|
140
150
|
warning_threshold = $WARNING_THRESHOLD
|
|
141
151
|
max_tokens = $MAX_TOKENS
|
|
142
152
|
permission_mode = '$PERMISSION_MODE'
|
|
153
|
+
relay_mode = '$RELAY_MODE' == 'true' # MSSCI-12395: Independent auto-handoff toggle
|
|
143
154
|
tirepump_threshold = $TIREPUMP_THRESHOLD # Threshold for TirePump auto-handoff (configurable)
|
|
144
155
|
|
|
145
156
|
with open('$TRANSCRIPT', 'r') as f:
|
|
@@ -193,16 +204,22 @@ if last_total is not None:
|
|
|
193
204
|
# Use usable percent for status decisions (more accurate for user)
|
|
194
205
|
if usable_pct > warning_threshold:
|
|
195
206
|
print('CONTEXT_STATUS=HIGH')
|
|
196
|
-
print('HANDOFF_MODE=auto')
|
|
197
207
|
else:
|
|
198
208
|
print('CONTEXT_STATUS=OK')
|
|
209
|
+
|
|
210
|
+
# HANDOFF_MODE: 'auto' if relay_mode enabled, 'ask' otherwise
|
|
211
|
+
# MSSCI-12395: relay_mode controls autohandoff independent of context level
|
|
212
|
+
if relay_mode:
|
|
213
|
+
print('HANDOFF_MODE=auto')
|
|
214
|
+
else:
|
|
199
215
|
print('HANDOFF_MODE=ask')
|
|
200
216
|
|
|
201
217
|
# TirePump: Use CONTEXT_CLEAR (clear + load next agent) when:
|
|
202
|
-
# 1.
|
|
218
|
+
# 1. relay_mode is true (auto-handoff enabled) - MSSCI-12395
|
|
203
219
|
# 2. context > 60% (tirepump_threshold)
|
|
204
220
|
# This enables continuous autonomous runs without manual intervention
|
|
205
|
-
|
|
221
|
+
# Legacy: also support permission_mode == 'turbo' for backwards compatibility
|
|
222
|
+
use_tirepump = (relay_mode or permission_mode == 'turbo') and usable_pct > tirepump_threshold
|
|
206
223
|
print(f'USE_TIREPUMP={str(use_tirepump).lower()}')
|
|
207
224
|
|
|
208
225
|
# Cyclist detection: Multiple methods for robustness
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
# prime.sh - Load essential project context at agent activation
|
|
3
3
|
# Usage: prime.sh [--minimal] [--full] [--quiet] [--agent <name>]
|
|
4
4
|
#
|
|
5
|
-
# Loads context in priority order (
|
|
6
|
-
# 1.
|
|
7
|
-
# 2.
|
|
8
|
-
# 3.
|
|
9
|
-
# 4.
|
|
10
|
-
# 5.
|
|
5
|
+
# Loads context in priority order (optimized for attention):
|
|
6
|
+
# 1. CLAUDE.md (already in system prompt - skipped)
|
|
7
|
+
# 2. Agent definition + behavior guide (HIGHEST PRIORITY - load first!)
|
|
8
|
+
# 3. Persona (already output by agent-session.sh before this runs)
|
|
9
|
+
# 4. Session summary (active work context)
|
|
10
|
+
# 5. Sidecars (patterns, gotchas, decisions - lowest priority)
|
|
11
|
+
# 6. Domain docs (--full only)
|
|
11
12
|
|
|
12
13
|
set -euo pipefail
|
|
13
14
|
|
|
@@ -33,7 +34,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
33
34
|
echo " --minimal Skip all context (fastest)"
|
|
34
35
|
echo " --full Include domain docs from .claude/project/"
|
|
35
36
|
echo " --quiet Suppress section headers"
|
|
36
|
-
echo " --agent <name> Load agent
|
|
37
|
+
echo " --agent <name> Load agent definition and sidecar"
|
|
37
38
|
exit 0
|
|
38
39
|
;;
|
|
39
40
|
*) shift ;;
|
|
@@ -48,33 +49,61 @@ print_header() {
|
|
|
48
49
|
fi
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
# CLAUDE.md is already loaded by Claude Code system prompt - skip it
|
|
52
|
-
|
|
53
52
|
# Stop here for minimal mode
|
|
54
53
|
if [[ "$MINIMAL" == "true" ]]; then
|
|
55
54
|
exit 0
|
|
56
55
|
fi
|
|
57
56
|
|
|
58
|
-
#
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# PRIORITY 1: Agent definition (HIGHEST ATTENTION ZONE)
|
|
59
|
+
# =============================================================================
|
|
60
|
+
# This is the most critical content - load it FIRST while attention is highest
|
|
61
|
+
|
|
62
|
+
if [[ -n "$AGENT_NAME" ]]; then
|
|
63
|
+
AGENT_FILE="$PROJECT_ROOT/.pennyfarthing/agents/${AGENT_NAME}.md"
|
|
64
|
+
if [[ -f "$AGENT_FILE" ]]; then
|
|
65
|
+
print_header "Agent Definition: ${AGENT_NAME}"
|
|
66
|
+
cat "$AGENT_FILE"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# PRIORITY 2: Agent behavior guide (shared protocols)
|
|
72
|
+
# =============================================================================
|
|
73
|
+
|
|
74
|
+
if [[ -n "$AGENT_NAME" ]]; then
|
|
75
|
+
BEHAVIOR_GUIDE="$PROJECT_ROOT/.pennyfarthing/guides/agent-behavior.md"
|
|
76
|
+
if [[ -f "$BEHAVIOR_GUIDE" ]]; then
|
|
77
|
+
print_header "Agent Behavior Guide"
|
|
78
|
+
cat "$BEHAVIOR_GUIDE"
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# =============================================================================
|
|
83
|
+
# PRIORITY 3: Persona already loaded by agent-session.sh (before prime.sh runs)
|
|
84
|
+
# =============================================================================
|
|
85
|
+
# Nothing to do here - persona is output by agent-session.sh start
|
|
86
|
+
|
|
87
|
+
# =============================================================================
|
|
88
|
+
# PRIORITY 4: Session summary (active work context)
|
|
89
|
+
# =============================================================================
|
|
90
|
+
|
|
91
|
+
# Sprint summary (brief - just name and progress)
|
|
59
92
|
if [[ -f "$(get_sprint_file)" ]]; then
|
|
60
93
|
print_header "Sprint Context"
|
|
61
|
-
|
|
62
|
-
# Use shared functions for consistent output
|
|
63
94
|
summary=$(get_sprint_summary)
|
|
64
95
|
if [[ -n "$summary" ]]; then
|
|
65
96
|
echo "$summary"
|
|
66
97
|
fi
|
|
67
|
-
|
|
68
98
|
progress=$(get_sprint_progress)
|
|
69
99
|
if [[ -n "$progress" ]]; then
|
|
70
100
|
echo "$progress"
|
|
71
101
|
fi
|
|
72
102
|
fi
|
|
73
103
|
|
|
74
|
-
#
|
|
104
|
+
# Active session (if exists) - extract header metadata + current assessment
|
|
75
105
|
SESSION_FILE=""
|
|
76
106
|
if [[ -d "$PROJECT_ROOT/.session" ]]; then
|
|
77
|
-
# Find a session file (typically only one active at a time)
|
|
78
107
|
SESSION_FILE=$(find "$PROJECT_ROOT/.session" -maxdepth 1 -name "*-session.md" -type f 2>/dev/null | head -1)
|
|
79
108
|
fi
|
|
80
109
|
|
|
@@ -82,18 +111,14 @@ if [[ -n "$SESSION_FILE" && -f "$SESSION_FILE" ]]; then
|
|
|
82
111
|
print_header "Active Session: $(basename "$SESSION_FILE")"
|
|
83
112
|
|
|
84
113
|
# Extract header (everything before first ## heading)
|
|
85
|
-
# This includes: title, metadata fields (Phase, Workflow, Repos, Branch, etc.)
|
|
86
114
|
awk '/^## / {exit} {print}' "$SESSION_FILE"
|
|
87
115
|
|
|
88
|
-
# Find the most recent assessment section
|
|
89
|
-
# Assessment sections are named: "## {Agent} Assessment" (TEA, Dev, Reviewer, SM, etc.)
|
|
90
|
-
# Show the LAST one in the file as it represents current state
|
|
116
|
+
# Find the most recent assessment section
|
|
91
117
|
last_assessment=$(grep -n '^## .*Assessment' "$SESSION_FILE" | tail -1 | cut -d: -f1)
|
|
92
118
|
|
|
93
119
|
if [[ -n "$last_assessment" ]]; then
|
|
94
120
|
echo ""
|
|
95
121
|
echo "---"
|
|
96
|
-
# Extract from that line to next ## or EOF
|
|
97
122
|
awk -v start="$last_assessment" '
|
|
98
123
|
NR >= start {
|
|
99
124
|
if (NR > start && /^## /) exit
|
|
@@ -103,29 +128,29 @@ if [[ -n "$SESSION_FILE" && -f "$SESSION_FILE" ]]; then
|
|
|
103
128
|
fi
|
|
104
129
|
fi
|
|
105
130
|
|
|
106
|
-
#
|
|
131
|
+
# =============================================================================
|
|
132
|
+
# PRIORITY 5: Sidecars (patterns, gotchas, decisions - LOWEST PRIORITY)
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# These are supplementary - loaded last when attention is lower
|
|
135
|
+
|
|
107
136
|
if [[ -n "$AGENT_NAME" ]]; then
|
|
108
137
|
SIDECAR_DIR="$PROJECT_ROOT/.pennyfarthing/sidecars/${AGENT_NAME}"
|
|
109
138
|
if [[ -d "$SIDECAR_DIR" ]]; then
|
|
110
|
-
|
|
139
|
+
# Load in specific order: patterns first (most useful), then gotchas, then decisions
|
|
140
|
+
for filename in patterns.md gotchas.md decisions.md; do
|
|
141
|
+
pattern_file="$SIDECAR_DIR/$filename"
|
|
111
142
|
if [[ -f "$pattern_file" ]]; then
|
|
112
|
-
print_header "Agent Sidecar: $
|
|
143
|
+
print_header "Agent Sidecar: $filename"
|
|
113
144
|
cat "$pattern_file"
|
|
114
145
|
fi
|
|
115
146
|
done
|
|
116
147
|
fi
|
|
117
148
|
fi
|
|
118
149
|
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if [[ -f "$BEHAVIOR_GUIDE" ]]; then
|
|
123
|
-
print_header "Agent Behavior Guide"
|
|
124
|
-
cat "$BEHAVIOR_GUIDE"
|
|
125
|
-
fi
|
|
126
|
-
fi
|
|
150
|
+
# =============================================================================
|
|
151
|
+
# PRIORITY 6: Domain docs (--full only, rarely used)
|
|
152
|
+
# =============================================================================
|
|
127
153
|
|
|
128
|
-
# 5. Domain docs (--full only)
|
|
129
154
|
if [[ "$FULL" == "true" ]]; then
|
|
130
155
|
for doc in "$PROJECT_ROOT/.claude/project"/CLAUDE-*.md; do
|
|
131
156
|
if [[ -f "$doc" ]]; then
|
|
@@ -143,6 +143,7 @@ echo " Branch: $BRANCH_NAME"
|
|
|
143
143
|
echo " Repos: $REPOS"
|
|
144
144
|
|
|
145
145
|
# Process repos based on selection
|
|
146
|
+
# When processing multiple repos, run in parallel for faster network I/O
|
|
146
147
|
case "$REPOS" in
|
|
147
148
|
api)
|
|
148
149
|
create_or_checkout_branch "$REPO_BASE/Pennyfarthing-api" "Pennyfarthing-api"
|
|
@@ -151,8 +152,42 @@ case "$REPOS" in
|
|
|
151
152
|
create_or_checkout_branch "$REPO_BASE/Pennyfarthing-ui" "Pennyfarthing-ui"
|
|
152
153
|
;;
|
|
153
154
|
all)
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
# Parallel execution for both repos
|
|
156
|
+
tmpdir=$(mktemp -d)
|
|
157
|
+
trap "rm -rf '$tmpdir'" EXIT
|
|
158
|
+
HAD_ERRORS=false
|
|
159
|
+
|
|
160
|
+
# Run both in parallel, capturing output
|
|
161
|
+
# Write to separate files to avoid race condition on shared file
|
|
162
|
+
(
|
|
163
|
+
create_or_checkout_branch "$REPO_BASE/Pennyfarthing-api" "Pennyfarthing-api"
|
|
164
|
+
echo "$REPO_BASE/Pennyfarthing-api:Pennyfarthing-api" > "$tmpdir/api.processed"
|
|
165
|
+
) > "$tmpdir/api.out" 2>&1 &
|
|
166
|
+
pid_api=$!
|
|
167
|
+
|
|
168
|
+
(
|
|
169
|
+
create_or_checkout_branch "$REPO_BASE/Pennyfarthing-ui" "Pennyfarthing-ui"
|
|
170
|
+
echo "$REPO_BASE/Pennyfarthing-ui:Pennyfarthing-ui" > "$tmpdir/ui.processed"
|
|
171
|
+
) > "$tmpdir/ui.out" 2>&1 &
|
|
172
|
+
pid_ui=$!
|
|
173
|
+
|
|
174
|
+
# Wait for both and capture exit codes
|
|
175
|
+
wait $pid_api; rc_api=$?
|
|
176
|
+
wait $pid_ui; rc_ui=$?
|
|
177
|
+
|
|
178
|
+
# Show output in order
|
|
179
|
+
[ -f "$tmpdir/api.out" ] && cat "$tmpdir/api.out"
|
|
180
|
+
[ -f "$tmpdir/ui.out" ] && cat "$tmpdir/ui.out"
|
|
181
|
+
|
|
182
|
+
# Rebuild PROCESSED_REPOS from separate files (avoids race condition)
|
|
183
|
+
[ -f "$tmpdir/api.processed" ] && PROCESSED_REPOS+=("$(cat "$tmpdir/api.processed")")
|
|
184
|
+
[ -f "$tmpdir/ui.processed" ] && PROCESSED_REPOS+=("$(cat "$tmpdir/ui.processed")")
|
|
185
|
+
|
|
186
|
+
# Check for failures
|
|
187
|
+
if [ $rc_api -ne 0 ] || [ $rc_ui -ne 0 ]; then
|
|
188
|
+
echo "⚠️ Some repos had errors"
|
|
189
|
+
HAD_ERRORS=true
|
|
190
|
+
fi
|
|
156
191
|
;;
|
|
157
192
|
esac
|
|
158
193
|
|
|
@@ -222,5 +257,11 @@ done
|
|
|
222
257
|
|
|
223
258
|
echo ""
|
|
224
259
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
225
|
-
|
|
226
|
-
echo "
|
|
260
|
+
if [ "$HAD_ERRORS" = true ]; then
|
|
261
|
+
echo "⚠️ Done with errors. Check output above."
|
|
262
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
263
|
+
exit 1
|
|
264
|
+
else
|
|
265
|
+
echo "✅ Done! All branches verified and ready."
|
|
266
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
267
|
+
fi
|
|
@@ -82,15 +82,26 @@ if ! $BRIEF; then
|
|
|
82
82
|
echo ""
|
|
83
83
|
fi
|
|
84
84
|
|
|
85
|
-
# Check each repo from configuration
|
|
85
|
+
# Check each repo from configuration (parallelized for performance)
|
|
86
86
|
repo_count=$(get_repo_count)
|
|
87
87
|
if [[ "$repo_count" -eq 0 ]]; then
|
|
88
88
|
# No repos configured, just show current directory
|
|
89
89
|
show_repo_status "Project" "$PROJECT_ROOT"
|
|
90
90
|
else
|
|
91
|
+
# Create temp dir for parallel output capture
|
|
92
|
+
tmpdir=$(mktemp -d)
|
|
93
|
+
trap "rm -rf '$tmpdir'" EXIT
|
|
94
|
+
|
|
95
|
+
# Launch status checks in parallel
|
|
91
96
|
for repo in $(get_repos); do
|
|
92
97
|
repo_path=$(get_repo_full_path "$repo")
|
|
93
|
-
show_repo_status "$repo" "$repo_path"
|
|
98
|
+
(show_repo_status "$repo" "$repo_path" > "$tmpdir/$repo.out" 2>&1) &
|
|
99
|
+
done
|
|
100
|
+
wait
|
|
101
|
+
|
|
102
|
+
# Output results in order
|
|
103
|
+
for repo in $(get_repos); do
|
|
104
|
+
[ -f "$tmpdir/$repo.out" ] && cat "$tmpdir/$repo.out"
|
|
94
105
|
done
|
|
95
106
|
fi
|
|
96
107
|
|
|
@@ -108,14 +119,28 @@ if ! $BRIEF; then
|
|
|
108
119
|
unpushed=$(git -C "$PROJECT_ROOT" log origin/develop..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ')
|
|
109
120
|
total_unpushed=$((total_unpushed + unpushed))
|
|
110
121
|
else
|
|
122
|
+
# Parallelize summary collection
|
|
123
|
+
summary_tmp=$(mktemp -d)
|
|
111
124
|
for repo in $(get_repos); do
|
|
112
125
|
repo_path=$(get_repo_full_path "$repo")
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
(
|
|
127
|
+
[ -d "$repo_path/.git" ] || [ -d "$repo_path" ] || exit 0
|
|
128
|
+
count=$(git -C "$repo_path" status --short 2>/dev/null | wc -l | tr -d ' ')
|
|
129
|
+
unpushed=$(git -C "$repo_path" log origin/develop..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ')
|
|
130
|
+
echo "$count $unpushed" > "$summary_tmp/$repo.count"
|
|
131
|
+
) &
|
|
132
|
+
done
|
|
133
|
+
wait
|
|
134
|
+
|
|
135
|
+
# Aggregate results
|
|
136
|
+
for repo in $(get_repos); do
|
|
137
|
+
if [ -f "$summary_tmp/$repo.count" ]; then
|
|
138
|
+
read count unpushed < "$summary_tmp/$repo.count"
|
|
139
|
+
total_changes=$((total_changes + count))
|
|
140
|
+
total_unpushed=$((total_unpushed + unpushed))
|
|
141
|
+
fi
|
|
118
142
|
done
|
|
143
|
+
rm -rf "$summary_tmp"
|
|
119
144
|
fi
|
|
120
145
|
|
|
121
146
|
if [ "$total_changes" -eq 0 ] && [ "$total_unpushed" -eq 0 ]; then
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# Claude's next API call.
|
|
9
9
|
#
|
|
10
10
|
# Configuration files:
|
|
11
|
-
# .pennyfarthing/
|
|
11
|
+
# .pennyfarthing/config.local.yaml - workflow.bell_mode: true/false
|
|
12
12
|
# .pennyfarthing/bell-queue.json - [{ "text": "...", "images": [...] }, ...]
|
|
13
13
|
#
|
|
14
14
|
# Output format (when injecting):
|
|
@@ -35,15 +35,17 @@ if [[ ! -d "$PROJECT_ROOT/.pennyfarthing" ]]; then
|
|
|
35
35
|
exit 0
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
CONFIG_LOCAL_YAML="$PROJECT_ROOT/.pennyfarthing/config.local.yaml"
|
|
39
39
|
BELL_QUEUE_FILE="$PROJECT_ROOT/.pennyfarthing/bell-queue.json"
|
|
40
40
|
|
|
41
|
-
# Check if bell mode is enabled
|
|
42
|
-
if [[ ! -f "$
|
|
41
|
+
# Check if bell mode is enabled in config.local.yaml
|
|
42
|
+
if [[ ! -f "$CONFIG_LOCAL_YAML" ]]; then
|
|
43
43
|
exit 0
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
# Parse YAML to check workflow.bell_mode - look for "bell_mode: true"
|
|
47
|
+
# This handles both "bell_mode: true" and " bell_mode: true" (indented under workflow)
|
|
48
|
+
ENABLED=$(grep -E '^\s*bell_mode:\s*true' "$CONFIG_LOCAL_YAML" 2>/dev/null || true)
|
|
47
49
|
if [[ -z "$ENABLED" ]]; then
|
|
48
50
|
exit 0
|
|
49
51
|
fi
|
|
@@ -67,6 +69,13 @@ if [[ -z "$FIRST_MESSAGE_TEXT" ]]; then
|
|
|
67
69
|
exit 0
|
|
68
70
|
fi
|
|
69
71
|
|
|
72
|
+
# Get Cyclist port (if running)
|
|
73
|
+
CYCLIST_PORT=""
|
|
74
|
+
PORT_FILE="$PROJECT_ROOT/.cyclist-port"
|
|
75
|
+
if [[ -f "$PORT_FILE" ]]; then
|
|
76
|
+
CYCLIST_PORT=$(cat "$PORT_FILE" 2>/dev/null)
|
|
77
|
+
fi
|
|
78
|
+
|
|
70
79
|
# Output the hook response JSON
|
|
71
80
|
cat << EOF
|
|
72
81
|
{
|
|
@@ -77,11 +86,21 @@ cat << EOF
|
|
|
77
86
|
}
|
|
78
87
|
EOF
|
|
79
88
|
|
|
80
|
-
# Remove the first message from the queue
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
# Remove the first message from the queue and notify Cyclist
|
|
90
|
+
# Run in background to avoid blocking hook response
|
|
91
|
+
(
|
|
92
|
+
# Dequeue using jq if available
|
|
93
|
+
if command -v jq &> /dev/null; then
|
|
94
|
+
jq 'if length > 0 then .[1:] else [] end' "$BELL_QUEUE_FILE" > "$BELL_QUEUE_FILE.tmp" 2>/dev/null && mv "$BELL_QUEUE_FILE.tmp" "$BELL_QUEUE_FILE"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Notify Cyclist browser to dequeue and display the message
|
|
98
|
+
if [[ -n "$CYCLIST_PORT" ]] && [[ "$CYCLIST_PORT" =~ ^[0-9]+$ ]]; then
|
|
99
|
+
curl -s -X POST "http://localhost:$CYCLIST_PORT/api/bell-consumed" \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d "{\"text\": \"$FIRST_MESSAGE_TEXT\"}" \
|
|
102
|
+
>/dev/null 2>&1 || true
|
|
103
|
+
fi
|
|
104
|
+
) &
|
|
86
105
|
|
|
87
106
|
exit 0
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# pre-commit.sh - Git hook to enforce branch protection
|
|
2
|
+
# pre-commit.sh - Git hook to enforce branch protection and agent validation
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Checks:
|
|
5
|
+
# 1. Prevents direct commits to protected branches (main, develop)
|
|
6
|
+
# Exception: sprint/ folder commits allowed on develop
|
|
7
|
+
# 2. Validates agent files when pennyfarthing-dist/agents/*.md is modified
|
|
6
8
|
#
|
|
7
9
|
# Installation:
|
|
8
10
|
# Installed to .git/hooks/pre-commit by pennyfarthing init or doctor --fix
|
|
@@ -10,6 +12,19 @@
|
|
|
10
12
|
|
|
11
13
|
set -uo pipefail
|
|
12
14
|
|
|
15
|
+
# Find project root
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
# Handle both direct execution and symlink from .git/hooks
|
|
18
|
+
if [[ "$SCRIPT_DIR" == *".git/hooks"* ]]; then
|
|
19
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
20
|
+
else
|
|
21
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# Check 1: Branch Protection
|
|
26
|
+
# =============================================================================
|
|
27
|
+
|
|
13
28
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
14
29
|
PROTECTED_BRANCHES="^(main|develop)$"
|
|
15
30
|
|
|
@@ -21,30 +36,72 @@ if [[ $BRANCH =~ $PROTECTED_BRANCHES ]]; then
|
|
|
21
36
|
|
|
22
37
|
# If all staged files are in sprint/, allow the commit
|
|
23
38
|
if [ -z "$NON_SPRINT_FILES" ] && [ -n "$STAGED_FILES" ]; then
|
|
24
|
-
|
|
39
|
+
# Continue to agent validation check
|
|
40
|
+
:
|
|
41
|
+
else
|
|
42
|
+
echo ""
|
|
43
|
+
echo "COMMIT BLOCKED"
|
|
44
|
+
echo ""
|
|
45
|
+
echo "You are trying to commit directly to: $BRANCH"
|
|
46
|
+
echo "This violates the git workflow rules."
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Protected branches: main, develop"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "What to do:"
|
|
51
|
+
echo "1. Create a feature branch:"
|
|
52
|
+
echo " git checkout -b <type>/<epic-story>-<description>"
|
|
53
|
+
echo ""
|
|
54
|
+
echo "2. Example:"
|
|
55
|
+
echo " git checkout -b feat/8-2-add-authentication"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "3. Then commit your changes on the feature branch"
|
|
58
|
+
echo ""
|
|
59
|
+
echo "Exception: Sprint tracking files (sprint/*) can be committed to develop"
|
|
60
|
+
echo ""
|
|
61
|
+
exit 1
|
|
25
62
|
fi
|
|
63
|
+
else
|
|
64
|
+
echo ""
|
|
65
|
+
echo "COMMIT BLOCKED - Cannot commit directly to $BRANCH"
|
|
66
|
+
echo ""
|
|
67
|
+
exit 1
|
|
26
68
|
fi
|
|
69
|
+
fi
|
|
27
70
|
|
|
71
|
+
# =============================================================================
|
|
72
|
+
# Check 2: Agent File Validation
|
|
73
|
+
# =============================================================================
|
|
74
|
+
|
|
75
|
+
STAGED_FILES=$(git diff --cached --name-only 2>/dev/null || true)
|
|
76
|
+
AGENT_FILES=$(echo "$STAGED_FILES" | grep "^pennyfarthing-dist/agents/.*\.md$" || true)
|
|
77
|
+
|
|
78
|
+
if [[ -n "$AGENT_FILES" ]]; then
|
|
79
|
+
echo "Agent files staged for commit:"
|
|
80
|
+
echo "$AGENT_FILES" | sed 's/^/ /'
|
|
28
81
|
echo ""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
82
|
+
|
|
83
|
+
VALIDATOR="$PROJECT_ROOT/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh"
|
|
84
|
+
|
|
85
|
+
if [[ -x "$VALIDATOR" ]]; then
|
|
86
|
+
echo "Running agent schema validation..."
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
if ! "$VALIDATOR"; then
|
|
90
|
+
echo ""
|
|
91
|
+
echo "COMMIT BLOCKED - Agent validation failed"
|
|
92
|
+
echo ""
|
|
93
|
+
echo "Fix the validation errors above and try again."
|
|
94
|
+
echo "Run 'just validate-agents -v' for detailed output."
|
|
95
|
+
echo ""
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
echo ""
|
|
100
|
+
echo "✓ Agent validation passed"
|
|
101
|
+
else
|
|
102
|
+
echo "Warning: Agent validator not found at $VALIDATOR"
|
|
103
|
+
echo "Skipping agent validation."
|
|
104
|
+
fi
|
|
48
105
|
fi
|
|
49
106
|
|
|
50
107
|
exit 0
|