agileflow 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/skill-loader.js +0 -1
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +81 -0
- package/scripts/agileflow-welcome.js +79 -0
- package/scripts/claude-tmux.sh +90 -23
- package/scripts/claude-watchdog.sh +225 -0
- package/scripts/generators/agent-registry.js +14 -1
- package/scripts/generators/inject-babysit.js +22 -9
- package/scripts/generators/inject-help.js +19 -9
- package/scripts/lib/ac-test-matcher.js +452 -0
- package/scripts/lib/audit-cleanup.js +250 -0
- package/scripts/lib/audit-registry.js +304 -0
- package/scripts/lib/configure-features.js +35 -0
- package/scripts/lib/feature-catalog.js +3 -3
- package/scripts/lib/gate-enforcer.js +295 -0
- package/scripts/lib/model-profiles.js +118 -0
- package/scripts/lib/quality-gates.js +163 -0
- package/scripts/lib/signal-detectors.js +44 -1
- package/scripts/lib/skill-catalog.js +557 -0
- package/scripts/lib/skill-recommender.js +311 -0
- package/scripts/lib/status-writer.js +255 -0
- package/scripts/lib/story-claiming.js +128 -45
- package/scripts/lib/task-sync.js +32 -38
- package/scripts/lib/tdd-phase-manager.js +455 -0
- package/scripts/lib/team-events.js +34 -3
- package/scripts/lib/tmux-audit-monitor.js +611 -0
- package/scripts/lib/tmux-group-colors.js +113 -0
- package/scripts/lib/tool-registry.yaml +241 -0
- package/scripts/lib/tool-shed.js +441 -0
- package/scripts/messaging-bridge.js +209 -1
- package/scripts/native-team-observer.js +219 -0
- package/scripts/obtain-context.js +14 -0
- package/scripts/ralph-loop.js +30 -5
- package/scripts/smart-detect.js +21 -0
- package/scripts/spawn-audit-sessions.js +877 -0
- package/scripts/team-manager.js +56 -16
- package/scripts/tmux-close-windows.sh +180 -0
- package/src/core/agents/a11y-analyzer-aria.md +155 -0
- package/src/core/agents/a11y-analyzer-forms.md +162 -0
- package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
- package/src/core/agents/a11y-analyzer-semantic.md +153 -0
- package/src/core/agents/a11y-analyzer-visual.md +158 -0
- package/src/core/agents/a11y-consensus.md +248 -0
- package/src/core/agents/ads-audit-budget.md +181 -0
- package/src/core/agents/ads-audit-compliance.md +169 -0
- package/src/core/agents/ads-audit-creative.md +164 -0
- package/src/core/agents/ads-audit-google.md +226 -0
- package/src/core/agents/ads-audit-meta.md +183 -0
- package/src/core/agents/ads-audit-tracking.md +197 -0
- package/src/core/agents/ads-consensus.md +396 -0
- package/src/core/agents/ads-generate.md +145 -0
- package/src/core/agents/ads-performance-tracker.md +197 -0
- package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
- package/src/core/agents/api-quality-analyzer-docs.md +176 -0
- package/src/core/agents/api-quality-analyzer-errors.md +183 -0
- package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
- package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
- package/src/core/agents/api-quality-consensus.md +214 -0
- package/src/core/agents/arch-analyzer-circular.md +148 -0
- package/src/core/agents/arch-analyzer-complexity.md +171 -0
- package/src/core/agents/arch-analyzer-coupling.md +146 -0
- package/src/core/agents/arch-analyzer-layering.md +151 -0
- package/src/core/agents/arch-analyzer-patterns.md +162 -0
- package/src/core/agents/arch-consensus.md +227 -0
- package/src/core/agents/brainstorm-analyzer-features.md +169 -0
- package/src/core/agents/brainstorm-analyzer-growth.md +161 -0
- package/src/core/agents/brainstorm-analyzer-integration.md +172 -0
- package/src/core/agents/brainstorm-analyzer-market.md +147 -0
- package/src/core/agents/brainstorm-analyzer-ux.md +167 -0
- package/src/core/agents/brainstorm-consensus.md +237 -0
- package/src/core/agents/completeness-consensus.md +5 -5
- package/src/core/agents/perf-consensus.md +2 -2
- package/src/core/agents/security-consensus.md +2 -2
- package/src/core/agents/seo-analyzer-content.md +167 -0
- package/src/core/agents/seo-analyzer-images.md +187 -0
- package/src/core/agents/seo-analyzer-performance.md +206 -0
- package/src/core/agents/seo-analyzer-schema.md +176 -0
- package/src/core/agents/seo-analyzer-sitemap.md +172 -0
- package/src/core/agents/seo-analyzer-technical.md +144 -0
- package/src/core/agents/seo-consensus.md +289 -0
- package/src/core/agents/test-consensus.md +2 -2
- package/src/core/commands/adr.md +1 -0
- package/src/core/commands/ads/audit.md +375 -0
- package/src/core/commands/ads/budget.md +97 -0
- package/src/core/commands/ads/competitor.md +112 -0
- package/src/core/commands/ads/creative.md +85 -0
- package/src/core/commands/ads/generate.md +238 -0
- package/src/core/commands/ads/google.md +112 -0
- package/src/core/commands/ads/health.md +327 -0
- package/src/core/commands/ads/landing.md +119 -0
- package/src/core/commands/ads/linkedin.md +112 -0
- package/src/core/commands/ads/meta.md +91 -0
- package/src/core/commands/ads/microsoft.md +115 -0
- package/src/core/commands/ads/plan.md +321 -0
- package/src/core/commands/ads/test-plan.md +317 -0
- package/src/core/commands/ads/tiktok.md +129 -0
- package/src/core/commands/ads/track.md +288 -0
- package/src/core/commands/ads/youtube.md +124 -0
- package/src/core/commands/ads.md +140 -0
- package/src/core/commands/assign.md +1 -0
- package/src/core/commands/audit.md +43 -6
- package/src/core/commands/babysit.md +315 -1266
- package/src/core/commands/baseline.md +1 -0
- package/src/core/commands/blockers.md +1 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +1 -0
- package/src/core/commands/choose.md +1 -0
- package/src/core/commands/ci.md +1 -0
- package/src/core/commands/code/accessibility.md +347 -0
- package/src/core/commands/code/api.md +297 -0
- package/src/core/commands/code/architecture.md +297 -0
- package/src/core/commands/{audit → code}/completeness.md +72 -25
- package/src/core/commands/{audit → code}/legal.md +63 -16
- package/src/core/commands/{audit → code}/logic.md +64 -16
- package/src/core/commands/{audit → code}/performance.md +67 -20
- package/src/core/commands/{audit → code}/security.md +69 -19
- package/src/core/commands/{audit → code}/test.md +67 -20
- package/src/core/commands/configure.md +1 -0
- package/src/core/commands/council.md +1 -0
- package/src/core/commands/deploy.md +1 -0
- package/src/core/commands/diagnose.md +1 -0
- package/src/core/commands/docs.md +1 -0
- package/src/core/commands/epic/edit.md +213 -0
- package/src/core/commands/epic.md +1 -0
- package/src/core/commands/export.md +238 -0
- package/src/core/commands/help.md +16 -1
- package/src/core/commands/{discovery → ideate}/brief.md +12 -12
- package/src/core/commands/{discovery/new.md → ideate/discover.md} +20 -16
- package/src/core/commands/ideate/features.md +496 -0
- package/src/core/commands/ideate/new.md +158 -124
- package/src/core/commands/impact.md +1 -0
- package/src/core/commands/learn/explain.md +118 -0
- package/src/core/commands/learn/glossary.md +135 -0
- package/src/core/commands/learn/patterns.md +138 -0
- package/src/core/commands/learn/tour.md +126 -0
- package/src/core/commands/migrate/codemods.md +151 -0
- package/src/core/commands/migrate/plan.md +131 -0
- package/src/core/commands/migrate/scan.md +114 -0
- package/src/core/commands/migrate/validate.md +119 -0
- package/src/core/commands/multi-expert.md +1 -0
- package/src/core/commands/pr.md +1 -0
- package/src/core/commands/review.md +1 -0
- package/src/core/commands/seo/audit.md +373 -0
- package/src/core/commands/seo/competitor.md +174 -0
- package/src/core/commands/seo/content.md +107 -0
- package/src/core/commands/seo/geo.md +229 -0
- package/src/core/commands/seo/hreflang.md +140 -0
- package/src/core/commands/seo/images.md +96 -0
- package/src/core/commands/seo/page.md +198 -0
- package/src/core/commands/seo/plan.md +163 -0
- package/src/core/commands/seo/programmatic.md +131 -0
- package/src/core/commands/seo/references/cwv-thresholds.md +64 -0
- package/src/core/commands/seo/references/eeat-framework.md +110 -0
- package/src/core/commands/seo/references/quality-gates.md +91 -0
- package/src/core/commands/seo/references/schema-types.md +102 -0
- package/src/core/commands/seo/schema.md +183 -0
- package/src/core/commands/seo/sitemap.md +97 -0
- package/src/core/commands/seo/technical.md +100 -0
- package/src/core/commands/seo.md +107 -0
- package/src/core/commands/skill/list.md +68 -212
- package/src/core/commands/skill/recommend.md +216 -0
- package/src/core/commands/sprint.md +1 -0
- package/src/core/commands/status/undo.md +191 -0
- package/src/core/commands/status.md +1 -0
- package/src/core/commands/story/edit.md +204 -0
- package/src/core/commands/story/view.md +29 -7
- package/src/core/commands/story-validate.md +1 -0
- package/src/core/commands/story.md +1 -0
- package/src/core/commands/tdd-next.md +238 -0
- package/src/core/commands/tdd.md +211 -0
- package/src/core/commands/team/start.md +10 -6
- package/src/core/commands/tests.md +1 -0
- package/src/core/commands/verify.md +27 -1
- package/src/core/commands/workflow.md +2 -0
- package/src/core/experts/_core-expertise.yaml +105 -0
- package/src/core/experts/analytics/expertise.yaml +5 -99
- package/src/core/experts/codebase-query/expertise.yaml +3 -72
- package/src/core/experts/compliance/expertise.yaml +6 -72
- package/src/core/experts/database/expertise.yaml +9 -52
- package/src/core/experts/documentation/expertise.yaml +7 -140
- package/src/core/experts/integrations/expertise.yaml +7 -127
- package/src/core/experts/mentor/expertise.yaml +8 -35
- package/src/core/experts/monitoring/expertise.yaml +7 -49
- package/src/core/experts/performance/expertise.yaml +1 -26
- package/src/core/experts/security/expertise.yaml +9 -34
- package/src/core/experts/ui/expertise.yaml +6 -36
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +424 -0
- package/src/core/knowledge/ads/ad-optimization-logic.md +590 -0
- package/src/core/knowledge/ads/ad-technical-specifications.md +385 -0
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +506 -0
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +445 -0
- package/src/core/teams/backend.json +41 -0
- package/src/core/teams/frontend.json +41 -0
- package/src/core/teams/qa.json +41 -0
- package/src/core/teams/solo.json +35 -0
- package/src/core/templates/agileflow-metadata.json +20 -1
- package/tools/cli/commands/setup.js +85 -3
- package/tools/cli/commands/update.js +42 -0
- package/tools/cli/installers/ide/_base-ide.js +42 -5
- package/tools/cli/installers/ide/claude-code.js +71 -3
- package/tools/cli/lib/content-injector.js +160 -12
- package/tools/cli/lib/docs-setup.js +1 -1
- package/src/core/commands/skill/create.md +0 -698
- package/src/core/commands/skill/delete.md +0 -316
- package/src/core/commands/skill/edit.md +0 -359
- package/src/core/commands/skill/test.md +0 -394
- package/src/core/commands/skill/upgrade.md +0 -552
- package/src/core/templates/skill-template.md +0 -117
package/scripts/team-manager.js
CHANGED
|
@@ -81,6 +81,19 @@ function getMessagingBridge() {
|
|
|
81
81
|
return _messagingBridge;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// Lazy-load team events for dual-write (session-state + JSONL bus)
|
|
85
|
+
let _teamEvents;
|
|
86
|
+
function getTeamEvents() {
|
|
87
|
+
if (!_teamEvents) {
|
|
88
|
+
try {
|
|
89
|
+
_teamEvents = require('./lib/team-events');
|
|
90
|
+
} catch (e) {
|
|
91
|
+
_teamEvents = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return _teamEvents;
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
/**
|
|
85
98
|
* Find the teams directory
|
|
86
99
|
*/
|
|
@@ -344,17 +357,14 @@ function startTeam(rootDir, templateName) {
|
|
|
344
357
|
// Non-critical - team can still function without state tracking
|
|
345
358
|
}
|
|
346
359
|
|
|
347
|
-
// Log team_created event to bus
|
|
360
|
+
// Log team_created event to session-state + JSONL bus (dual-write)
|
|
348
361
|
try {
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
to: 'team-lead',
|
|
354
|
-
type: 'team_created',
|
|
362
|
+
const teamEvents = getTeamEvents();
|
|
363
|
+
if (teamEvents) {
|
|
364
|
+
teamEvents.trackEvent(rootDir, 'team_created', {
|
|
365
|
+
trace_id: traceId,
|
|
355
366
|
template: templateName,
|
|
356
367
|
mode,
|
|
357
|
-
trace_id: traceId,
|
|
358
368
|
teammate_count: template.teammates.length,
|
|
359
369
|
});
|
|
360
370
|
}
|
|
@@ -439,17 +449,14 @@ function stopTeam(rootDir) {
|
|
|
439
449
|
fs.writeFileSync(sessionStatePath, JSON.stringify(state, null, 2) + '\n');
|
|
440
450
|
}
|
|
441
451
|
|
|
442
|
-
// Log team_stopped event
|
|
452
|
+
// Log team_stopped event to session-state + JSONL bus (dual-write)
|
|
443
453
|
try {
|
|
444
|
-
const
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
to: 'system',
|
|
449
|
-
type: 'team_stopped',
|
|
454
|
+
const teamEvents = getTeamEvents();
|
|
455
|
+
if (teamEvents) {
|
|
456
|
+
teamEvents.trackEvent(rootDir, 'team_stopped', {
|
|
457
|
+
trace_id: team.trace_id,
|
|
450
458
|
template: team.template,
|
|
451
459
|
mode: team.mode,
|
|
452
|
-
trace_id: team.trace_id,
|
|
453
460
|
duration_ms: duration,
|
|
454
461
|
tasks_completed: state.team_metrics ? state.team_metrics.tasks_completed : 0,
|
|
455
462
|
});
|
|
@@ -458,6 +465,20 @@ function stopTeam(rootDir) {
|
|
|
458
465
|
// Non-critical
|
|
459
466
|
}
|
|
460
467
|
|
|
468
|
+
// Log team_completed event for observability parity (AC3)
|
|
469
|
+
try {
|
|
470
|
+
const teamEvents = require('./lib/team-events');
|
|
471
|
+
teamEvents.trackEvent(rootDir, 'team_completed', {
|
|
472
|
+
trace_id: team.trace_id,
|
|
473
|
+
template: team.template,
|
|
474
|
+
mode: team.mode,
|
|
475
|
+
duration_ms: duration,
|
|
476
|
+
tasks_completed: state.team_metrics ? state.team_metrics.tasks_completed : 0,
|
|
477
|
+
});
|
|
478
|
+
} catch (e) {
|
|
479
|
+
// Non-critical
|
|
480
|
+
}
|
|
481
|
+
|
|
461
482
|
// Aggregate and save team metrics by trace_id
|
|
462
483
|
try {
|
|
463
484
|
if (team.trace_id) {
|
|
@@ -471,6 +492,25 @@ function stopTeam(rootDir) {
|
|
|
471
492
|
// Non-critical - metrics aggregation is best-effort
|
|
472
493
|
}
|
|
473
494
|
|
|
495
|
+
// Reconcile teammate final states to status.json (AC2/AC3)
|
|
496
|
+
try {
|
|
497
|
+
const taskSync = require('./lib/task-sync');
|
|
498
|
+
const nativeTasks = (team.teammates || [])
|
|
499
|
+
.filter(t => t.status === 'completed' || t.status === 'done')
|
|
500
|
+
.map(t => ({
|
|
501
|
+
id: t.agent,
|
|
502
|
+
status: 'completed',
|
|
503
|
+
metadata: { story_id: t.story_id },
|
|
504
|
+
}))
|
|
505
|
+
.filter(t => t.metadata.story_id);
|
|
506
|
+
|
|
507
|
+
if (nativeTasks.length > 0) {
|
|
508
|
+
taskSync.reconcile(rootDir, nativeTasks);
|
|
509
|
+
}
|
|
510
|
+
} catch (e) {
|
|
511
|
+
// Non-critical - reconciliation is best-effort
|
|
512
|
+
}
|
|
513
|
+
|
|
474
514
|
return {
|
|
475
515
|
ok: true,
|
|
476
516
|
template: team.template,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tmux-close-windows.sh - Batch close tmux windows via multi-select picker
|
|
3
|
+
#
|
|
4
|
+
# Called by Alt+W (Shift+Alt+w) keybind. Opens an fzf multi-select picker
|
|
5
|
+
# (or bash fallback) to close multiple windows at once. Each closed window
|
|
6
|
+
# is saved to the restore stack for Alt+T recovery.
|
|
7
|
+
#
|
|
8
|
+
# Usage: Run inside a tmux popup (display-popup -E)
|
|
9
|
+
|
|
10
|
+
STACK_FILE="$HOME/.tmux_closed_windows.log"
|
|
11
|
+
MAX_ENTRIES=20
|
|
12
|
+
SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
13
|
+
CURRENT_IDX=$(tmux display-message -p '#{window_index}' 2>/dev/null)
|
|
14
|
+
|
|
15
|
+
if [ -z "$SESSION" ]; then
|
|
16
|
+
echo "Error: not in a tmux session"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Build window list (excluding current window)
|
|
21
|
+
WINDOWS=()
|
|
22
|
+
while IFS=$'\t' read -r idx name path pane_count; do
|
|
23
|
+
[ "$idx" = "$CURRENT_IDX" ] && continue
|
|
24
|
+
WINDOWS+=("$idx"$'\t'"$name"$'\t'"$path"$'\t'"$pane_count")
|
|
25
|
+
done < <(tmux list-windows -t "$SESSION" -F '#{window_index} #{window_name} #{pane_current_path} #{window_panes}' 2>/dev/null)
|
|
26
|
+
|
|
27
|
+
if [ "${#WINDOWS[@]}" -eq 0 ]; then
|
|
28
|
+
echo "No other windows to close"
|
|
29
|
+
sleep 1
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Save a window's state to the restore stack (same format as tmux-save-closed-window.sh)
|
|
34
|
+
save_window() {
|
|
35
|
+
local idx="$1"
|
|
36
|
+
local win_name pane_path claude_uuid timestamp
|
|
37
|
+
|
|
38
|
+
win_name=$(tmux display-message -t "$SESSION:$idx" -p '#{window_name}' 2>/dev/null || echo '')
|
|
39
|
+
pane_path=$(tmux display-message -t "$SESSION:$idx" -p '#{pane_current_path}' 2>/dev/null || echo "$HOME")
|
|
40
|
+
claude_uuid=$(tmux show-options -p -t "$SESSION:$idx" -qv @claude_uuid 2>/dev/null || echo '')
|
|
41
|
+
timestamp=$(date +%s)
|
|
42
|
+
|
|
43
|
+
echo "${win_name}|${pane_path}|${claude_uuid}|${timestamp}" >> "$STACK_FILE"
|
|
44
|
+
|
|
45
|
+
# Prune to MAX_ENTRIES
|
|
46
|
+
if [ -f "$STACK_FILE" ]; then
|
|
47
|
+
local line_count
|
|
48
|
+
line_count=$(wc -l < "$STACK_FILE")
|
|
49
|
+
if [ "$line_count" -gt "$MAX_ENTRIES" ]; then
|
|
50
|
+
tail -n "$MAX_ENTRIES" "$STACK_FILE" > "${STACK_FILE}.tmp" && mv "${STACK_FILE}.tmp" "$STACK_FILE"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Format window list for display
|
|
56
|
+
format_entry() {
|
|
57
|
+
local idx="$1" name="$2" path="$3"
|
|
58
|
+
local short_path
|
|
59
|
+
short_path=$(echo "$path" | sed "s|^$HOME|~|")
|
|
60
|
+
printf '[%s] %-14s %s' "$idx" "$name" "$short_path"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ── fzf path ────────────────────────────────────────────────────────────────
|
|
64
|
+
if command -v fzf &>/dev/null; then
|
|
65
|
+
# Build fzf input
|
|
66
|
+
FZF_INPUT=""
|
|
67
|
+
for entry in "${WINDOWS[@]}"; do
|
|
68
|
+
IFS=$'\t' read -r idx name path pane_count <<< "$entry"
|
|
69
|
+
FZF_INPUT+="$(format_entry "$idx" "$name" "$path")"$'\n'
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
SELECTED=$(printf '%s' "$FZF_INPUT" | fzf --multi \
|
|
73
|
+
--header="TAB=select ENTER=close selected ESC=cancel" \
|
|
74
|
+
--prompt="Close windows> " \
|
|
75
|
+
--color="fg:#a9b1d6,bg:#1a1b26,hl:#e8683a,fg+:#e0e0e0,bg+:#2d2f3a,hl+:#e8683a,pointer:#e8683a,marker:#e8683a,prompt:#7aa2f7" \
|
|
76
|
+
2>/dev/null)
|
|
77
|
+
|
|
78
|
+
if [ -z "$SELECTED" ]; then
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Extract window indices from selection, sort descending to avoid renumbering
|
|
83
|
+
INDICES=()
|
|
84
|
+
while IFS= read -r line; do
|
|
85
|
+
idx=$(echo "$line" | sed 's/^\[//' | sed 's/\].*//')
|
|
86
|
+
INDICES+=("$idx")
|
|
87
|
+
done <<< "$SELECTED"
|
|
88
|
+
|
|
89
|
+
# Sort indices in reverse order (highest first)
|
|
90
|
+
IFS=$'\n' SORTED=($(printf '%s\n' "${INDICES[@]}" | sort -rn)); unset IFS
|
|
91
|
+
|
|
92
|
+
CLOSED=0
|
|
93
|
+
for idx in "${SORTED[@]}"; do
|
|
94
|
+
save_window "$idx"
|
|
95
|
+
tmux kill-window -t "$SESSION:$idx" 2>/dev/null && CLOSED=$((CLOSED + 1))
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
tmux display-message "Closed $CLOSED window(s)"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ── Bash fallback (no fzf) ──────────────────────────────────────────────────
|
|
103
|
+
SELECTED_FLAGS=()
|
|
104
|
+
for _ in "${WINDOWS[@]}"; do
|
|
105
|
+
SELECTED_FLAGS+=(0)
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
while true; do
|
|
109
|
+
clear
|
|
110
|
+
printf '\n \033[1;38;5;208mClose Windows\033[0m (type numbers to toggle, Enter=close, q=cancel)\n\n'
|
|
111
|
+
|
|
112
|
+
i=0
|
|
113
|
+
for entry in "${WINDOWS[@]}"; do
|
|
114
|
+
IFS=$'\t' read -r idx name path pane_count <<< "$entry"
|
|
115
|
+
short_path=$(echo "$path" | sed "s|^$HOME|~|")
|
|
116
|
+
if [ "${SELECTED_FLAGS[$i]}" = "1" ]; then
|
|
117
|
+
printf ' \033[38;5;208m%d) [x] %-14s %s\033[0m\n' "$((i + 1))" "$name" "$short_path"
|
|
118
|
+
else
|
|
119
|
+
printf ' %d) [ ] %-14s %s\n' "$((i + 1))" "$name" "$short_path"
|
|
120
|
+
fi
|
|
121
|
+
i=$((i + 1))
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
# Count selected
|
|
125
|
+
SEL_COUNT=0
|
|
126
|
+
for f in "${SELECTED_FLAGS[@]}"; do
|
|
127
|
+
[ "$f" = "1" ] && SEL_COUNT=$((SEL_COUNT + 1))
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
printf '\n Selected: %d\n' "$SEL_COUNT"
|
|
131
|
+
printf ' Toggle> '
|
|
132
|
+
read -r INPUT
|
|
133
|
+
|
|
134
|
+
if [ "$INPUT" = "q" ] || [ "$INPUT" = "Q" ]; then
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [ -z "$INPUT" ]; then
|
|
139
|
+
# Enter pressed — close selected windows
|
|
140
|
+
if [ "$SEL_COUNT" -eq 0 ]; then
|
|
141
|
+
printf ' No windows selected.\n'
|
|
142
|
+
sleep 1
|
|
143
|
+
continue
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# Collect selected indices in reverse order
|
|
147
|
+
INDICES=()
|
|
148
|
+
i=0
|
|
149
|
+
for entry in "${WINDOWS[@]}"; do
|
|
150
|
+
if [ "${SELECTED_FLAGS[$i]}" = "1" ]; then
|
|
151
|
+
IFS=$'\t' read -r idx _ _ _ <<< "$entry"
|
|
152
|
+
INDICES+=("$idx")
|
|
153
|
+
fi
|
|
154
|
+
i=$((i + 1))
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
IFS=$'\n' SORTED=($(printf '%s\n' "${INDICES[@]}" | sort -rn)); unset IFS
|
|
158
|
+
|
|
159
|
+
CLOSED=0
|
|
160
|
+
for idx in "${SORTED[@]}"; do
|
|
161
|
+
save_window "$idx"
|
|
162
|
+
tmux kill-window -t "$SESSION:$idx" 2>/dev/null && CLOSED=$((CLOSED + 1))
|
|
163
|
+
done
|
|
164
|
+
|
|
165
|
+
tmux display-message "Closed $CLOSED window(s)"
|
|
166
|
+
exit 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Toggle numbers from input (space-separated)
|
|
170
|
+
for num in $INPUT; do
|
|
171
|
+
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le "${#WINDOWS[@]}" ]; then
|
|
172
|
+
idx=$((num - 1))
|
|
173
|
+
if [ "${SELECTED_FLAGS[$idx]}" = "0" ]; then
|
|
174
|
+
SELECTED_FLAGS[$idx]=1
|
|
175
|
+
else
|
|
176
|
+
SELECTED_FLAGS[$idx]=0
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
done
|
|
180
|
+
done
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: a11y-analyzer-aria
|
|
3
|
+
description: ARIA usage analyzer for roles, states, properties, live regions, and widget patterns in web components
|
|
4
|
+
tools: Read, Glob, Grep
|
|
5
|
+
model: haiku
|
|
6
|
+
team_role: utility
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Accessibility Analyzer: ARIA Usage
|
|
11
|
+
|
|
12
|
+
You are a specialized accessibility analyzer focused on **ARIA (Accessible Rich Internet Applications)** usage. Your job is to find incorrect, missing, or redundant ARIA attributes that create barriers or confusion for assistive technology users.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Your Focus Areas
|
|
17
|
+
|
|
18
|
+
1. **Invalid ARIA roles**: Misspelled roles, roles on wrong elements, abstract roles used directly
|
|
19
|
+
2. **Missing required ARIA properties**: Widgets missing required states (e.g., `aria-expanded` on disclosure)
|
|
20
|
+
3. **ARIA on wrong elements**: Roles that conflict with native semantics
|
|
21
|
+
4. **Live regions**: Missing or incorrect `aria-live`, `aria-atomic`, `aria-relevant` for dynamic content
|
|
22
|
+
5. **Widget patterns**: Custom widgets missing expected ARIA pattern (tabs, menus, dialogs, combobox)
|
|
23
|
+
6. **Redundant ARIA**: ARIA that duplicates native semantics (e.g., `role="button"` on `<button>`)
|
|
24
|
+
7. **aria-hidden misuse**: Interactive elements hidden from AT but visible on screen
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Analysis Process
|
|
29
|
+
|
|
30
|
+
### Step 1: Read the Target Code
|
|
31
|
+
|
|
32
|
+
Read the files you're asked to analyze. Focus on:
|
|
33
|
+
- Custom interactive components (dropdowns, modals, tabs, accordions)
|
|
34
|
+
- Dynamic content areas (notifications, chat, live feeds)
|
|
35
|
+
- Custom form controls (date pickers, sliders, toggles)
|
|
36
|
+
- Overlay patterns (modals, drawers, popovers)
|
|
37
|
+
|
|
38
|
+
### Step 2: Look for These Patterns
|
|
39
|
+
|
|
40
|
+
**Pattern 1: Missing aria-expanded on toggleable content**
|
|
41
|
+
```jsx
|
|
42
|
+
// VULN: No aria-expanded state
|
|
43
|
+
<button onClick={() => setOpen(!open)}>Menu</button>
|
|
44
|
+
{open && <div className="dropdown">...</div>}
|
|
45
|
+
// Needs: aria-expanded={open} on the button
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Pattern 2: Custom tabs without ARIA pattern**
|
|
49
|
+
```jsx
|
|
50
|
+
// VULN: Tab pattern without proper roles
|
|
51
|
+
<div className="tabs">
|
|
52
|
+
<div className="tab active" onClick={() => setTab(0)}>Tab 1</div>
|
|
53
|
+
<div className="tab" onClick={() => setTab(1)}>Tab 2</div>
|
|
54
|
+
</div>
|
|
55
|
+
// Needs: role="tablist", role="tab", aria-selected, role="tabpanel"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Pattern 3: Modal without dialog role**
|
|
59
|
+
```jsx
|
|
60
|
+
// VULN: Modal overlay without dialog semantics
|
|
61
|
+
<div className="modal-overlay" style={{ display: isOpen ? 'flex' : 'none' }}>
|
|
62
|
+
<div className="modal-content">
|
|
63
|
+
<h2>Confirm</h2>
|
|
64
|
+
<p>Are you sure?</p>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
// Needs: role="dialog", aria-modal="true", aria-labelledby
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Pattern 4: Dynamic content without live region**
|
|
71
|
+
```jsx
|
|
72
|
+
// VULN: Toast notification not announced to screen readers
|
|
73
|
+
{toast && <div className="toast">{toast.message}</div>}
|
|
74
|
+
// Needs: aria-live="polite" or role="status"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Pattern 5: aria-hidden on focusable element**
|
|
78
|
+
```jsx
|
|
79
|
+
// VULN: Button hidden from AT but still focusable
|
|
80
|
+
<button aria-hidden="true" onClick={handleClose}>X</button>
|
|
81
|
+
// aria-hidden removes from AT but button is still tabbable
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Pattern 6: Redundant ARIA**
|
|
85
|
+
```jsx
|
|
86
|
+
// ISSUE: Redundant - <nav> already has navigation role
|
|
87
|
+
<nav role="navigation">...</nav>
|
|
88
|
+
|
|
89
|
+
// ISSUE: Redundant - <button> already has button role
|
|
90
|
+
<button role="button">Click</button>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Output Format
|
|
96
|
+
|
|
97
|
+
For each potential issue found, output:
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
### FINDING-{N}: {Brief Title}
|
|
101
|
+
|
|
102
|
+
**Location**: `{file}:{line}`
|
|
103
|
+
**Severity**: BLOCKER (AT crash/confusion) | MAJOR (missing critical info) | MINOR (degraded) | ENHANCEMENT
|
|
104
|
+
**Confidence**: HIGH | MEDIUM | LOW
|
|
105
|
+
**WCAG**: SC {number} ({name}) - Level {A/AA/AAA}
|
|
106
|
+
|
|
107
|
+
**Code**:
|
|
108
|
+
\`\`\`{language}
|
|
109
|
+
{relevant code snippet, 3-7 lines}
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
**Issue**: {Clear explanation of the ARIA problem}
|
|
113
|
+
|
|
114
|
+
**Impact**:
|
|
115
|
+
- Assistive technology: {what AT does with this code}
|
|
116
|
+
- User experience: {what the user perceives}
|
|
117
|
+
|
|
118
|
+
**Remediation**:
|
|
119
|
+
- {Specific fix with code example}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## WCAG Reference
|
|
125
|
+
|
|
126
|
+
| Issue | WCAG SC | Level | Typical Severity |
|
|
127
|
+
|-------|---------|-------|-----------------|
|
|
128
|
+
| Missing ARIA states | SC 4.1.2 | A | BLOCKER |
|
|
129
|
+
| Invalid ARIA roles | SC 4.1.2 | A | MAJOR |
|
|
130
|
+
| Missing live region | SC 4.1.3 | AA | MAJOR |
|
|
131
|
+
| aria-hidden on focusable | SC 4.1.2 | A | BLOCKER |
|
|
132
|
+
| Missing dialog semantics | SC 4.1.2 | A | BLOCKER |
|
|
133
|
+
| Redundant ARIA | SC 4.1.2 | A | ENHANCEMENT |
|
|
134
|
+
| Missing widget pattern | SC 4.1.2 | A | MAJOR |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Important Rules
|
|
139
|
+
|
|
140
|
+
1. **Be SPECIFIC**: Include exact file paths and line numbers
|
|
141
|
+
2. **Check for headless UI**: Libraries like Radix, Headless UI, React Aria handle ARIA automatically
|
|
142
|
+
3. **Verify component output**: A component wrapping a headless library may already have correct ARIA
|
|
143
|
+
4. **First rule of ARIA**: Don't use ARIA if native HTML can do the job
|
|
144
|
+
5. **Check for aria-label vs visible text**: Prefer visible labels over aria-label when possible
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## What NOT to Report
|
|
149
|
+
|
|
150
|
+
- Components using Radix UI, Headless UI, React Aria, or Reach UI (they handle ARIA correctly)
|
|
151
|
+
- Native HTML elements with correct implicit roles
|
|
152
|
+
- Server-only code that doesn't render UI
|
|
153
|
+
- Color contrast (visual analyzer handles those)
|
|
154
|
+
- Heading hierarchy (semantic analyzer handles those)
|
|
155
|
+
- Focus management (keyboard analyzer handles those)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: a11y-analyzer-forms
|
|
3
|
+
description: Forms accessibility analyzer for labels, error messages, autocomplete, validation feedback, and form control associations
|
|
4
|
+
tools: Read, Glob, Grep
|
|
5
|
+
model: haiku
|
|
6
|
+
team_role: utility
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Accessibility Analyzer: Forms Accessibility
|
|
11
|
+
|
|
12
|
+
You are a specialized accessibility analyzer focused on **forms accessibility**. Your job is to find code patterns where form controls lack proper labels, error handling is inaccessible, or form interactions create barriers for assistive technology users.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Your Focus Areas
|
|
17
|
+
|
|
18
|
+
1. **Missing labels**: Inputs without associated `<label>`, missing `aria-label` or `aria-labelledby`
|
|
19
|
+
2. **Error messages**: Errors not programmatically associated with inputs, missing `aria-describedby`
|
|
20
|
+
3. **Autocomplete**: Missing `autocomplete` attributes on identity/payment fields
|
|
21
|
+
4. **Validation feedback**: Client-side validation that's not announced to screen readers
|
|
22
|
+
5. **Required fields**: Missing `aria-required` or `required` attribute, unclear required indication
|
|
23
|
+
6. **Fieldset/legend**: Related controls (radio groups, checkbox groups) not grouped with `<fieldset>`/`<legend>`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Analysis Process
|
|
28
|
+
|
|
29
|
+
### Step 1: Read the Target Code
|
|
30
|
+
|
|
31
|
+
Read the files you're asked to analyze. Focus on:
|
|
32
|
+
- Form components and form handlers
|
|
33
|
+
- Input, select, textarea, and custom form control components
|
|
34
|
+
- Validation logic and error display
|
|
35
|
+
- Login, registration, checkout, and settings forms
|
|
36
|
+
- Search inputs and filter controls
|
|
37
|
+
|
|
38
|
+
### Step 2: Look for These Patterns
|
|
39
|
+
|
|
40
|
+
**Pattern 1: Input without label**
|
|
41
|
+
```jsx
|
|
42
|
+
// VULN: Input with no associated label
|
|
43
|
+
<input type="text" placeholder="Enter email" />
|
|
44
|
+
// placeholder is NOT a label substitute
|
|
45
|
+
|
|
46
|
+
// VULN: Label not associated with input
|
|
47
|
+
<label>Email</label>
|
|
48
|
+
<input type="email" id="email-input" />
|
|
49
|
+
// Missing htmlFor="email-input" on label
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Pattern 2: Error message not associated**
|
|
53
|
+
```jsx
|
|
54
|
+
// VULN: Error shown visually but not linked to input
|
|
55
|
+
<input type="email" value={email} onChange={setEmail} />
|
|
56
|
+
{error && <span className="error">{error}</span>}
|
|
57
|
+
// Needs: aria-describedby on input pointing to error span's id
|
|
58
|
+
// Also needs: aria-invalid="true" on the input
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Pattern 3: Missing autocomplete on identity fields**
|
|
62
|
+
```jsx
|
|
63
|
+
// VULN: Login form without autocomplete hints
|
|
64
|
+
<form>
|
|
65
|
+
<input type="text" name="username" /> // needs autocomplete="username"
|
|
66
|
+
<input type="password" name="password" /> // needs autocomplete="current-password"
|
|
67
|
+
</form>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Pattern 4: Required fields without programmatic indication**
|
|
71
|
+
```jsx
|
|
72
|
+
// VULN: Visual asterisk but no programmatic required
|
|
73
|
+
<label>Name <span className="required">*</span></label>
|
|
74
|
+
<input type="text" name="name" />
|
|
75
|
+
// Needs: required attribute or aria-required="true"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Pattern 5: Radio/checkbox group without fieldset**
|
|
79
|
+
```jsx
|
|
80
|
+
// VULN: Related radio buttons not grouped
|
|
81
|
+
<label><input type="radio" name="plan" value="free" /> Free</label>
|
|
82
|
+
<label><input type="radio" name="plan" value="pro" /> Pro</label>
|
|
83
|
+
<label><input type="radio" name="plan" value="enterprise" /> Enterprise</label>
|
|
84
|
+
// Needs: <fieldset><legend>Choose a plan</legend>...</fieldset>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Pattern 6: Custom validation without announcement**
|
|
88
|
+
```jsx
|
|
89
|
+
// VULN: Validation error appears but isn't announced
|
|
90
|
+
const [errors, setErrors] = useState({});
|
|
91
|
+
const validate = () => {
|
|
92
|
+
if (!email) setErrors({ email: 'Email is required' });
|
|
93
|
+
};
|
|
94
|
+
// Error state changes aren't announced to screen readers
|
|
95
|
+
// Needs: aria-live region or role="alert" on error container
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Output Format
|
|
101
|
+
|
|
102
|
+
For each potential issue found, output:
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
### FINDING-{N}: {Brief Title}
|
|
106
|
+
|
|
107
|
+
**Location**: `{file}:{line}`
|
|
108
|
+
**Severity**: BLOCKER (form unusable) | MAJOR (significant barrier) | MINOR (degraded) | ENHANCEMENT
|
|
109
|
+
**Confidence**: HIGH | MEDIUM | LOW
|
|
110
|
+
**WCAG**: SC {number} ({name}) - Level {A/AA/AAA}
|
|
111
|
+
|
|
112
|
+
**Code**:
|
|
113
|
+
\`\`\`{language}
|
|
114
|
+
{relevant code snippet, 3-7 lines}
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
**Issue**: {Clear explanation of the forms accessibility barrier}
|
|
118
|
+
|
|
119
|
+
**Impact**:
|
|
120
|
+
- Users affected: {screen reader users, voice control, cognitive disabilities}
|
|
121
|
+
- Barrier: {what they cannot do or understand}
|
|
122
|
+
|
|
123
|
+
**Remediation**:
|
|
124
|
+
- {Specific fix with code example}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## WCAG Reference
|
|
130
|
+
|
|
131
|
+
| Issue | WCAG SC | Level | Typical Severity |
|
|
132
|
+
|-------|---------|-------|-----------------|
|
|
133
|
+
| Missing label | SC 1.3.1, 4.1.2 | A | BLOCKER |
|
|
134
|
+
| Missing error association | SC 3.3.1 | A | MAJOR |
|
|
135
|
+
| Missing autocomplete | SC 1.3.5 | AA | MINOR |
|
|
136
|
+
| Missing required indication | SC 3.3.2 | A | MAJOR |
|
|
137
|
+
| Missing fieldset/legend | SC 1.3.1 | A | MAJOR |
|
|
138
|
+
| Error prevention | SC 3.3.4 | AA | MINOR |
|
|
139
|
+
| Validation not announced | SC 4.1.3 | AA | MAJOR |
|
|
140
|
+
| Missing input purpose | SC 1.3.5 | AA | MINOR |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Important Rules
|
|
145
|
+
|
|
146
|
+
1. **Be SPECIFIC**: Include exact file paths and line numbers
|
|
147
|
+
2. **Check for form libraries**: React Hook Form, Formik, and Zod may handle some a11y automatically
|
|
148
|
+
3. **Verify aria-label**: `aria-label` is valid even without a visible label
|
|
149
|
+
4. **Check label wrapping**: `<label><input /> Text</label>` is a valid implicit association
|
|
150
|
+
5. **Consider UI libraries**: shadcn/ui, MUI, Chakra UI often include label associations
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## What NOT to Report
|
|
155
|
+
|
|
156
|
+
- Inputs wrapped in `<label>` elements (implicit association is valid)
|
|
157
|
+
- Inputs with `aria-label` or `aria-labelledby`
|
|
158
|
+
- Form libraries that auto-generate error associations
|
|
159
|
+
- Search inputs with `role="search"` on the form and clear visual context
|
|
160
|
+
- Hidden inputs (`type="hidden"`) that don't need labels
|
|
161
|
+
- Focus management issues (keyboard analyzer handles those)
|
|
162
|
+
- Color-only error indicators (visual analyzer handles those)
|