@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.
Files changed (64) hide show
  1. package/.claude/commands/relentless.learn.md +52 -0
  2. package/.claude/skills/checklist/SKILL.md +9 -2
  3. package/.claude/skills/constitution/SKILL.md +11 -0
  4. package/.claude/skills/constitution/templates/constitution.md +0 -3
  5. package/.claude/skills/learn/SKILL.md +361 -0
  6. package/.claude/skills/learn/scripts/common.sh +46 -0
  7. package/.claude/skills/learn/scripts/extract-costs.sh +48 -0
  8. package/.claude/skills/learn/scripts/extract-errors.sh +47 -0
  9. package/.claude/skills/learn/scripts/extract-failures.sh +47 -0
  10. package/.claude/skills/learn/scripts/extract-learnings.sh +66 -0
  11. package/.claude/skills/learn/scripts/extract-patterns.sh +72 -0
  12. package/.claude/skills/learn/scripts/extract-session-context.sh +77 -0
  13. package/.claude/skills/learn/scripts/generate-stats.sh +129 -0
  14. package/.claude/skills/learn/templates/learning-proposal.md +105 -0
  15. package/.claude/skills/learn/templates/stats.md +69 -0
  16. package/.claude/skills/plan/SKILL.md +10 -3
  17. package/.claude/skills/specify/SKILL.md +10 -4
  18. package/.claude/skills/tasks/SKILL.md +10 -3
  19. package/.claude/skills/validators/SKILL.md +174 -0
  20. package/.claude/skills/validators/scripts/common.sh +182 -0
  21. package/.claude/skills/validators/scripts/validate-checklist.sh +190 -0
  22. package/.claude/skills/validators/scripts/validate-constitution.sh +111 -0
  23. package/.claude/skills/validators/scripts/validate-plan.sh +163 -0
  24. package/.claude/skills/validators/scripts/validate-prompt.sh +125 -0
  25. package/.claude/skills/validators/scripts/validate-spec.sh +214 -0
  26. package/.claude/skills/validators/scripts/validate-tasks.sh +179 -0
  27. package/.claude/skills/validators/scripts/validate.sh +295 -0
  28. package/CHANGELOG.md +112 -1
  29. package/package.json +1 -1
  30. package/relentless/constitution.md +0 -3
  31. package/src/agents/amp.ts +3 -6
  32. package/src/agents/claude.ts +4 -7
  33. package/src/agents/codex.ts +3 -6
  34. package/src/agents/droid.ts +3 -6
  35. package/src/agents/exec.ts +56 -10
  36. package/src/agents/gemini.ts +3 -6
  37. package/src/agents/opencode.ts +3 -6
  38. package/src/agents/types.ts +2 -0
  39. package/src/config/schema.ts +2 -2
  40. package/src/execution/runner.ts +4 -0
  41. package/src/init/scaffolder.ts +2 -0
  42. package/src/tui/App.tsx +132 -16
  43. package/src/tui/TUIRunner.tsx +68 -4
  44. package/src/tui/components/CostBadge.tsx +59 -0
  45. package/src/tui/components/MessageItem.tsx +113 -0
  46. package/src/tui/components/MessageQueuePanel.tsx +126 -0
  47. package/src/tui/components/OutputPanel.tsx +270 -0
  48. package/src/tui/components/QueueInput.tsx +28 -11
  49. package/src/tui/components/RateLimitIndicator.tsx +97 -0
  50. package/src/tui/components/StatusBar.tsx +188 -0
  51. package/src/tui/components/TaskItem.tsx +131 -0
  52. package/src/tui/components/TaskPanel.tsx +189 -0
  53. package/src/tui/components/TokenCounter.tsx +48 -0
  54. package/src/tui/hooks/useAnimation.ts +220 -0
  55. package/src/tui/hooks/useCostTracking.ts +199 -0
  56. package/src/tui/hooks/useResponsiveLayout.ts +94 -0
  57. package/src/tui/hooks/useTUI.ts +57 -1
  58. package/src/tui/index.tsx +24 -0
  59. package/src/tui/layouts/LayoutSwitcher.tsx +95 -0
  60. package/src/tui/layouts/ThreeColumnLayout.tsx +97 -0
  61. package/src/tui/layouts/VerticalLayout.tsx +69 -0
  62. package/src/tui/layouts/index.ts +9 -0
  63. package/src/tui/theme.ts +152 -21
  64. 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.6.0...HEAD
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arvorco/relentless",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "Universal AI agent orchestrator - works with Claude Code, Amp, OpenCode, Codex, Droid, and Gemini",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,6 +1,3 @@
1
- <!-- TEMPLATE_VERSION: 2.1.0 -->
2
- <!-- LAST_UPDATED: 2026-01-20 -->
3
-
4
1
  # Relentless Project Constitution
5
2
 
6
3
  **Version:** 2.1.0
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
- if (output.includes("[Relentless] Idle timeout")) {
106
- return {
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 = [
@@ -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
- if (output.includes("[Relentless] Idle timeout")) {
199
- debugRateLimit("idle_timeout", "Claude idle timeout");
200
- return {
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");
@@ -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
- if (output.includes("[Relentless] Idle timeout")) {
113
- return {
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) ||
@@ -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
- if (output.includes("[Relentless] Idle timeout")) {
113
- return {
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 {
@@ -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
- try {
80
- proc.kill();
81
- } catch {
82
- // Best-effort kill on timeout.
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
- }, 500);
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
  }
@@ -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
- if (output.includes("[Relentless] Idle timeout")) {
109
- return {
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 = [