@arvorco/relentless 0.6.1 → 0.8.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/.claude/commands/relentless.learn.md +52 -0
- package/.claude/skills/checklist/SKILL.md +9 -2
- package/.claude/skills/constitution/SKILL.md +11 -0
- package/.claude/skills/constitution/templates/constitution.md +0 -3
- package/.claude/skills/learn/SKILL.md +361 -0
- package/.claude/skills/learn/scripts/common.sh +46 -0
- package/.claude/skills/learn/scripts/extract-costs.sh +48 -0
- package/.claude/skills/learn/scripts/extract-errors.sh +47 -0
- package/.claude/skills/learn/scripts/extract-failures.sh +47 -0
- package/.claude/skills/learn/scripts/extract-learnings.sh +66 -0
- package/.claude/skills/learn/scripts/extract-patterns.sh +72 -0
- package/.claude/skills/learn/scripts/extract-session-context.sh +77 -0
- package/.claude/skills/learn/scripts/generate-stats.sh +129 -0
- package/.claude/skills/learn/templates/learning-proposal.md +105 -0
- package/.claude/skills/learn/templates/stats.md +69 -0
- package/.claude/skills/plan/SKILL.md +10 -3
- package/.claude/skills/specify/SKILL.md +10 -4
- package/.claude/skills/tasks/SKILL.md +10 -3
- package/.claude/skills/validators/SKILL.md +174 -0
- package/.claude/skills/validators/scripts/common.sh +182 -0
- package/.claude/skills/validators/scripts/validate-checklist.sh +190 -0
- package/.claude/skills/validators/scripts/validate-constitution.sh +111 -0
- package/.claude/skills/validators/scripts/validate-plan.sh +163 -0
- package/.claude/skills/validators/scripts/validate-prompt.sh +125 -0
- package/.claude/skills/validators/scripts/validate-spec.sh +214 -0
- package/.claude/skills/validators/scripts/validate-tasks.sh +179 -0
- package/.claude/skills/validators/scripts/validate.sh +295 -0
- package/CHANGELOG.md +112 -1
- package/package.json +1 -1
- package/relentless/constitution.md +0 -3
- package/src/agents/amp.ts +3 -6
- package/src/agents/claude.ts +4 -7
- package/src/agents/codex.ts +3 -6
- package/src/agents/droid.ts +3 -6
- package/src/agents/exec.ts +56 -10
- package/src/agents/gemini.ts +3 -6
- package/src/agents/opencode.ts +3 -6
- package/src/agents/types.ts +2 -0
- package/src/config/schema.ts +2 -2
- package/src/execution/runner.ts +4 -0
- package/src/init/scaffolder.ts +2 -0
- package/src/tui/App.tsx +132 -16
- package/src/tui/TUIRunner.tsx +68 -4
- package/src/tui/components/CostBadge.tsx +59 -0
- package/src/tui/components/MessageItem.tsx +113 -0
- package/src/tui/components/MessageQueuePanel.tsx +126 -0
- package/src/tui/components/OutputPanel.tsx +270 -0
- package/src/tui/components/QueueInput.tsx +28 -11
- package/src/tui/components/RateLimitIndicator.tsx +97 -0
- package/src/tui/components/StatusBar.tsx +188 -0
- package/src/tui/components/TaskItem.tsx +131 -0
- package/src/tui/components/TaskPanel.tsx +189 -0
- package/src/tui/components/TokenCounter.tsx +48 -0
- package/src/tui/hooks/useAnimation.ts +220 -0
- package/src/tui/hooks/useCostTracking.ts +199 -0
- package/src/tui/hooks/useResponsiveLayout.ts +94 -0
- package/src/tui/hooks/useTUI.ts +57 -1
- package/src/tui/index.tsx +24 -0
- package/src/tui/layouts/LayoutSwitcher.tsx +95 -0
- package/src/tui/layouts/ThreeColumnLayout.tsx +97 -0
- package/src/tui/layouts/VerticalLayout.tsx +69 -0
- package/src/tui/layouts/index.ts +9 -0
- package/src/tui/theme.ts +152 -21
- package/src/tui/types.ts +95 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# validate.sh - Main validation orchestrator
|
|
3
|
+
# Usage:
|
|
4
|
+
# validate.sh <feature-path> # Validate all docs in feature directory
|
|
5
|
+
# validate.sh <file.md> # Validate single file (auto-detect type)
|
|
6
|
+
# validate.sh --type=spec <file.md> # Validate file as specific type
|
|
7
|
+
# validate.sh --help # Show help
|
|
8
|
+
#
|
|
9
|
+
# Exit codes:
|
|
10
|
+
# 0 - All validations passed
|
|
11
|
+
# 1 - Validation errors found
|
|
12
|
+
# 2 - Usage error
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
|
|
18
|
+
# Colors
|
|
19
|
+
RED='\033[0;31m'
|
|
20
|
+
GREEN='\033[0;32m'
|
|
21
|
+
YELLOW='\033[1;33m'
|
|
22
|
+
BLUE='\033[0;34m'
|
|
23
|
+
BOLD='\033[1m'
|
|
24
|
+
NC='\033[0m'
|
|
25
|
+
|
|
26
|
+
show_help() {
|
|
27
|
+
cat <<EOF
|
|
28
|
+
Relentless Document Validator
|
|
29
|
+
|
|
30
|
+
USAGE:
|
|
31
|
+
validate.sh <feature-path> Validate all docs in feature directory
|
|
32
|
+
validate.sh <file.md> Validate single file (auto-detect type)
|
|
33
|
+
validate.sh --type=<type> <file.md> Validate file as specific type
|
|
34
|
+
validate.sh --help Show this help
|
|
35
|
+
|
|
36
|
+
TYPES:
|
|
37
|
+
spec Feature specification (spec.md)
|
|
38
|
+
plan Technical plan (plan.md)
|
|
39
|
+
tasks Implementation tasks (tasks.md)
|
|
40
|
+
checklist Quality checklist (checklist.md)
|
|
41
|
+
constitution Project constitution (constitution.md)
|
|
42
|
+
prompt Agent instructions (prompt.md)
|
|
43
|
+
|
|
44
|
+
EXAMPLES:
|
|
45
|
+
# Validate all documents in a feature
|
|
46
|
+
validate.sh relentless/features/001-queued-prompts
|
|
47
|
+
|
|
48
|
+
# Validate a single spec file
|
|
49
|
+
validate.sh relentless/features/001-queued-prompts/spec.md
|
|
50
|
+
|
|
51
|
+
# Force validation as specific type
|
|
52
|
+
validate.sh --type=spec my-document.md
|
|
53
|
+
|
|
54
|
+
EXIT CODES:
|
|
55
|
+
0 All validations passed
|
|
56
|
+
1 Validation errors found
|
|
57
|
+
2 Usage error
|
|
58
|
+
EOF
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Detect document type from filename
|
|
62
|
+
detect_type() {
|
|
63
|
+
local file="$1"
|
|
64
|
+
local basename
|
|
65
|
+
basename=$(basename "$file")
|
|
66
|
+
|
|
67
|
+
case "$basename" in
|
|
68
|
+
spec.md|*-spec.md|specification.md)
|
|
69
|
+
echo "spec"
|
|
70
|
+
;;
|
|
71
|
+
plan.md|*-plan.md|technical-plan.md)
|
|
72
|
+
echo "plan"
|
|
73
|
+
;;
|
|
74
|
+
tasks.md|*-tasks.md|implementation-tasks.md)
|
|
75
|
+
echo "tasks"
|
|
76
|
+
;;
|
|
77
|
+
checklist.md|*-checklist.md)
|
|
78
|
+
echo "checklist"
|
|
79
|
+
;;
|
|
80
|
+
constitution.md|*-constitution.md)
|
|
81
|
+
echo "constitution"
|
|
82
|
+
;;
|
|
83
|
+
prompt.md|*-prompt.md|agent-prompt.md)
|
|
84
|
+
echo "prompt"
|
|
85
|
+
;;
|
|
86
|
+
*)
|
|
87
|
+
# Try to detect from content
|
|
88
|
+
if grep -qE "^# Feature Specification:" "$file" 2>/dev/null; then
|
|
89
|
+
echo "spec"
|
|
90
|
+
elif grep -qE "^# (Technical )?Plan:" "$file" 2>/dev/null; then
|
|
91
|
+
echo "plan"
|
|
92
|
+
elif grep -qE "^# Implementation Tasks:" "$file" 2>/dev/null; then
|
|
93
|
+
echo "tasks"
|
|
94
|
+
elif grep -qE "^# .*Checklist:" "$file" 2>/dev/null; then
|
|
95
|
+
echo "checklist"
|
|
96
|
+
elif grep -qE "^# .*Constitution" "$file" 2>/dev/null; then
|
|
97
|
+
echo "constitution"
|
|
98
|
+
elif grep -qE "^# .*Agent.*Instructions|^# Relentless" "$file" 2>/dev/null; then
|
|
99
|
+
echo "prompt"
|
|
100
|
+
else
|
|
101
|
+
echo "unknown"
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Run validator for specific type
|
|
108
|
+
run_validator() {
|
|
109
|
+
local type="$1"
|
|
110
|
+
local file="$2"
|
|
111
|
+
|
|
112
|
+
case "$type" in
|
|
113
|
+
spec)
|
|
114
|
+
"$SCRIPT_DIR/validate-spec.sh" "$file"
|
|
115
|
+
;;
|
|
116
|
+
plan)
|
|
117
|
+
"$SCRIPT_DIR/validate-plan.sh" "$file"
|
|
118
|
+
;;
|
|
119
|
+
tasks)
|
|
120
|
+
"$SCRIPT_DIR/validate-tasks.sh" "$file"
|
|
121
|
+
;;
|
|
122
|
+
checklist)
|
|
123
|
+
"$SCRIPT_DIR/validate-checklist.sh" "$file"
|
|
124
|
+
;;
|
|
125
|
+
constitution)
|
|
126
|
+
"$SCRIPT_DIR/validate-constitution.sh" "$file"
|
|
127
|
+
;;
|
|
128
|
+
prompt)
|
|
129
|
+
"$SCRIPT_DIR/validate-prompt.sh" "$file"
|
|
130
|
+
;;
|
|
131
|
+
*)
|
|
132
|
+
echo -e "${RED}Unknown document type: $type${NC}"
|
|
133
|
+
return 2
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Validate all documents in a feature directory
|
|
139
|
+
validate_feature() {
|
|
140
|
+
local feature_path="$1"
|
|
141
|
+
local exit_code=0
|
|
142
|
+
|
|
143
|
+
echo -e "${BOLD}${BLUE}╔═══════════════════════════════════════════╗${NC}"
|
|
144
|
+
echo -e "${BOLD}${BLUE}║ Relentless Document Validator ║${NC}"
|
|
145
|
+
echo -e "${BOLD}${BLUE}╚═══════════════════════════════════════════╝${NC}"
|
|
146
|
+
echo ""
|
|
147
|
+
echo -e "Feature: ${BOLD}$feature_path${NC}"
|
|
148
|
+
echo ""
|
|
149
|
+
|
|
150
|
+
# Find and validate each document type
|
|
151
|
+
local docs_validated=0
|
|
152
|
+
|
|
153
|
+
# spec.md
|
|
154
|
+
if [[ -f "$feature_path/spec.md" ]]; then
|
|
155
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
156
|
+
if ! run_validator "spec" "$feature_path/spec.md"; then
|
|
157
|
+
exit_code=1
|
|
158
|
+
fi
|
|
159
|
+
((docs_validated++))
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# plan.md
|
|
163
|
+
if [[ -f "$feature_path/plan.md" ]]; then
|
|
164
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
165
|
+
if ! run_validator "plan" "$feature_path/plan.md"; then
|
|
166
|
+
exit_code=1
|
|
167
|
+
fi
|
|
168
|
+
((docs_validated++))
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# tasks.md
|
|
172
|
+
if [[ -f "$feature_path/tasks.md" ]]; then
|
|
173
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
174
|
+
if ! run_validator "tasks" "$feature_path/tasks.md"; then
|
|
175
|
+
exit_code=1
|
|
176
|
+
fi
|
|
177
|
+
((docs_validated++))
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# checklist.md
|
|
181
|
+
if [[ -f "$feature_path/checklist.md" ]]; then
|
|
182
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
183
|
+
if ! run_validator "checklist" "$feature_path/checklist.md"; then
|
|
184
|
+
exit_code=1
|
|
185
|
+
fi
|
|
186
|
+
((docs_validated++))
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Project-level constitution (check parent directories)
|
|
190
|
+
local constitution_path=""
|
|
191
|
+
if [[ -f "$feature_path/../../constitution.md" ]]; then
|
|
192
|
+
constitution_path="$feature_path/../../constitution.md"
|
|
193
|
+
elif [[ -f "$feature_path/../../../relentless/constitution.md" ]]; then
|
|
194
|
+
constitution_path="$feature_path/../../../relentless/constitution.md"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
if [[ -n "$constitution_path" && -f "$constitution_path" ]]; then
|
|
198
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
199
|
+
if ! run_validator "constitution" "$constitution_path"; then
|
|
200
|
+
exit_code=1
|
|
201
|
+
fi
|
|
202
|
+
((docs_validated++))
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Project-level prompt.md (check parent directories)
|
|
206
|
+
local prompt_path=""
|
|
207
|
+
if [[ -f "$feature_path/../../prompt.md" ]]; then
|
|
208
|
+
prompt_path="$feature_path/../../prompt.md"
|
|
209
|
+
elif [[ -f "$feature_path/../../../relentless/prompt.md" ]]; then
|
|
210
|
+
prompt_path="$feature_path/../../../relentless/prompt.md"
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
if [[ -n "$prompt_path" && -f "$prompt_path" ]]; then
|
|
214
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
215
|
+
if ! run_validator "prompt" "$prompt_path"; then
|
|
216
|
+
exit_code=1
|
|
217
|
+
fi
|
|
218
|
+
((docs_validated++))
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Summary
|
|
222
|
+
echo ""
|
|
223
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
224
|
+
echo -e "${BOLD}Overall Summary${NC}"
|
|
225
|
+
echo -e "Documents validated: $docs_validated"
|
|
226
|
+
|
|
227
|
+
if [[ "$exit_code" -eq 0 ]]; then
|
|
228
|
+
echo -e "${GREEN}${BOLD}✓ All documents passed validation${NC}"
|
|
229
|
+
else
|
|
230
|
+
echo -e "${RED}${BOLD}✗ Some documents have validation errors${NC}"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
return $exit_code
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Main
|
|
237
|
+
main() {
|
|
238
|
+
local type=""
|
|
239
|
+
local target=""
|
|
240
|
+
|
|
241
|
+
# Parse arguments
|
|
242
|
+
while [[ $# -gt 0 ]]; do
|
|
243
|
+
case "$1" in
|
|
244
|
+
--help|-h)
|
|
245
|
+
show_help
|
|
246
|
+
exit 0
|
|
247
|
+
;;
|
|
248
|
+
--type=*)
|
|
249
|
+
type="${1#--type=}"
|
|
250
|
+
shift
|
|
251
|
+
;;
|
|
252
|
+
*)
|
|
253
|
+
target="$1"
|
|
254
|
+
shift
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
if [[ -z "$target" ]]; then
|
|
260
|
+
echo -e "${RED}Error: No target specified${NC}"
|
|
261
|
+
echo ""
|
|
262
|
+
show_help
|
|
263
|
+
exit 2
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# Check if target exists
|
|
267
|
+
if [[ ! -e "$target" ]]; then
|
|
268
|
+
echo -e "${RED}Error: Target not found: $target${NC}"
|
|
269
|
+
exit 2
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# Directory or file?
|
|
273
|
+
if [[ -d "$target" ]]; then
|
|
274
|
+
validate_feature "$target"
|
|
275
|
+
elif [[ -f "$target" ]]; then
|
|
276
|
+
# Validate single file
|
|
277
|
+
if [[ -z "$type" ]]; then
|
|
278
|
+
type=$(detect_type "$target")
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if [[ "$type" == "unknown" ]]; then
|
|
282
|
+
echo -e "${RED}Error: Could not detect document type for: $target${NC}"
|
|
283
|
+
echo "Use --type=<type> to specify the document type"
|
|
284
|
+
exit 2
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
echo -e "${BOLD}${BLUE}Validating $target as $type${NC}"
|
|
288
|
+
run_validator "$type" "$target"
|
|
289
|
+
else
|
|
290
|
+
echo -e "${RED}Error: Target is neither a file nor directory: $target${NC}"
|
|
291
|
+
exit 2
|
|
292
|
+
fi
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
main "$@"
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0](https://github.com/ArvorCo/Relentless/releases/tag/v0.8.0) - 2026-01-24
|
|
11
|
+
|
|
12
|
+
### Major Features
|
|
13
|
+
|
|
14
|
+
#### Document Validators - Ensure Artifact Quality
|
|
15
|
+
- **New validators module**: `.claude/skills/validators/` with comprehensive validation scripts
|
|
16
|
+
- **6 document validators**:
|
|
17
|
+
- `validate-spec.sh` - Feature specification validation
|
|
18
|
+
- `validate-plan.sh` - Technical plan validation
|
|
19
|
+
- `validate-tasks.sh` - Implementation tasks validation (PRD-convertible format)
|
|
20
|
+
- `validate-checklist.sh` - Quality checklist validation
|
|
21
|
+
- `validate-constitution.sh` - Project constitution validation
|
|
22
|
+
- `validate-prompt.sh` - Agent instructions validation
|
|
23
|
+
- **Main orchestrator**: `validate.sh` validates all docs in a feature directory
|
|
24
|
+
- **Shared utilities**: `common.sh` with color output, pattern counting, placeholder detection
|
|
25
|
+
- **Auto-detect document type**: From filename or content analysis
|
|
26
|
+
|
|
27
|
+
#### Learning System (`/relentless.learn`) - Feedback Loop
|
|
28
|
+
- **New skill**: Distill learnings from completed features and propose amendments
|
|
29
|
+
- **Script-based extraction**: CLI tools (jq, grep, awk) extract data without context bloat
|
|
30
|
+
- **Dual-target amendments**: Constitutional (MUST/SHOULD) vs Tactical (prompt.md)
|
|
31
|
+
- **Human approval workflow**: Proposals require explicit approval before applying
|
|
32
|
+
- **Stats aggregation**: `relentless/stats.md` tracks costs, tokens, and patterns across features
|
|
33
|
+
- **Runner integration**: Recommends `/relentless.learn` after feature completion
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- `.claude/skills/validators/` - Complete validation framework
|
|
37
|
+
- `.claude/skills/validators/scripts/common.sh` - Shared utilities
|
|
38
|
+
- `.claude/skills/validators/scripts/validate-*.sh` - Individual validators
|
|
39
|
+
- `.claude/skills/validators/SKILL.md` - Validator documentation
|
|
40
|
+
- `.claude/skills/learn/` - Learning skill implementation
|
|
41
|
+
- `.claude/skills/learn/scripts/` - Extraction scripts
|
|
42
|
+
- `.claude/commands/relentless.learn.md` - Command wrapper
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- **All skills now include validation steps**: specify, plan, tasks, checklist, constitution
|
|
46
|
+
- Each skill runs the appropriate validator after generating its artifact
|
|
47
|
+
- Validation failures must be fixed before proceeding
|
|
48
|
+
- **Constitution skill**: Now validates both `constitution.md` and `prompt.md`
|
|
49
|
+
- **Removed XML comment tags**: Cleaned up `<!-- TEMPLATE_VERSION: -->` from constitution templates
|
|
50
|
+
- **Validators are flexible**: Support both old and new document formats
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- **Bash arithmetic with `set -e`**: Fixed `((PASSED++))` causing exit code 1 when PASSED=0
|
|
54
|
+
- **Pattern counting**: Fixed `count_pattern` returning "0\n0" due to grep exit codes
|
|
55
|
+
- **Warning handling**: `check_section_warn` now returns 0 (warnings don't fail validation)
|
|
56
|
+
- **grep option parsing**: Added `--` to prevent patterns like `- [ ]` being parsed as options
|
|
57
|
+
- **Octal interpretation**: Fixed bash interpreting 008/009 as invalid octal (use `10#$num`)
|
|
58
|
+
|
|
59
|
+
## [0.7.0](https://github.com/ArvorCo/Relentless/releases/tag/v0.7.0) - 2026-01-24
|
|
60
|
+
|
|
61
|
+
### Major Features
|
|
62
|
+
|
|
63
|
+
#### Enhanced TUI with mIRC-Inspired 3-Column Layout
|
|
64
|
+
- **Responsive layout system**: Automatic switching between layouts based on terminal width
|
|
65
|
+
- Full 3-column (≥120 cols): Tasks | Output | Queue
|
|
66
|
+
- Compressed 3-column (≥100 cols): Narrower panels
|
|
67
|
+
- Vertical layout (<100 cols): Stacked panels for narrow terminals
|
|
68
|
+
- **New layout components**: `ThreeColumnLayout`, `VerticalLayout`, `LayoutSwitcher`
|
|
69
|
+
- **Task Panel (Left)**: Scrollable task list with status indicators and pulse animation for active tasks
|
|
70
|
+
- **Output Panel (Center)**: Enhanced output display with context header and code block detection
|
|
71
|
+
- **Message Queue Panel (Right)**: mIRC-style message display with timestamps
|
|
72
|
+
|
|
73
|
+
#### Real-Time Metrics Display
|
|
74
|
+
- **Cost Badge**: Real-time cost tracking display
|
|
75
|
+
- **Token Counter**: Input/output token usage visualization
|
|
76
|
+
- **Rate Limit Indicator**: Countdown timer when rate limited
|
|
77
|
+
- **Status Bar**: Comprehensive bottom bar with all metrics
|
|
78
|
+
|
|
79
|
+
#### Skip Iteration with Process Kill
|
|
80
|
+
- **Idle warning**: Shows warning after 5 minutes of agent inactivity
|
|
81
|
+
- **Press 's' to skip**: Kills the running agent process and moves to next iteration
|
|
82
|
+
- **AbortController integration**: Clean process termination via SIGTERM (with SIGKILL fallback)
|
|
83
|
+
- **AbortSignal support**: Added to all 6 agent adapters (Claude, Amp, OpenCode, Codex, Droid, Gemini)
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
- `src/tui/layouts/` - New layout system with responsive switching
|
|
87
|
+
- `src/tui/components/TaskItem.tsx` - Individual task display with animations
|
|
88
|
+
- `src/tui/components/TaskPanel.tsx` - Left panel with scrollable task list
|
|
89
|
+
- `src/tui/components/OutputPanel.tsx` - Enhanced center output panel
|
|
90
|
+
- `src/tui/components/MessageItem.tsx` - mIRC-style message component
|
|
91
|
+
- `src/tui/components/MessageQueuePanel.tsx` - Right panel queue display
|
|
92
|
+
- `src/tui/components/CostBadge.tsx` - Real-time cost display
|
|
93
|
+
- `src/tui/components/TokenCounter.tsx` - Token usage display
|
|
94
|
+
- `src/tui/components/RateLimitIndicator.tsx` - Rate limit countdown
|
|
95
|
+
- `src/tui/components/StatusBar.tsx` - Bottom metrics bar
|
|
96
|
+
- `src/tui/hooks/useResponsiveLayout.ts` - Terminal size detection with breakpoints
|
|
97
|
+
- `src/tui/hooks/useAnimation.ts` - Pulse, typing, blinking effects
|
|
98
|
+
- `src/tui/hooks/useCostTracking.ts` - Cost and token aggregation
|
|
99
|
+
- `signal` option in `InvokeOptions` for agent cancellation
|
|
100
|
+
- `aborted` flag in `RunCommandResult` to detect cancelled commands
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
- **Default timeout**: Increased from 10 minutes to 30 minutes (1800000ms)
|
|
104
|
+
- **Idle timeout behavior**: No longer kills process automatically, just shows warning
|
|
105
|
+
- **Theme extended**: Added panel colors, animation constants, badges to `theme.ts`
|
|
106
|
+
- **Types extended**: Added `TokenUsage`, `CostData`, `MessageItem`, `LayoutMode`, `OutputMode` interfaces
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- **Idle timeout no longer detected as rate limit**: Removed idle timeout from rate limit detection in all 6 agents
|
|
110
|
+
- **Process cleanup**: Skip functionality now properly kills the agent process instead of leaving it running
|
|
111
|
+
- **YAML parsing error for routing_preference** (#9): Fixed template quoting to prevent YAML parser errors when routing_preference contains colons (thanks @namick!)
|
|
112
|
+
|
|
113
|
+
## [0.6.1](https://github.com/ArvorCo/Relentless/releases/tag/v0.6.1) - 2026-01-23
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
- **Rate Limit Detection**: Added debug logging to help diagnose false positive rate limit detection
|
|
117
|
+
|
|
10
118
|
## [0.6.0](https://github.com/ArvorCo/Relentless/releases/tag/v0.6.0) - 2026-01-23
|
|
11
119
|
|
|
12
120
|
### Major Features
|
|
@@ -510,7 +618,10 @@ Relentless evolved from the [Ralph Wiggum Pattern](https://ghuntley.com/ralph/)
|
|
|
510
618
|
- **License**: MIT
|
|
511
619
|
- **Inspiration**: [Ralph Wiggum Pattern](https://ghuntley.com/ralph/) by Geoffrey Huntley
|
|
512
620
|
|
|
513
|
-
[Unreleased]: https://github.com/ArvorCo/Relentless/compare/v0.
|
|
621
|
+
[Unreleased]: https://github.com/ArvorCo/Relentless/compare/v0.8.0...HEAD
|
|
622
|
+
[0.8.0]: https://github.com/ArvorCo/Relentless/compare/v0.7.0...v0.8.0
|
|
623
|
+
[0.7.0]: https://github.com/ArvorCo/Relentless/compare/v0.6.1...v0.7.0
|
|
624
|
+
[0.6.1]: https://github.com/ArvorCo/Relentless/compare/v0.6.0...v0.6.1
|
|
514
625
|
[0.6.0]: https://github.com/ArvorCo/Relentless/compare/v0.5.3...v0.6.0
|
|
515
626
|
[0.5.3]: https://github.com/ArvorCo/Relentless/compare/v0.5.2...v0.5.3
|
|
516
627
|
[0.5.2]: https://github.com/ArvorCo/Relentless/compare/v0.5.1...v0.5.2
|
package/package.json
CHANGED
package/src/agents/amp.ts
CHANGED
|
@@ -80,6 +80,7 @@ export const ampAdapter: AgentAdapter = {
|
|
|
80
80
|
cwd: options?.workingDirectory,
|
|
81
81
|
stdin: new Blob([prompt]),
|
|
82
82
|
timeoutMs: options?.timeout,
|
|
83
|
+
signal: options?.signal,
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
const timeoutNote =
|
|
@@ -102,12 +103,8 @@ export const ampAdapter: AgentAdapter = {
|
|
|
102
103
|
},
|
|
103
104
|
|
|
104
105
|
detectRateLimit(output: string): RateLimitInfo {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
limited: true,
|
|
108
|
-
message: "Amp idle timeout",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
106
|
+
// NOTE: Idle timeout is NOT a rate limit - it just means the agent stopped
|
|
107
|
+
// producing output for a while, which is normal behavior for complex tasks.
|
|
111
108
|
|
|
112
109
|
// Amp rate limit patterns
|
|
113
110
|
const patterns = [
|
package/src/agents/claude.ts
CHANGED
|
@@ -90,6 +90,7 @@ export const claudeAdapter: AgentAdapter = {
|
|
|
90
90
|
stdin: new Blob([prompt]),
|
|
91
91
|
timeoutMs: options?.timeout,
|
|
92
92
|
env: Object.keys(env).length > 0 ? env : undefined,
|
|
93
|
+
signal: options?.signal,
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
const timeoutNote =
|
|
@@ -195,13 +196,9 @@ export const claudeAdapter: AgentAdapter = {
|
|
|
195
196
|
}
|
|
196
197
|
};
|
|
197
198
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
limited: true,
|
|
202
|
-
message: "Claude idle timeout",
|
|
203
|
-
};
|
|
204
|
-
}
|
|
199
|
+
// NOTE: Idle timeout is NOT a rate limit - it just means the agent stopped
|
|
200
|
+
// producing output for a while, which is normal behavior for complex tasks.
|
|
201
|
+
// We should NOT switch agents on idle timeout.
|
|
205
202
|
|
|
206
203
|
if (/(?:operation not permitted|permission denied|\beperm\b).*(?:\/\.claude|\.claude)/i.test(output)) {
|
|
207
204
|
debugRateLimit("permission_error", "Claude unavailable due to permission error");
|
package/src/agents/codex.ts
CHANGED
|
@@ -87,6 +87,7 @@ export const codexAdapter: AgentAdapter = {
|
|
|
87
87
|
cwd: options?.workingDirectory,
|
|
88
88
|
stdin: new Blob([prompt]),
|
|
89
89
|
timeoutMs: options?.timeout,
|
|
90
|
+
signal: options?.signal,
|
|
90
91
|
});
|
|
91
92
|
|
|
92
93
|
const timeoutNote =
|
|
@@ -109,12 +110,8 @@ export const codexAdapter: AgentAdapter = {
|
|
|
109
110
|
},
|
|
110
111
|
|
|
111
112
|
detectRateLimit(output: string): RateLimitInfo {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
limited: true,
|
|
115
|
-
message: "Codex idle timeout",
|
|
116
|
-
};
|
|
117
|
-
}
|
|
113
|
+
// NOTE: Idle timeout is NOT a rate limit - it just means the agent stopped
|
|
114
|
+
// producing output for a while, which is normal behavior for complex tasks.
|
|
118
115
|
|
|
119
116
|
if (
|
|
120
117
|
/cannot access session files/i.test(output) ||
|
package/src/agents/droid.ts
CHANGED
|
@@ -87,6 +87,7 @@ export const droidAdapter: AgentAdapter = {
|
|
|
87
87
|
cwd: options?.workingDirectory,
|
|
88
88
|
stdin: new Blob([prompt]),
|
|
89
89
|
timeoutMs: options?.timeout,
|
|
90
|
+
signal: options?.signal,
|
|
90
91
|
});
|
|
91
92
|
|
|
92
93
|
const timeoutNote =
|
|
@@ -109,12 +110,8 @@ export const droidAdapter: AgentAdapter = {
|
|
|
109
110
|
},
|
|
110
111
|
|
|
111
112
|
detectRateLimit(output: string): RateLimitInfo {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
limited: true,
|
|
115
|
-
message: "Droid idle timeout",
|
|
116
|
-
};
|
|
117
|
-
}
|
|
113
|
+
// NOTE: Idle timeout is NOT a rate limit - it just means the agent stopped
|
|
114
|
+
// producing output for a while, which is normal behavior for complex tasks.
|
|
118
115
|
|
|
119
116
|
if (/mcp start failed/i.test(output) || /error reloading mcp servers/i.test(output)) {
|
|
120
117
|
return {
|
package/src/agents/exec.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface RunCommandOptions {
|
|
|
8
8
|
timeoutMs?: number;
|
|
9
9
|
/** Environment variables to pass to the command */
|
|
10
10
|
env?: Record<string, string>;
|
|
11
|
+
/** AbortSignal for cancelling the command */
|
|
12
|
+
signal?: AbortSignal;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface RunCommandResult {
|
|
@@ -16,6 +18,8 @@ export interface RunCommandResult {
|
|
|
16
18
|
exitCode: number;
|
|
17
19
|
duration: number;
|
|
18
20
|
timedOut: boolean;
|
|
21
|
+
/** Whether the command was aborted via signal */
|
|
22
|
+
aborted: boolean;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
async function readStream(
|
|
@@ -66,34 +70,75 @@ export async function runCommand(
|
|
|
66
70
|
|
|
67
71
|
let lastOutput = Date.now();
|
|
68
72
|
let timedOut = false;
|
|
73
|
+
let aborted = false;
|
|
69
74
|
let idleTimer: ReturnType<typeof setInterval> | undefined;
|
|
70
75
|
|
|
71
76
|
const onChunk = () => {
|
|
72
77
|
lastOutput = Date.now();
|
|
73
78
|
};
|
|
74
79
|
|
|
80
|
+
// Handle abort signal - kill the process when skip is requested
|
|
81
|
+
const abortHandler = () => {
|
|
82
|
+
aborted = true;
|
|
83
|
+
proc.kill("SIGTERM");
|
|
84
|
+
// Give it a moment, then force kill if still running
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
try {
|
|
87
|
+
proc.kill("SIGKILL");
|
|
88
|
+
} catch {
|
|
89
|
+
// Process already exited, ignore
|
|
90
|
+
}
|
|
91
|
+
}, 1000);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (options.signal) {
|
|
95
|
+
if (options.signal.aborted) {
|
|
96
|
+
// Already aborted before we started
|
|
97
|
+
proc.kill("SIGTERM");
|
|
98
|
+
aborted = true;
|
|
99
|
+
} else {
|
|
100
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// NOTE: We no longer kill the process on idle timeout.
|
|
105
|
+
// Idle timeout is just informational - the TUI will show a warning
|
|
106
|
+
// and let the user decide to skip if needed.
|
|
107
|
+
// The process continues running until it completes naturally.
|
|
75
108
|
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
76
109
|
idleTimer = setInterval(() => {
|
|
77
110
|
if (Date.now() - lastOutput > options.timeoutMs!) {
|
|
78
111
|
timedOut = true;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
112
|
+
// We intentionally do NOT kill the process here anymore.
|
|
113
|
+
// Just mark that idle timeout was reached for informational purposes.
|
|
114
|
+
clearInterval(idleTimer!);
|
|
115
|
+
idleTimer = undefined;
|
|
84
116
|
}
|
|
85
|
-
},
|
|
117
|
+
}, 1000);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Read streams - they may error if process is killed, so we handle that
|
|
121
|
+
let stdout = "";
|
|
122
|
+
let stderr = "";
|
|
123
|
+
try {
|
|
124
|
+
[stdout, stderr] = await Promise.all([
|
|
125
|
+
readStream(proc.stdout, onChunk),
|
|
126
|
+
readStream(proc.stderr, onChunk),
|
|
127
|
+
]);
|
|
128
|
+
} catch {
|
|
129
|
+
// Stream read failed, likely due to process being killed
|
|
130
|
+
// Continue with whatever output we collected
|
|
86
131
|
}
|
|
87
132
|
|
|
88
|
-
const [stdout, stderr] = await Promise.all([
|
|
89
|
-
readStream(proc.stdout, onChunk),
|
|
90
|
-
readStream(proc.stderr, onChunk),
|
|
91
|
-
]);
|
|
92
133
|
const exitCode = await proc.exited;
|
|
93
134
|
|
|
135
|
+
// Cleanup
|
|
94
136
|
if (idleTimer) {
|
|
95
137
|
clearInterval(idleTimer);
|
|
96
138
|
}
|
|
139
|
+
if (options.signal) {
|
|
140
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
141
|
+
}
|
|
97
142
|
|
|
98
143
|
return {
|
|
99
144
|
stdout,
|
|
@@ -101,5 +146,6 @@ export async function runCommand(
|
|
|
101
146
|
exitCode,
|
|
102
147
|
duration: Date.now() - startTime,
|
|
103
148
|
timedOut,
|
|
149
|
+
aborted,
|
|
104
150
|
};
|
|
105
151
|
}
|
package/src/agents/gemini.ts
CHANGED
|
@@ -83,6 +83,7 @@ export const geminiAdapter: AgentAdapter = {
|
|
|
83
83
|
const result = await runCommand(["gemini", ...args], {
|
|
84
84
|
cwd: options?.workingDirectory,
|
|
85
85
|
timeoutMs: options?.timeout,
|
|
86
|
+
signal: options?.signal,
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
const timeoutNote =
|
|
@@ -105,12 +106,8 @@ export const geminiAdapter: AgentAdapter = {
|
|
|
105
106
|
},
|
|
106
107
|
|
|
107
108
|
detectRateLimit(output: string): RateLimitInfo {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
limited: true,
|
|
111
|
-
message: "Gemini idle timeout",
|
|
112
|
-
};
|
|
113
|
-
}
|
|
109
|
+
// NOTE: Idle timeout is NOT a rate limit - it just means the agent stopped
|
|
110
|
+
// producing output for a while, which is normal behavior for complex tasks.
|
|
114
111
|
|
|
115
112
|
// Gemini rate limit patterns
|
|
116
113
|
const patterns = [
|