@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
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
# validate-agent-schema.sh
|
|
3
|
+
# Validates agent files against schema and best practices
|
|
4
|
+
#
|
|
5
|
+
# Schema Rules:
|
|
6
|
+
# - Primary agents: Must have required XML tags, everything in tags
|
|
7
|
+
# - Subagents: Must have YAML frontmatter (validated separately)
|
|
8
|
+
#
|
|
9
|
+
# Best Practice Rules:
|
|
10
|
+
# - First <critical> within line 30
|
|
11
|
+
# - <on-activation> within line 100
|
|
12
|
+
# - File size under 300 lines
|
|
13
|
+
# - All XML tags properly closed
|
|
14
|
+
# - Checklists use proper markdown format
|
|
15
|
+
#
|
|
16
|
+
# Exit codes:
|
|
17
|
+
# 0 = All agents valid
|
|
18
|
+
# 1 = One or more agents invalid
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# Find project root
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
24
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
25
|
+
AGENTS_DIR="$PROJECT_ROOT/pennyfarthing-dist/agents"
|
|
26
|
+
|
|
27
|
+
# Colors
|
|
28
|
+
RED='\033[0;31m'
|
|
29
|
+
GREEN='\033[0;32m'
|
|
30
|
+
YELLOW='\033[0;33m'
|
|
31
|
+
BLUE='\033[0;34m'
|
|
32
|
+
NC='\033[0m'
|
|
33
|
+
|
|
34
|
+
# Primary agents (not subagents)
|
|
35
|
+
PRIMARY_AGENTS=(
|
|
36
|
+
"sm.md"
|
|
37
|
+
"tea.md"
|
|
38
|
+
"dev.md"
|
|
39
|
+
"reviewer.md"
|
|
40
|
+
"orchestrator.md"
|
|
41
|
+
"architect.md"
|
|
42
|
+
"pm.md"
|
|
43
|
+
"devops.md"
|
|
44
|
+
"tech-writer.md"
|
|
45
|
+
"ux-designer.md"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Subagents (have YAML frontmatter, different schema)
|
|
49
|
+
SUBAGENTS=(
|
|
50
|
+
"handoff.md"
|
|
51
|
+
"sm-setup.md"
|
|
52
|
+
"sm-finish.md"
|
|
53
|
+
"sm-file-summary.md"
|
|
54
|
+
"sm-handoff.md"
|
|
55
|
+
"workflow-status-check.md"
|
|
56
|
+
"testing-runner.md"
|
|
57
|
+
"reviewer-preflight.md"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Required tags for primary agents
|
|
61
|
+
# Note: persona is emitted by agent-session.sh, not in agent files
|
|
62
|
+
REQUIRED_TAGS=(
|
|
63
|
+
"role"
|
|
64
|
+
"helpers"
|
|
65
|
+
"exit"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Optional but recommended tags
|
|
69
|
+
RECOMMENDED_TAGS=(
|
|
70
|
+
"critical"
|
|
71
|
+
"on-activation"
|
|
72
|
+
"skills"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Tags that require checklists
|
|
76
|
+
CHECKLIST_TAGS=(
|
|
77
|
+
"gate"
|
|
78
|
+
"handoff-gate"
|
|
79
|
+
"self-review"
|
|
80
|
+
"review-checklist"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Mindset tags - each primary agent should have one
|
|
84
|
+
# Format: agent_file:expected_tag
|
|
85
|
+
declare -A MINDSET_TAGS=(
|
|
86
|
+
["sm.md"]="coordination-discipline"
|
|
87
|
+
["tea.md"]="test-paranoia"
|
|
88
|
+
["dev.md"]="minimalist-discipline"
|
|
89
|
+
["reviewer.md"]="adversarial-mindset"
|
|
90
|
+
["orchestrator.md"]="systems-thinking"
|
|
91
|
+
["architect.md"]="pragmatic-restraint"
|
|
92
|
+
["pm.md"]="ruthless-prioritization"
|
|
93
|
+
["devops.md"]="automation-discipline"
|
|
94
|
+
["tech-writer.md"]="clarity-obsession"
|
|
95
|
+
["ux-designer.md"]="consistency-guardian"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Best practice thresholds
|
|
99
|
+
MAX_LINES=300
|
|
100
|
+
FIRST_CRITICAL_MAX_LINE=30
|
|
101
|
+
ON_ACTIVATION_MAX_LINE=100
|
|
102
|
+
|
|
103
|
+
# Counters
|
|
104
|
+
valid_count=0
|
|
105
|
+
warning_count=0
|
|
106
|
+
invalid_count=0
|
|
107
|
+
|
|
108
|
+
# Verbose mode
|
|
109
|
+
VERBOSE=false
|
|
110
|
+
FIX_MODE=false
|
|
111
|
+
|
|
112
|
+
# Parse arguments
|
|
113
|
+
while [[ $# -gt 0 ]]; do
|
|
114
|
+
case $1 in
|
|
115
|
+
-v|--verbose) VERBOSE=true; shift ;;
|
|
116
|
+
--fix) FIX_MODE=true; shift ;;
|
|
117
|
+
-h|--help)
|
|
118
|
+
echo "Usage: validate-agent-schema.sh [-v|--verbose] [--fix]"
|
|
119
|
+
echo " -v, --verbose Show detailed validation info"
|
|
120
|
+
echo " --fix Attempt to fix simple issues (not implemented)"
|
|
121
|
+
exit 0
|
|
122
|
+
;;
|
|
123
|
+
*) shift ;;
|
|
124
|
+
esac
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
# =============================================================================
|
|
128
|
+
# Validation Functions
|
|
129
|
+
# =============================================================================
|
|
130
|
+
|
|
131
|
+
check_xml_tags_balanced() {
|
|
132
|
+
local file="$1"
|
|
133
|
+
local errors=()
|
|
134
|
+
|
|
135
|
+
# Find all opening tags
|
|
136
|
+
local open_tags=($(grep -oE '<[a-z][-a-z]*>' "$file" 2>/dev/null | sed 's/[<>]//g' | sort -u))
|
|
137
|
+
|
|
138
|
+
for tag in "${open_tags[@]}"; do
|
|
139
|
+
local open_count=$(grep -c "<${tag}>" "$file" 2>/dev/null || echo 0)
|
|
140
|
+
local close_count=$(grep -c "</${tag}>" "$file" 2>/dev/null || echo 0)
|
|
141
|
+
|
|
142
|
+
if [[ $open_count -ne $close_count ]]; then
|
|
143
|
+
errors+=("Tag <$tag> has $open_count opens but $close_count closes")
|
|
144
|
+
fi
|
|
145
|
+
done
|
|
146
|
+
|
|
147
|
+
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
148
|
+
for error in "${errors[@]}"; do
|
|
149
|
+
echo " ${YELLOW}→${NC} $error"
|
|
150
|
+
done
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
return 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
check_required_tags() {
|
|
157
|
+
local file="$1"
|
|
158
|
+
local missing=()
|
|
159
|
+
|
|
160
|
+
for tag in "${REQUIRED_TAGS[@]}"; do
|
|
161
|
+
if ! grep -q "<${tag}>" "$file" 2>/dev/null; then
|
|
162
|
+
missing+=("$tag")
|
|
163
|
+
fi
|
|
164
|
+
done
|
|
165
|
+
|
|
166
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
167
|
+
echo " ${RED}→${NC} Missing required tags: ${missing[*]}"
|
|
168
|
+
return 1
|
|
169
|
+
fi
|
|
170
|
+
return 0
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
check_recommended_tags() {
|
|
174
|
+
local file="$1"
|
|
175
|
+
local missing=()
|
|
176
|
+
|
|
177
|
+
for tag in "${RECOMMENDED_TAGS[@]}"; do
|
|
178
|
+
if ! grep -q "<${tag}>" "$file" 2>/dev/null; then
|
|
179
|
+
missing+=("$tag")
|
|
180
|
+
fi
|
|
181
|
+
done
|
|
182
|
+
|
|
183
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
184
|
+
echo " ${YELLOW}→${NC} Missing recommended tags: ${missing[*]}"
|
|
185
|
+
return 1 # Warning, not error
|
|
186
|
+
fi
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
check_checklist_format() {
|
|
191
|
+
local file="$1"
|
|
192
|
+
local errors=()
|
|
193
|
+
|
|
194
|
+
for tag in "${CHECKLIST_TAGS[@]}"; do
|
|
195
|
+
if grep -q "<${tag}>" "$file" 2>/dev/null; then
|
|
196
|
+
# Extract content between tags
|
|
197
|
+
local content=$(sed -n "/<${tag}>/,/<\/${tag}>/p" "$file" 2>/dev/null)
|
|
198
|
+
|
|
199
|
+
# Check for checklist items
|
|
200
|
+
if echo "$content" | grep -qE '^\s*-\s*\['; then
|
|
201
|
+
# Validate checklist format: - [ ] or - [x]
|
|
202
|
+
local bad_format=$(echo "$content" | grep -E '^\s*-\s*\[' | grep -vE '^\s*-\s*\[\s*[x ]?\s*\]' || true)
|
|
203
|
+
if [[ -n "$bad_format" ]]; then
|
|
204
|
+
errors+=("Tag <$tag> has malformed checklist items")
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
fi
|
|
208
|
+
done
|
|
209
|
+
|
|
210
|
+
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
211
|
+
for error in "${errors[@]}"; do
|
|
212
|
+
echo " ${YELLOW}→${NC} $error"
|
|
213
|
+
done
|
|
214
|
+
return 1
|
|
215
|
+
fi
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
check_best_practices() {
|
|
220
|
+
local file="$1"
|
|
221
|
+
local filename=$(basename "$file")
|
|
222
|
+
local warnings=()
|
|
223
|
+
local errors=()
|
|
224
|
+
|
|
225
|
+
# Check file length
|
|
226
|
+
local line_count=$(wc -l < "$file" | tr -d ' ')
|
|
227
|
+
if [[ $line_count -gt $MAX_LINES ]]; then
|
|
228
|
+
errors+=("File has $line_count lines (max: $MAX_LINES)")
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Check first <critical> position
|
|
232
|
+
local first_critical=$(grep -n '<critical>' "$file" 2>/dev/null | head -1 | cut -d: -f1)
|
|
233
|
+
if [[ -n "$first_critical" && $first_critical -gt $FIRST_CRITICAL_MAX_LINE ]]; then
|
|
234
|
+
warnings+=("First <critical> at line $first_critical (target: ≤$FIRST_CRITICAL_MAX_LINE)")
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# Check <on-activation> position
|
|
238
|
+
local on_activation=$(grep -n '<on-activation>' "$file" 2>/dev/null | head -1 | cut -d: -f1)
|
|
239
|
+
if [[ -n "$on_activation" && $on_activation -gt $ON_ACTIVATION_MAX_LINE ]]; then
|
|
240
|
+
warnings+=("<on-activation> at line $on_activation (target: ≤$ON_ACTIVATION_MAX_LINE)")
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# Check for content outside XML tags (after header)
|
|
244
|
+
# Allow: # Header, blank lines, ## sections inside flows
|
|
245
|
+
# This is a soft check - some content outside tags is OK
|
|
246
|
+
|
|
247
|
+
# Report
|
|
248
|
+
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
249
|
+
for error in "${errors[@]}"; do
|
|
250
|
+
echo " ${RED}→${NC} $error"
|
|
251
|
+
done
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
if [[ ${#warnings[@]} -gt 0 ]]; then
|
|
255
|
+
for warning in "${warnings[@]}"; do
|
|
256
|
+
echo " ${YELLOW}→${NC} $warning"
|
|
257
|
+
done
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
[[ ${#errors[@]} -eq 0 ]]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
check_header_format() {
|
|
264
|
+
local file="$1"
|
|
265
|
+
local filename=$(basename "$file" .md)
|
|
266
|
+
|
|
267
|
+
# Check for proper header: # AgentName Agent - Title
|
|
268
|
+
local header=$(head -1 "$file")
|
|
269
|
+
if ! echo "$header" | grep -qE '^# .+ Agent'; then
|
|
270
|
+
echo " ${YELLOW}→${NC} Header should be '# Name Agent - Description'"
|
|
271
|
+
return 1
|
|
272
|
+
fi
|
|
273
|
+
return 0
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
check_no_orphan_content() {
|
|
277
|
+
local file="$1"
|
|
278
|
+
local errors=()
|
|
279
|
+
|
|
280
|
+
# Content after last closing tag is an error (except whitespace)
|
|
281
|
+
local last_close_line=$(grep -n '</[a-z][-a-z]*>' "$file" 2>/dev/null | tail -1 | cut -d: -f1)
|
|
282
|
+
local total_lines=$(wc -l < "$file" | tr -d ' ')
|
|
283
|
+
|
|
284
|
+
if [[ -n "$last_close_line" ]]; then
|
|
285
|
+
# Check if there's non-whitespace content after last tag
|
|
286
|
+
local remaining=$(tail -n +$((last_close_line + 1)) "$file" | grep -v '^[[:space:]]*$' || true)
|
|
287
|
+
if [[ -n "$remaining" ]]; then
|
|
288
|
+
errors+=("Content found after last closing tag (line $last_close_line)")
|
|
289
|
+
fi
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
293
|
+
for error in "${errors[@]}"; do
|
|
294
|
+
echo " ${YELLOW}→${NC} $error"
|
|
295
|
+
done
|
|
296
|
+
return 1
|
|
297
|
+
fi
|
|
298
|
+
return 0
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
check_mindset_tag() {
|
|
302
|
+
local file="$1"
|
|
303
|
+
local filename=$(basename "$file")
|
|
304
|
+
|
|
305
|
+
# Get expected mindset tag for this agent
|
|
306
|
+
local expected_tag="${MINDSET_TAGS[$filename]:-}"
|
|
307
|
+
|
|
308
|
+
if [[ -z "$expected_tag" ]]; then
|
|
309
|
+
# No mindset tag expected for this agent
|
|
310
|
+
return 0
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
if ! grep -q "<${expected_tag}>" "$file" 2>/dev/null; then
|
|
314
|
+
echo " ${RED}→${NC} Missing mindset tag: <$expected_tag>"
|
|
315
|
+
return 1
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
# Verify it's closed
|
|
319
|
+
if ! grep -q "</${expected_tag}>" "$file" 2>/dev/null; then
|
|
320
|
+
echo " ${RED}→${NC} Unclosed mindset tag: <$expected_tag>"
|
|
321
|
+
return 1
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
return 0
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
check_parameters_section() {
|
|
328
|
+
local file="$1"
|
|
329
|
+
|
|
330
|
+
# If file has <helpers> tag, it should also have <parameters>
|
|
331
|
+
if grep -q "<helpers>" "$file" 2>/dev/null; then
|
|
332
|
+
if ! grep -q "<parameters>" "$file" 2>/dev/null; then
|
|
333
|
+
echo " ${YELLOW}→${NC} Has <helpers> but missing <parameters> section"
|
|
334
|
+
return 1
|
|
335
|
+
fi
|
|
336
|
+
fi
|
|
337
|
+
return 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
check_arguments_section() {
|
|
341
|
+
local file="$1"
|
|
342
|
+
|
|
343
|
+
# Subagents should have <arguments> section
|
|
344
|
+
if ! grep -q "<arguments>" "$file" 2>/dev/null; then
|
|
345
|
+
echo " ${YELLOW}→${NC} Missing <arguments> section"
|
|
346
|
+
return 1
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
# Verify it's closed
|
|
350
|
+
if ! grep -q "</arguments>" "$file" 2>/dev/null; then
|
|
351
|
+
echo " ${RED}→${NC} Unclosed <arguments> tag"
|
|
352
|
+
return 1
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
return 0
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
check_all_content_in_tags() {
|
|
359
|
+
local file="$1"
|
|
360
|
+
local orphan_lines=()
|
|
361
|
+
local in_tag=0
|
|
362
|
+
local line_num=0
|
|
363
|
+
|
|
364
|
+
while IFS= read -r line; do
|
|
365
|
+
((line_num++))
|
|
366
|
+
|
|
367
|
+
# Skip header line (# Agent Name)
|
|
368
|
+
[[ $line_num -eq 1 && $line =~ ^#\ ]] && continue
|
|
369
|
+
|
|
370
|
+
# Skip blank lines
|
|
371
|
+
[[ -z "${line// /}" ]] && continue
|
|
372
|
+
|
|
373
|
+
# Count opening and closing tags on this line
|
|
374
|
+
local opens=$(echo "$line" | grep -oE '<[a-z][-a-z]*>' | wc -l | tr -d ' ')
|
|
375
|
+
local closes=$(echo "$line" | grep -oE '</[a-z][-a-z]*>' | wc -l | tr -d ' ')
|
|
376
|
+
|
|
377
|
+
# If at depth 0 and line doesn't contain a tag, it's orphaned
|
|
378
|
+
if [[ $in_tag -eq 0 && $opens -eq 0 ]]; then
|
|
379
|
+
orphan_lines+=("$line_num")
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
in_tag=$((in_tag + opens - closes))
|
|
383
|
+
done < "$file"
|
|
384
|
+
|
|
385
|
+
if [[ ${#orphan_lines[@]} -gt 0 ]]; then
|
|
386
|
+
local first_few="${orphan_lines[*]:0:5}"
|
|
387
|
+
echo " ${RED}→${NC} Content outside XML tags at lines: $first_few..."
|
|
388
|
+
return 1
|
|
389
|
+
fi
|
|
390
|
+
return 0
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# =============================================================================
|
|
394
|
+
# Main Validation
|
|
395
|
+
# =============================================================================
|
|
396
|
+
|
|
397
|
+
validate_primary_agent() {
|
|
398
|
+
local file="$1"
|
|
399
|
+
local filename=$(basename "$file")
|
|
400
|
+
local has_error=false
|
|
401
|
+
local has_warning=false
|
|
402
|
+
|
|
403
|
+
if [[ "$VERBOSE" == "true" ]]; then
|
|
404
|
+
echo ""
|
|
405
|
+
echo -e "${BLUE}Validating:${NC} $filename"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
# Required checks (errors)
|
|
409
|
+
if ! check_xml_tags_balanced "$file"; then
|
|
410
|
+
has_error=true
|
|
411
|
+
fi
|
|
412
|
+
|
|
413
|
+
if ! check_required_tags "$file"; then
|
|
414
|
+
has_error=true
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
# Best practice checks (can be warnings or errors)
|
|
418
|
+
if ! check_best_practices "$file"; then
|
|
419
|
+
has_error=true
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
# All content must be within XML tags (required)
|
|
423
|
+
if ! check_all_content_in_tags "$file"; then
|
|
424
|
+
has_error=true
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
# Mindset tag check (required)
|
|
428
|
+
if ! check_mindset_tag "$file"; then
|
|
429
|
+
has_error=true
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
# Parameters section check (warning if helpers present)
|
|
433
|
+
if ! check_parameters_section "$file"; then
|
|
434
|
+
has_warning=true
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
# Warning checks
|
|
438
|
+
if ! check_recommended_tags "$file"; then
|
|
439
|
+
has_warning=true
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
if ! check_checklist_format "$file"; then
|
|
443
|
+
has_warning=true
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if ! check_header_format "$file"; then
|
|
447
|
+
has_warning=true
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
# Report result
|
|
451
|
+
if [[ "$has_error" == "true" ]]; then
|
|
452
|
+
echo -e "${RED}✗ INVALID:${NC} $filename"
|
|
453
|
+
((invalid_count++))
|
|
454
|
+
return 1
|
|
455
|
+
elif [[ "$has_warning" == "true" ]]; then
|
|
456
|
+
echo -e "${YELLOW}⚠ WARNING:${NC} $filename"
|
|
457
|
+
((warning_count++))
|
|
458
|
+
return 0
|
|
459
|
+
else
|
|
460
|
+
echo -e "${GREEN}✓ VALID:${NC} $filename"
|
|
461
|
+
((valid_count++))
|
|
462
|
+
return 0
|
|
463
|
+
fi
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
validate_subagent() {
|
|
467
|
+
local file="$1"
|
|
468
|
+
local filename=$(basename "$file")
|
|
469
|
+
|
|
470
|
+
# Check for YAML frontmatter
|
|
471
|
+
local first_line=$(head -1 "$file")
|
|
472
|
+
if [[ "$first_line" != "---" ]]; then
|
|
473
|
+
echo -e "${RED}✗ INVALID:${NC} $filename (missing YAML frontmatter)"
|
|
474
|
+
((invalid_count++))
|
|
475
|
+
return 1
|
|
476
|
+
fi
|
|
477
|
+
|
|
478
|
+
# Check for closing ---
|
|
479
|
+
local frontmatter_end=$(awk '/^---$/{count++; if(count==2) print NR}' "$file")
|
|
480
|
+
if [[ -z "$frontmatter_end" ]]; then
|
|
481
|
+
echo -e "${RED}✗ INVALID:${NC} $filename (unclosed YAML frontmatter)"
|
|
482
|
+
((invalid_count++))
|
|
483
|
+
return 1
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
# Check required frontmatter fields
|
|
487
|
+
local frontmatter=$(sed -n "2,$((frontmatter_end - 1))p" "$file")
|
|
488
|
+
local missing=()
|
|
489
|
+
|
|
490
|
+
for field in name description tools model; do
|
|
491
|
+
if ! echo "$frontmatter" | grep -q "^${field}:"; then
|
|
492
|
+
missing+=("$field")
|
|
493
|
+
fi
|
|
494
|
+
done
|
|
495
|
+
|
|
496
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
497
|
+
echo -e "${RED}✗ INVALID:${NC} $filename (missing: ${missing[*]})"
|
|
498
|
+
((invalid_count++))
|
|
499
|
+
return 1
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
# Check XML tags are balanced
|
|
503
|
+
if ! check_xml_tags_balanced "$file"; then
|
|
504
|
+
echo -e "${RED}✗ INVALID:${NC} $filename"
|
|
505
|
+
((invalid_count++))
|
|
506
|
+
return 1
|
|
507
|
+
fi
|
|
508
|
+
|
|
509
|
+
# Check for <arguments> section
|
|
510
|
+
if ! check_arguments_section "$file"; then
|
|
511
|
+
echo -e "${YELLOW}⚠ WARNING:${NC} $filename (subagent)"
|
|
512
|
+
((warning_count++))
|
|
513
|
+
return 0
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
echo -e "${GREEN}✓ VALID:${NC} $filename (subagent)"
|
|
517
|
+
((valid_count++))
|
|
518
|
+
return 0
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
# =============================================================================
|
|
522
|
+
# Run Validation
|
|
523
|
+
# =============================================================================
|
|
524
|
+
|
|
525
|
+
echo "Agent Schema Validator"
|
|
526
|
+
echo "======================"
|
|
527
|
+
echo "Directory: $AGENTS_DIR"
|
|
528
|
+
echo ""
|
|
529
|
+
|
|
530
|
+
echo -e "${BLUE}Primary Agents:${NC}"
|
|
531
|
+
for agent in "${PRIMARY_AGENTS[@]}"; do
|
|
532
|
+
if [[ -f "$AGENTS_DIR/$agent" ]]; then
|
|
533
|
+
validate_primary_agent "$AGENTS_DIR/$agent" || true
|
|
534
|
+
else
|
|
535
|
+
echo -e "${YELLOW}⚠ MISSING:${NC} $agent"
|
|
536
|
+
((warning_count++))
|
|
537
|
+
fi
|
|
538
|
+
done
|
|
539
|
+
|
|
540
|
+
echo ""
|
|
541
|
+
echo -e "${BLUE}Subagents:${NC}"
|
|
542
|
+
for subagent in "${SUBAGENTS[@]}"; do
|
|
543
|
+
if [[ -f "$AGENTS_DIR/$subagent" ]]; then
|
|
544
|
+
validate_subagent "$AGENTS_DIR/$subagent" || true
|
|
545
|
+
else
|
|
546
|
+
echo -e "${YELLOW}⚠ MISSING:${NC} $subagent"
|
|
547
|
+
((warning_count++))
|
|
548
|
+
fi
|
|
549
|
+
done
|
|
550
|
+
|
|
551
|
+
# =============================================================================
|
|
552
|
+
# Summary
|
|
553
|
+
# =============================================================================
|
|
554
|
+
|
|
555
|
+
echo ""
|
|
556
|
+
echo "====================================="
|
|
557
|
+
echo "Validation Summary"
|
|
558
|
+
echo "====================================="
|
|
559
|
+
total=$((${#PRIMARY_AGENTS[@]} + ${#SUBAGENTS[@]}))
|
|
560
|
+
echo -e "Valid: ${GREEN}$valid_count${NC} / $total"
|
|
561
|
+
echo -e "Warnings: ${YELLOW}$warning_count${NC}"
|
|
562
|
+
echo -e "Invalid: ${RED}$invalid_count${NC}"
|
|
563
|
+
echo ""
|
|
564
|
+
|
|
565
|
+
if [[ $invalid_count -eq 0 ]]; then
|
|
566
|
+
if [[ $warning_count -eq 0 ]]; then
|
|
567
|
+
echo -e "${GREEN}All agents pass schema validation!${NC}"
|
|
568
|
+
else
|
|
569
|
+
echo -e "${YELLOW}All agents valid with warnings.${NC}"
|
|
570
|
+
fi
|
|
571
|
+
exit 0
|
|
572
|
+
else
|
|
573
|
+
echo -e "${RED}$invalid_count agent(s) failed validation.${NC}"
|
|
574
|
+
exit 1
|
|
575
|
+
fi
|