codeharness 0.6.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/bin/codeharness +9 -0
- package/dist/chunk-7ZD2ZNDU.js +540 -0
- package/dist/docker-CT57JGM7.js +33 -0
- package/dist/index.js +6104 -0
- package/package.json +39 -0
- package/ralph/AGENTS.md +38 -0
- package/ralph/bridge.sh +421 -0
- package/ralph/db_schema_gen.sh +109 -0
- package/ralph/doc_gardener.sh +352 -0
- package/ralph/drivers/claude-code.sh +160 -0
- package/ralph/exec_plans.sh +252 -0
- package/ralph/harness_status.sh +156 -0
- package/ralph/lib/circuit_breaker.sh +210 -0
- package/ralph/lib/date_utils.sh +60 -0
- package/ralph/lib/timeout_utils.sh +77 -0
- package/ralph/onboard.sh +83 -0
- package/ralph/ralph.sh +1006 -0
- package/ralph/retro.sh +298 -0
- package/ralph/validate_epic_docs.sh +129 -0
- package/ralph/verify_gates.sh +241 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# doc_gardener.sh — Documentation freshness scanner
|
|
3
|
+
# Finds: missing AGENTS.md, stale AGENTS.md, stale exec-plans.
|
|
4
|
+
# Used by the doc-gardener subagent and during retrospectives.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ralph/doc_gardener.sh --project-dir DIR [--json]
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
PROJECT_DIR=""
|
|
11
|
+
JSON_OUTPUT=false
|
|
12
|
+
GENERATE_REPORT=false
|
|
13
|
+
COMPLEXITY_THRESHOLD=3 # Minimum source files to require AGENTS.md
|
|
14
|
+
|
|
15
|
+
show_help() {
|
|
16
|
+
cat << 'HELPEOF'
|
|
17
|
+
Doc-Gardener Scanner — find stale and missing documentation
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
ralph/doc_gardener.sh --project-dir DIR [--json]
|
|
21
|
+
|
|
22
|
+
Checks:
|
|
23
|
+
1. Modules with 3+ source files but no AGENTS.md
|
|
24
|
+
2. AGENTS.md files older than corresponding source code changes
|
|
25
|
+
3. Exec-plans in active/ for already-completed stories
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--project-dir DIR Project root directory
|
|
29
|
+
--json Output findings as JSON (default: human-readable)
|
|
30
|
+
--report Generate quality-score.md and tech-debt-tracker.md
|
|
31
|
+
--threshold N Min source files to require AGENTS.md (default: 3)
|
|
32
|
+
-h, --help Show this help message
|
|
33
|
+
HELPEOF
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case $1 in
|
|
38
|
+
-h|--help)
|
|
39
|
+
show_help
|
|
40
|
+
exit 0
|
|
41
|
+
;;
|
|
42
|
+
--project-dir)
|
|
43
|
+
PROJECT_DIR="$2"
|
|
44
|
+
shift 2
|
|
45
|
+
;;
|
|
46
|
+
--json)
|
|
47
|
+
JSON_OUTPUT=true
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
--report)
|
|
51
|
+
GENERATE_REPORT=true
|
|
52
|
+
shift
|
|
53
|
+
;;
|
|
54
|
+
--threshold)
|
|
55
|
+
COMPLEXITY_THRESHOLD="$2"
|
|
56
|
+
shift 2
|
|
57
|
+
;;
|
|
58
|
+
*)
|
|
59
|
+
echo "Unknown option: $1" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
if [[ -z "$PROJECT_DIR" ]]; then
|
|
66
|
+
echo "Error: --project-dir is required" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [[ ! -d "$PROJECT_DIR" ]]; then
|
|
71
|
+
echo "Error: project directory not found: $PROJECT_DIR" >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ─── Findings collection ─────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
FINDINGS_JSON="[]"
|
|
78
|
+
|
|
79
|
+
add_finding() {
|
|
80
|
+
local type="$1"
|
|
81
|
+
local path="$2"
|
|
82
|
+
local message="$3"
|
|
83
|
+
|
|
84
|
+
FINDINGS_JSON=$(echo "$FINDINGS_JSON" | jq \
|
|
85
|
+
--arg type "$type" \
|
|
86
|
+
--arg path "$path" \
|
|
87
|
+
--arg message "$message" \
|
|
88
|
+
'. += [{"type": $type, "path": $path, "message": $message}]')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# ─── Check 1: Missing AGENTS.md for modules above complexity threshold ────
|
|
92
|
+
|
|
93
|
+
check_missing_agents_md() {
|
|
94
|
+
# Find directories with source files but no AGENTS.md
|
|
95
|
+
# Exclude hidden dirs, node_modules, _bmad, .ralph, docs, tests
|
|
96
|
+
while IFS= read -r dir; do
|
|
97
|
+
[[ -z "$dir" ]] && continue
|
|
98
|
+
|
|
99
|
+
# Count source files (common extensions)
|
|
100
|
+
local src_count
|
|
101
|
+
src_count=$(find "$dir" -maxdepth 1 \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.rb" \) 2>/dev/null | wc -l | tr -d ' ')
|
|
102
|
+
|
|
103
|
+
if [[ $src_count -ge $COMPLEXITY_THRESHOLD ]]; then
|
|
104
|
+
if [[ ! -f "$dir/AGENTS.md" ]]; then
|
|
105
|
+
local rel_path="${dir#$PROJECT_DIR/}"
|
|
106
|
+
add_finding "missing_agents_md" "$rel_path" "Module $rel_path has $src_count source files but no AGENTS.md"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
done < <(find "$PROJECT_DIR" -type d \
|
|
110
|
+
-not -path "$PROJECT_DIR/.*" \
|
|
111
|
+
-not -path "*/node_modules/*" \
|
|
112
|
+
-not -path "*/_bmad/*" \
|
|
113
|
+
-not -path "*/.ralph/*" \
|
|
114
|
+
-not -path "*/docs/*" \
|
|
115
|
+
-not -path "*/tests/*" \
|
|
116
|
+
-not -path "$PROJECT_DIR" \
|
|
117
|
+
2>/dev/null)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# ─── Check 2: Stale AGENTS.md (code changed after docs) ──────────────────
|
|
121
|
+
|
|
122
|
+
check_stale_agents_md() {
|
|
123
|
+
while IFS= read -r agents_file; do
|
|
124
|
+
[[ -z "$agents_file" ]] && continue
|
|
125
|
+
|
|
126
|
+
local dir
|
|
127
|
+
dir=$(dirname "$agents_file")
|
|
128
|
+
local rel_dir="${dir#$PROJECT_DIR/}"
|
|
129
|
+
|
|
130
|
+
# Get AGENTS.md last commit time
|
|
131
|
+
local agents_commit_time
|
|
132
|
+
agents_commit_time=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- "$agents_file" 2>/dev/null || echo "0")
|
|
133
|
+
|
|
134
|
+
# Get latest source file commit time in the same directory
|
|
135
|
+
local latest_src_time="0"
|
|
136
|
+
while IFS= read -r src_file; do
|
|
137
|
+
[[ -z "$src_file" ]] && continue
|
|
138
|
+
local src_time
|
|
139
|
+
src_time=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- "$src_file" 2>/dev/null || echo "0")
|
|
140
|
+
if [[ $src_time -gt $latest_src_time ]]; then
|
|
141
|
+
latest_src_time=$src_time
|
|
142
|
+
fi
|
|
143
|
+
done < <(find "$dir" -maxdepth 1 \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.rb" \) -not -name "AGENTS.md" 2>/dev/null)
|
|
144
|
+
|
|
145
|
+
if [[ $latest_src_time -gt $agents_commit_time && $agents_commit_time -gt 0 ]]; then
|
|
146
|
+
add_finding "stale_agents_md" "$rel_dir" "AGENTS.md in $rel_dir is stale — source code changed after docs"
|
|
147
|
+
fi
|
|
148
|
+
done < <(find "$PROJECT_DIR" -name "AGENTS.md" \
|
|
149
|
+
-not -path "*/node_modules/*" \
|
|
150
|
+
-not -path "*/_bmad/*" \
|
|
151
|
+
-not -path "*/.ralph/*" \
|
|
152
|
+
2>/dev/null)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# ─── Check 3: Stale exec-plans (completed stories still in active/) ──────
|
|
156
|
+
|
|
157
|
+
check_stale_exec_plans() {
|
|
158
|
+
local progress_file="$PROJECT_DIR/ralph/progress.json"
|
|
159
|
+
local active_dir="$PROJECT_DIR/docs/exec-plans/active"
|
|
160
|
+
|
|
161
|
+
if [[ ! -f "$progress_file" || ! -d "$active_dir" ]]; then
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Find active exec-plans for completed stories
|
|
166
|
+
for plan_file in "$active_dir"/*.md; do
|
|
167
|
+
[[ -f "$plan_file" ]] || continue
|
|
168
|
+
|
|
169
|
+
local story_id
|
|
170
|
+
story_id=$(basename "$plan_file" .md)
|
|
171
|
+
|
|
172
|
+
local story_status
|
|
173
|
+
story_status=$(jq -r --arg id "$story_id" '.tasks[] | select(.id == $id) | .status // ""' "$progress_file" 2>/dev/null)
|
|
174
|
+
|
|
175
|
+
if [[ "$story_status" == "complete" ]]; then
|
|
176
|
+
add_finding "stale_exec_plan" "docs/exec-plans/active/$story_id.md" \
|
|
177
|
+
"Exec-plan for story $story_id is still in active/ but story is complete — should be in completed/"
|
|
178
|
+
fi
|
|
179
|
+
done
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# ─── Quality scoring ──────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
# Collect module info for quality grading
|
|
185
|
+
declare -A MODULE_GRADES
|
|
186
|
+
|
|
187
|
+
grade_modules() {
|
|
188
|
+
while IFS= read -r dir; do
|
|
189
|
+
[[ -z "$dir" ]] && continue
|
|
190
|
+
|
|
191
|
+
local src_count
|
|
192
|
+
src_count=$(find "$dir" -maxdepth 1 \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.rb" \) 2>/dev/null | wc -l | tr -d ' ')
|
|
193
|
+
|
|
194
|
+
# Skip directories with no source files
|
|
195
|
+
[[ $src_count -eq 0 ]] && continue
|
|
196
|
+
|
|
197
|
+
local rel_path="${dir#$PROJECT_DIR/}"
|
|
198
|
+
local has_agents="false"
|
|
199
|
+
local is_stale="false"
|
|
200
|
+
|
|
201
|
+
if [[ -f "$dir/AGENTS.md" ]]; then
|
|
202
|
+
has_agents="true"
|
|
203
|
+
|
|
204
|
+
# Check staleness
|
|
205
|
+
local agents_time
|
|
206
|
+
agents_time=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- "$dir/AGENTS.md" 2>/dev/null || echo "0")
|
|
207
|
+
local latest_src="0"
|
|
208
|
+
while IFS= read -r sf; do
|
|
209
|
+
[[ -z "$sf" ]] && continue
|
|
210
|
+
local st
|
|
211
|
+
st=$(git -C "$PROJECT_DIR" log -1 --format="%ct" -- "$sf" 2>/dev/null || echo "0")
|
|
212
|
+
[[ $st -gt $latest_src ]] && latest_src=$st
|
|
213
|
+
done < <(find "$dir" -maxdepth 1 \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" \) -not -name "AGENTS.md" 2>/dev/null)
|
|
214
|
+
|
|
215
|
+
if [[ $latest_src -gt $agents_time && $agents_time -gt 0 ]]; then
|
|
216
|
+
is_stale="true"
|
|
217
|
+
fi
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# Grade: A = has fresh AGENTS.md, B = has stale AGENTS.md, F = missing
|
|
221
|
+
local grade="F"
|
|
222
|
+
if [[ "$has_agents" == "true" && "$is_stale" == "false" ]]; then
|
|
223
|
+
grade="A"
|
|
224
|
+
elif [[ "$has_agents" == "true" && "$is_stale" == "true" ]]; then
|
|
225
|
+
grade="B"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
MODULE_GRADES["$rel_path"]="$grade"
|
|
229
|
+
done < <(find "$PROJECT_DIR" -type d \
|
|
230
|
+
-not -path "$PROJECT_DIR/.*" \
|
|
231
|
+
-not -path "*/node_modules/*" \
|
|
232
|
+
-not -path "*/_bmad/*" \
|
|
233
|
+
-not -path "*/.ralph/*" \
|
|
234
|
+
-not -path "*/docs/*" \
|
|
235
|
+
-not -path "*/tests/*" \
|
|
236
|
+
-not -path "$PROJECT_DIR" \
|
|
237
|
+
2>/dev/null)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
generate_quality_report() {
|
|
241
|
+
local output_file="$PROJECT_DIR/docs/quality/quality-score.md"
|
|
242
|
+
mkdir -p "$(dirname "$output_file")"
|
|
243
|
+
|
|
244
|
+
local timestamp
|
|
245
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
246
|
+
|
|
247
|
+
{
|
|
248
|
+
echo "<!-- DO NOT EDIT MANUALLY — generated by doc-gardener -->"
|
|
249
|
+
echo ""
|
|
250
|
+
echo "# Documentation Quality Score"
|
|
251
|
+
echo ""
|
|
252
|
+
echo "**Generated:** $timestamp"
|
|
253
|
+
echo ""
|
|
254
|
+
echo "## Module Grades"
|
|
255
|
+
echo ""
|
|
256
|
+
echo "| Module | Grade | Status |"
|
|
257
|
+
echo "|--------|-------|--------|"
|
|
258
|
+
|
|
259
|
+
# Sort and output grades
|
|
260
|
+
for module in $(echo "${!MODULE_GRADES[@]}" | tr ' ' '\n' | sort); do
|
|
261
|
+
local grade="${MODULE_GRADES[$module]}"
|
|
262
|
+
local status_text
|
|
263
|
+
case "$grade" in
|
|
264
|
+
A) status_text="AGENTS.md present and current" ;;
|
|
265
|
+
B) status_text="AGENTS.md present but stale" ;;
|
|
266
|
+
F) status_text="AGENTS.md missing" ;;
|
|
267
|
+
esac
|
|
268
|
+
echo "| $module | $grade | $status_text |"
|
|
269
|
+
done
|
|
270
|
+
|
|
271
|
+
echo ""
|
|
272
|
+
echo "## Grade Legend"
|
|
273
|
+
echo ""
|
|
274
|
+
echo "- **A**: Module has current AGENTS.md"
|
|
275
|
+
echo "- **B**: Module has AGENTS.md but code changed since last update"
|
|
276
|
+
echo "- **F**: Module has no AGENTS.md (3+ source files)"
|
|
277
|
+
} > "$output_file"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
generate_tech_debt_report() {
|
|
281
|
+
local output_file="$PROJECT_DIR/docs/exec-plans/tech-debt-tracker.md"
|
|
282
|
+
mkdir -p "$(dirname "$output_file")"
|
|
283
|
+
|
|
284
|
+
local timestamp
|
|
285
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
286
|
+
|
|
287
|
+
{
|
|
288
|
+
echo "<!-- DO NOT EDIT MANUALLY — generated by doc-gardener -->"
|
|
289
|
+
echo ""
|
|
290
|
+
echo "# Documentation Tech Debt"
|
|
291
|
+
echo ""
|
|
292
|
+
echo "**Generated:** $timestamp"
|
|
293
|
+
echo ""
|
|
294
|
+
|
|
295
|
+
local debt_count
|
|
296
|
+
debt_count=$(echo "$FINDINGS_JSON" | jq '. | length')
|
|
297
|
+
|
|
298
|
+
if [[ $debt_count -eq 0 ]]; then
|
|
299
|
+
echo "No documentation debt items."
|
|
300
|
+
else
|
|
301
|
+
echo "| # | Type | Path | Issue |"
|
|
302
|
+
echo "|---|------|------|-------|"
|
|
303
|
+
|
|
304
|
+
local i=1
|
|
305
|
+
echo "$FINDINGS_JSON" | jq -r '.[] | "\(.type)\t\(.path)\t\(.message)"' | while IFS=$'\t' read -r type path message; do
|
|
306
|
+
echo "| $i | $type | $path | $message |"
|
|
307
|
+
i=$((i + 1))
|
|
308
|
+
done
|
|
309
|
+
fi
|
|
310
|
+
} > "$output_file"
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
# ─── Run all checks ──────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
check_missing_agents_md
|
|
316
|
+
check_stale_agents_md
|
|
317
|
+
check_stale_exec_plans
|
|
318
|
+
|
|
319
|
+
# Generate reports if requested
|
|
320
|
+
if [[ "$GENERATE_REPORT" == "true" ]]; then
|
|
321
|
+
grade_modules
|
|
322
|
+
generate_quality_report
|
|
323
|
+
generate_tech_debt_report
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# ─── Output ───────────────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
finding_count=$(echo "$FINDINGS_JSON" | jq '. | length')
|
|
329
|
+
|
|
330
|
+
if [[ "$JSON_OUTPUT" == "true" ]]; then
|
|
331
|
+
jq -n \
|
|
332
|
+
--argjson findings "$FINDINGS_JSON" \
|
|
333
|
+
--argjson count "$finding_count" \
|
|
334
|
+
--arg scanned_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
335
|
+
'{
|
|
336
|
+
scanned_at: $scanned_at,
|
|
337
|
+
finding_count: $count,
|
|
338
|
+
findings: $findings
|
|
339
|
+
}'
|
|
340
|
+
else
|
|
341
|
+
echo "Doc-Gardener Scan"
|
|
342
|
+
echo ""
|
|
343
|
+
|
|
344
|
+
if [[ $finding_count -eq 0 ]]; then
|
|
345
|
+
echo "[OK] No documentation issues found"
|
|
346
|
+
else
|
|
347
|
+
echo "$FINDINGS_JSON" | jq -r '.[] | " [WARN] \(.type): \(.message)"'
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
echo ""
|
|
351
|
+
echo "$finding_count finding(s) total"
|
|
352
|
+
fi
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code driver for codeharness Ralph loop
|
|
3
|
+
# Handles instance lifecycle: spawn, monitor, terminate
|
|
4
|
+
# Each iteration gets a fresh Claude Code instance with the codeharness plugin
|
|
5
|
+
|
|
6
|
+
# Driver identification
|
|
7
|
+
driver_name() {
|
|
8
|
+
echo "claude-code"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
driver_display_name() {
|
|
12
|
+
echo "Claude Code"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
driver_cli_binary() {
|
|
16
|
+
echo "claude"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
driver_min_version() {
|
|
20
|
+
echo "2.0.76"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Check if the CLI binary is available
|
|
24
|
+
driver_check_available() {
|
|
25
|
+
command -v "$(driver_cli_binary)" &>/dev/null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Valid tool patterns for --allowedTools validation
|
|
29
|
+
driver_valid_tools() {
|
|
30
|
+
VALID_TOOL_PATTERNS=(
|
|
31
|
+
"Write"
|
|
32
|
+
"Read"
|
|
33
|
+
"Edit"
|
|
34
|
+
"MultiEdit"
|
|
35
|
+
"Glob"
|
|
36
|
+
"Grep"
|
|
37
|
+
"Task"
|
|
38
|
+
"TodoWrite"
|
|
39
|
+
"WebFetch"
|
|
40
|
+
"WebSearch"
|
|
41
|
+
"Bash"
|
|
42
|
+
"Bash(git *)"
|
|
43
|
+
"Bash(npm *)"
|
|
44
|
+
"Bash(bats *)"
|
|
45
|
+
"Bash(python *)"
|
|
46
|
+
"Bash(node *)"
|
|
47
|
+
"NotebookEdit"
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Build the CLI command arguments
|
|
52
|
+
# Populates global CLAUDE_CMD_ARGS array
|
|
53
|
+
# Parameters:
|
|
54
|
+
# $1 - prompt_file: path to the prompt file
|
|
55
|
+
# $2 - loop_context: context string for session continuity
|
|
56
|
+
# $3 - session_id: session ID for resume (empty for new session)
|
|
57
|
+
# $4 - plugin_dir: plugin directory (for --plugin-dir flag)
|
|
58
|
+
driver_build_command() {
|
|
59
|
+
local prompt_file=$1
|
|
60
|
+
local loop_context=$2
|
|
61
|
+
local session_id=$3
|
|
62
|
+
local plugin_dir=${4:-""}
|
|
63
|
+
|
|
64
|
+
CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
|
|
65
|
+
|
|
66
|
+
if [[ ! -f "$prompt_file" ]]; then
|
|
67
|
+
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Plugin directory
|
|
72
|
+
if [[ -n "$plugin_dir" ]]; then
|
|
73
|
+
CLAUDE_CMD_ARGS+=("--plugin-dir" "$plugin_dir")
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Output format
|
|
77
|
+
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
|
|
78
|
+
CLAUDE_CMD_ARGS+=("--output-format" "json")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# Allowed tools
|
|
82
|
+
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
83
|
+
CLAUDE_CMD_ARGS+=("--allowedTools")
|
|
84
|
+
local IFS=','
|
|
85
|
+
read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
|
|
86
|
+
for tool in "${tools_array[@]}"; do
|
|
87
|
+
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
88
|
+
if [[ -n "$tool" ]]; then
|
|
89
|
+
CLAUDE_CMD_ARGS+=("$tool")
|
|
90
|
+
fi
|
|
91
|
+
done
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Session resume
|
|
95
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
|
96
|
+
CLAUDE_CMD_ARGS+=("--resume" "$session_id")
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Loop context as system prompt
|
|
100
|
+
if [[ -n "$loop_context" ]]; then
|
|
101
|
+
CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Prompt content
|
|
105
|
+
local prompt_content
|
|
106
|
+
prompt_content=$(cat "$prompt_file")
|
|
107
|
+
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Whether this driver supports session continuity
|
|
111
|
+
driver_supports_sessions() {
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Claude Code supports stream-json live output
|
|
116
|
+
driver_supports_live_output() {
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Prepare command arguments for live stream-json output
|
|
121
|
+
driver_prepare_live_command() {
|
|
122
|
+
LIVE_CMD_ARGS=()
|
|
123
|
+
local skip_next=false
|
|
124
|
+
|
|
125
|
+
for arg in "${CLAUDE_CMD_ARGS[@]}"; do
|
|
126
|
+
if [[ "$skip_next" == "true" ]]; then
|
|
127
|
+
LIVE_CMD_ARGS+=("stream-json")
|
|
128
|
+
skip_next=false
|
|
129
|
+
elif [[ "$arg" == "--output-format" ]]; then
|
|
130
|
+
LIVE_CMD_ARGS+=("$arg")
|
|
131
|
+
skip_next=true
|
|
132
|
+
else
|
|
133
|
+
LIVE_CMD_ARGS+=("$arg")
|
|
134
|
+
fi
|
|
135
|
+
done
|
|
136
|
+
|
|
137
|
+
if [[ "$skip_next" == "true" ]]; then
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
LIVE_CMD_ARGS+=("--verbose" "--include-partial-messages")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Stream filter for raw Claude stream-json events
|
|
145
|
+
driver_stream_filter() {
|
|
146
|
+
echo '
|
|
147
|
+
if .type == "stream_event" then
|
|
148
|
+
if .event.type == "content_block_delta" and .event.delta.type == "text_delta" then
|
|
149
|
+
.event.delta.text
|
|
150
|
+
elif .event.type == "content_block_start" and .event.content_block.type == "tool_use" then
|
|
151
|
+
"\n\n⚡ [" + .event.content_block.name + "]\n"
|
|
152
|
+
elif .event.type == "content_block_stop" then
|
|
153
|
+
"\n"
|
|
154
|
+
else
|
|
155
|
+
empty
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
empty
|
|
159
|
+
end'
|
|
160
|
+
}
|