@cyperx/clawforge 1.2.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/LICENSE +21 -0
- package/README.md +312 -0
- package/VERSION +1 -0
- package/bin/attach.sh +98 -0
- package/bin/check-agents.sh +343 -0
- package/bin/clawforge +257 -0
- package/bin/clawforge-dashboard +0 -0
- package/bin/clean.sh +257 -0
- package/bin/config.sh +111 -0
- package/bin/conflicts.sh +224 -0
- package/bin/cost.sh +273 -0
- package/bin/dashboard.sh +557 -0
- package/bin/diff.sh +109 -0
- package/bin/doctor.sh +196 -0
- package/bin/eval.sh +217 -0
- package/bin/history.sh +91 -0
- package/bin/init.sh +182 -0
- package/bin/learn.sh +230 -0
- package/bin/logs.sh +126 -0
- package/bin/memory.sh +207 -0
- package/bin/merge-helper.sh +174 -0
- package/bin/multi-review.sh +215 -0
- package/bin/notify.sh +93 -0
- package/bin/on-complete.sh +149 -0
- package/bin/parse-cost.sh +205 -0
- package/bin/pr.sh +167 -0
- package/bin/resume.sh +183 -0
- package/bin/review-mode.sh +163 -0
- package/bin/review-pr.sh +145 -0
- package/bin/routing.sh +88 -0
- package/bin/scope-task.sh +169 -0
- package/bin/spawn-agent.sh +190 -0
- package/bin/sprint.sh +320 -0
- package/bin/steer.sh +107 -0
- package/bin/stop.sh +136 -0
- package/bin/summary.sh +182 -0
- package/bin/swarm.sh +525 -0
- package/bin/templates.sh +244 -0
- package/lib/common.sh +302 -0
- package/lib/templates/bugfix.json +6 -0
- package/lib/templates/migration.json +7 -0
- package/lib/templates/refactor.json +6 -0
- package/lib/templates/security-audit.json +5 -0
- package/lib/templates/test-coverage.json +6 -0
- package/package.json +31 -0
- package/registry/conflicts.jsonl +0 -0
- package/registry/costs.jsonl +0 -0
- package/tui/PRD.md +106 -0
- package/tui/agent.go +266 -0
- package/tui/animation.go +192 -0
- package/tui/dashboard.go +219 -0
- package/tui/filter.go +68 -0
- package/tui/go.mod +25 -0
- package/tui/go.sum +46 -0
- package/tui/keybindings.go +229 -0
- package/tui/main.go +61 -0
- package/tui/model.go +166 -0
- package/tui/steer.go +69 -0
- package/tui/styles.go +69 -0
package/bin/doctor.sh
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# doctor.sh — Diagnose and fix orphaned resources
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
usage() {
|
|
9
|
+
cat <<EOF
|
|
10
|
+
Usage: clawforge doctor [options]
|
|
11
|
+
|
|
12
|
+
Diagnose orphaned sessions, dangling worktrees, stale tasks, and disk usage.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--fix Auto-fix issues (kill orphans, remove dangling, archive stale)
|
|
16
|
+
--json Output as JSON
|
|
17
|
+
--help Show this help
|
|
18
|
+
EOF
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
FIX=false JSON_OUTPUT=false
|
|
22
|
+
while [[ $# -gt 0 ]]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--fix) FIX=true; shift ;;
|
|
25
|
+
--json) JSON_OUTPUT=true; shift ;;
|
|
26
|
+
--help|-h) usage; exit 0 ;;
|
|
27
|
+
*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
_ensure_registry
|
|
32
|
+
ISSUES=0 FIXED=0
|
|
33
|
+
|
|
34
|
+
check() {
|
|
35
|
+
local level="$1" msg="$2"
|
|
36
|
+
case "$level" in
|
|
37
|
+
OK) echo " ✅ $msg" ;;
|
|
38
|
+
WARN) echo " ⚠️ $msg"; ISSUES=$((ISSUES+1)) ;;
|
|
39
|
+
ERROR) echo " ❌ $msg"; ISSUES=$((ISSUES+1)) ;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
echo "🩺 ClawForge Doctor"
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
# 1. Registry integrity
|
|
47
|
+
echo "── Registry ──────────────────────────────"
|
|
48
|
+
if [[ -f "$REGISTRY_FILE" ]]; then
|
|
49
|
+
if jq empty "$REGISTRY_FILE" 2>/dev/null; then
|
|
50
|
+
task_count=$(jq '.tasks | length' "$REGISTRY_FILE")
|
|
51
|
+
check OK "Registry valid ($task_count tasks)"
|
|
52
|
+
else
|
|
53
|
+
check ERROR "Registry JSON is malformed"
|
|
54
|
+
if $FIX; then
|
|
55
|
+
echo '{"tasks":[]}' > "$REGISTRY_FILE"
|
|
56
|
+
echo " → Fixed: reset registry"
|
|
57
|
+
FIXED=$((FIXED+1))
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Duplicate IDs
|
|
62
|
+
dup_count=$(jq '[.tasks[].id] | group_by(.) | map(select(length > 1)) | length' "$REGISTRY_FILE" 2>/dev/null || echo 0)
|
|
63
|
+
if [[ "$dup_count" -gt 0 ]]; then
|
|
64
|
+
check WARN "Found $dup_count duplicate task IDs"
|
|
65
|
+
else
|
|
66
|
+
check OK "No duplicate IDs"
|
|
67
|
+
fi
|
|
68
|
+
else
|
|
69
|
+
check WARN "No registry file found"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# 2. Orphaned tmux sessions
|
|
73
|
+
echo ""
|
|
74
|
+
echo "── tmux Sessions ─────────────────────────"
|
|
75
|
+
TMUX_SESSIONS=$(tmux list-sessions -F "#{session_name}" 2>/dev/null || true)
|
|
76
|
+
REGISTERED_SESSIONS=$(jq -r '.tasks[].tmuxSession // empty' "$REGISTRY_FILE" 2>/dev/null | sort -u || true)
|
|
77
|
+
ORPHANS=""
|
|
78
|
+
|
|
79
|
+
if [[ -n "$TMUX_SESSIONS" ]]; then
|
|
80
|
+
while IFS= read -r sess; do
|
|
81
|
+
# Match clawforge-like session names
|
|
82
|
+
if [[ "$sess" =~ ^agent- ]] || [[ "$sess" =~ ^clawforge- ]] || [[ "$sess" =~ ^sprint ]] || [[ "$sess" =~ ^swarm ]]; then
|
|
83
|
+
if ! echo "$REGISTERED_SESSIONS" | grep -qxF "$sess"; then
|
|
84
|
+
check WARN "Orphaned tmux session: $sess"
|
|
85
|
+
ORPHANS="$ORPHANS $sess"
|
|
86
|
+
if $FIX; then
|
|
87
|
+
tmux kill-session -t "$sess" 2>/dev/null || true
|
|
88
|
+
echo " → Fixed: killed $sess"
|
|
89
|
+
FIXED=$((FIXED+1))
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
done <<< "$TMUX_SESSIONS"
|
|
94
|
+
fi
|
|
95
|
+
[[ -z "$ORPHANS" ]] && check OK "No orphaned tmux sessions"
|
|
96
|
+
|
|
97
|
+
# 3. Dangling worktrees
|
|
98
|
+
echo ""
|
|
99
|
+
echo "── Worktrees ─────────────────────────────"
|
|
100
|
+
DANGLING=0
|
|
101
|
+
WORKTREES=$(jq -r '.tasks[] | select(.status == "done" or .status == "archived" or .status == "cancelled" or .status == "timeout") | .worktree // empty' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
102
|
+
|
|
103
|
+
if [[ -n "$WORKTREES" ]]; then
|
|
104
|
+
while IFS= read -r wt; do
|
|
105
|
+
[[ -z "$wt" ]] && continue
|
|
106
|
+
if [[ -d "$wt" ]]; then
|
|
107
|
+
check WARN "Dangling worktree (task complete): $wt"
|
|
108
|
+
DANGLING=$((DANGLING+1))
|
|
109
|
+
if $FIX; then
|
|
110
|
+
rm -rf "$wt" 2>/dev/null || true
|
|
111
|
+
echo " → Fixed: removed $wt"
|
|
112
|
+
FIXED=$((FIXED+1))
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
done <<< "$WORKTREES"
|
|
116
|
+
fi
|
|
117
|
+
[[ $DANGLING -eq 0 ]] && check OK "No dangling worktrees"
|
|
118
|
+
|
|
119
|
+
# 4. Stale tasks
|
|
120
|
+
echo ""
|
|
121
|
+
echo "── Stale Tasks ───────────────────────────"
|
|
122
|
+
NOW_MS=$(epoch_ms)
|
|
123
|
+
STALE_CUTOFF=$((NOW_MS - 7 * 86400 * 1000)) # 7 days
|
|
124
|
+
STALE_TASKS=$(jq -r --argjson cutoff "$STALE_CUTOFF" '.tasks[] | select(.status == "running" and (.startedAt // 0) < $cutoff) | .id' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
125
|
+
STALE_COUNT=0
|
|
126
|
+
|
|
127
|
+
if [[ -n "$STALE_TASKS" ]]; then
|
|
128
|
+
while IFS= read -r id; do
|
|
129
|
+
[[ -z "$id" ]] && continue
|
|
130
|
+
check WARN "Stale running task (>7 days): $id"
|
|
131
|
+
STALE_COUNT=$((STALE_COUNT+1))
|
|
132
|
+
if $FIX; then
|
|
133
|
+
registry_update "$id" "status" '"archived"'
|
|
134
|
+
echo " → Fixed: archived $id"
|
|
135
|
+
FIXED=$((FIXED+1))
|
|
136
|
+
fi
|
|
137
|
+
done <<< "$STALE_TASKS"
|
|
138
|
+
fi
|
|
139
|
+
[[ $STALE_COUNT -eq 0 ]] && check OK "No stale tasks"
|
|
140
|
+
|
|
141
|
+
# 5. Merged branches not cleaned
|
|
142
|
+
echo ""
|
|
143
|
+
echo "── Branches ──────────────────────────────"
|
|
144
|
+
BRANCH_ISSUES=0
|
|
145
|
+
TASK_BRANCHES=$(jq -r '.tasks[] | select(.status == "done" or .status == "archived") | .branch // empty' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
146
|
+
TASK_REPOS=$(jq -r '.tasks[] | select(.status == "done" or .status == "archived") | .repo // empty' "$REGISTRY_FILE" 2>/dev/null | sort -u || true)
|
|
147
|
+
|
|
148
|
+
if [[ -n "$TASK_REPOS" ]]; then
|
|
149
|
+
while IFS= read -r repo; do
|
|
150
|
+
[[ -z "$repo" || ! -d "$repo" ]] && continue
|
|
151
|
+
MERGED=$(git -C "$repo" branch --merged 2>/dev/null | grep -E "sprint/|swarm/|quick/" | sed 's/^[* ]*//' || true)
|
|
152
|
+
if [[ -n "$MERGED" ]]; then
|
|
153
|
+
while IFS= read -r br; do
|
|
154
|
+
check WARN "Merged branch not deleted: $br (in $repo)"
|
|
155
|
+
BRANCH_ISSUES=$((BRANCH_ISSUES+1))
|
|
156
|
+
if $FIX; then
|
|
157
|
+
git -C "$repo" branch -d "$br" 2>/dev/null || true
|
|
158
|
+
echo " → Fixed: deleted $br"
|
|
159
|
+
FIXED=$((FIXED+1))
|
|
160
|
+
fi
|
|
161
|
+
done <<< "$MERGED"
|
|
162
|
+
fi
|
|
163
|
+
done <<< "$TASK_REPOS"
|
|
164
|
+
fi
|
|
165
|
+
[[ $BRANCH_ISSUES -eq 0 ]] && check OK "No leftover merged branches"
|
|
166
|
+
|
|
167
|
+
# 6. Disk space
|
|
168
|
+
echo ""
|
|
169
|
+
echo "── Disk Space ────────────────────────────"
|
|
170
|
+
AVAIL_KB=$(df -k . 2>/dev/null | awk 'NR==2{print $4}')
|
|
171
|
+
if [[ -n "$AVAIL_KB" ]]; then
|
|
172
|
+
AVAIL_GB=$((AVAIL_KB / 1048576))
|
|
173
|
+
if [[ $AVAIL_GB -lt 1 ]]; then
|
|
174
|
+
check ERROR "Critically low disk: ${AVAIL_GB}GB free"
|
|
175
|
+
elif [[ $AVAIL_GB -lt 5 ]]; then
|
|
176
|
+
check WARN "Low disk: ${AVAIL_GB}GB free"
|
|
177
|
+
else
|
|
178
|
+
check OK "Disk space: ${AVAIL_GB}GB free"
|
|
179
|
+
fi
|
|
180
|
+
else
|
|
181
|
+
check OK "Disk check skipped (df unavailable)"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Summary
|
|
185
|
+
echo ""
|
|
186
|
+
echo "────────────────────────────────────────"
|
|
187
|
+
if [[ $ISSUES -eq 0 ]]; then
|
|
188
|
+
echo "✅ All checks passed. System is healthy."
|
|
189
|
+
else
|
|
190
|
+
echo "Found $ISSUES issue(s)."
|
|
191
|
+
if $FIX; then
|
|
192
|
+
echo "Fixed $FIXED issue(s)."
|
|
193
|
+
else
|
|
194
|
+
echo "Run 'clawforge doctor --fix' to auto-fix."
|
|
195
|
+
fi
|
|
196
|
+
fi
|
package/bin/eval.sh
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# eval.sh — Lightweight evaluation logging + weekly summaries
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
EVAL_DIR="${CLAWFORGE_DIR}/evals"
|
|
9
|
+
RUN_LOG_FILE="${EVAL_DIR}/run-log.jsonl"
|
|
10
|
+
SCORECARD_FILE="${EVAL_DIR}/scorecard.md"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<USAGE
|
|
14
|
+
Usage: eval.sh <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
log Append one run-eval entry to evals/run-log.jsonl
|
|
18
|
+
weekly Print weekly summary from run-log.jsonl
|
|
19
|
+
compare Compare two weekly summaries
|
|
20
|
+
paths Show eval file paths
|
|
21
|
+
|
|
22
|
+
log options:
|
|
23
|
+
--command <name> Required (sprint|swarm|review|dashboard|memory|init|history)
|
|
24
|
+
--mode <name> Required (e.g. quick, auto, multi-repo)
|
|
25
|
+
--repo <path/name> Required
|
|
26
|
+
--status <ok|error|timeout|cancelled> Required
|
|
27
|
+
--duration-ms <n> Required
|
|
28
|
+
--retries <n> Default: 0
|
|
29
|
+
--cost-usd <n> Default: 0
|
|
30
|
+
--manual <n> Manual interventions (default: 0)
|
|
31
|
+
--tests-passed <true|false> Default: true
|
|
32
|
+
--review-comments <n> Default: 0
|
|
33
|
+
--reopened <true|false> Default: false
|
|
34
|
+
--reverted <true|false> Default: false
|
|
35
|
+
--task <text> Optional
|
|
36
|
+
--notes <text> Optional
|
|
37
|
+
|
|
38
|
+
weekly options:
|
|
39
|
+
--week <YYYY-WW> Optional (default: current local week)
|
|
40
|
+
|
|
41
|
+
compare options:
|
|
42
|
+
--week-a <YYYY-WW> Required
|
|
43
|
+
--week-b <YYYY-WW> Required
|
|
44
|
+
USAGE
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ensure_eval_files() {
|
|
48
|
+
mkdir -p "$EVAL_DIR"
|
|
49
|
+
[[ -f "$RUN_LOG_FILE" ]] || : > "$RUN_LOG_FILE"
|
|
50
|
+
[[ -f "$SCORECARD_FILE" ]] || {
|
|
51
|
+
cat > "$SCORECARD_FILE" <<'SC'
|
|
52
|
+
# ClawForge Evaluation Scorecard
|
|
53
|
+
|
|
54
|
+
See evals/run-log.jsonl for raw run entries.
|
|
55
|
+
SC
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
current_week() {
|
|
60
|
+
date +"%G-%V"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
week_for_ts() {
|
|
64
|
+
local ts="$1"
|
|
65
|
+
python3 - <<PY2
|
|
66
|
+
import datetime
|
|
67
|
+
ms=int("$ts")
|
|
68
|
+
dt=datetime.datetime.fromtimestamp(ms/1000)
|
|
69
|
+
print(dt.strftime("%G-%V"))
|
|
70
|
+
PY2
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cmd_log() {
|
|
74
|
+
local command="" mode="" repo="" status="" duration_ms=""
|
|
75
|
+
local retries=0 cost_usd=0 manual=0 tests_passed=true review_comments=0 reopened=false reverted=false
|
|
76
|
+
local task="" notes=""
|
|
77
|
+
|
|
78
|
+
while [[ $# -gt 0 ]]; do
|
|
79
|
+
case "$1" in
|
|
80
|
+
--command) command="$2"; shift 2 ;;
|
|
81
|
+
--mode) mode="$2"; shift 2 ;;
|
|
82
|
+
--repo) repo="$2"; shift 2 ;;
|
|
83
|
+
--status) status="$2"; shift 2 ;;
|
|
84
|
+
--duration-ms) duration_ms="$2"; shift 2 ;;
|
|
85
|
+
--retries) retries="$2"; shift 2 ;;
|
|
86
|
+
--cost-usd) cost_usd="$2"; shift 2 ;;
|
|
87
|
+
--manual) manual="$2"; shift 2 ;;
|
|
88
|
+
--tests-passed) tests_passed="$2"; shift 2 ;;
|
|
89
|
+
--review-comments) review_comments="$2"; shift 2 ;;
|
|
90
|
+
--reopened) reopened="$2"; shift 2 ;;
|
|
91
|
+
--reverted) reverted="$2"; shift 2 ;;
|
|
92
|
+
--task) task="$2"; shift 2 ;;
|
|
93
|
+
--notes) notes="$2"; shift 2 ;;
|
|
94
|
+
--help|-h) usage; exit 0 ;;
|
|
95
|
+
*) log_error "Unknown log option: $1"; usage; exit 1 ;;
|
|
96
|
+
esac
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
if [[ -z "$command" || -z "$mode" || -z "$repo" || -z "$status" || -z "$duration_ms" ]]; then
|
|
100
|
+
log_error "Missing required fields for eval log"
|
|
101
|
+
usage
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
ensure_eval_files
|
|
106
|
+
local now_iso now_ms run_id
|
|
107
|
+
now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
108
|
+
now_ms=$(epoch_ms)
|
|
109
|
+
run_id="run-${now_ms}"
|
|
110
|
+
|
|
111
|
+
jq -cn --arg ts "$now_iso" --arg runId "$run_id" --arg command "$command" --arg mode "$mode" --arg repo "$repo" --arg task "$task" --arg status "$status" --arg notes "$notes" --argjson durationMs "$duration_ms" --argjson retries "$retries" --argjson costUsd "$cost_usd" --argjson manualInterventions "$manual" --argjson testsPassed "$tests_passed" --argjson reviewComments "$review_comments" --argjson reopened "$reopened" --argjson reverted "$reverted" '{
|
|
112
|
+
ts:$ts, runId:$runId, command:$command, mode:$mode, repo:$repo, task:$task,
|
|
113
|
+
status:$status, durationMs:$durationMs, retries:$retries, costUsd:$costUsd,
|
|
114
|
+
manualInterventions:$manualInterventions,
|
|
115
|
+
qualityOutcome:{testsPassed:$testsPassed, reviewComments:$reviewComments, reopened:$reopened, reverted:$reverted},
|
|
116
|
+
notes:$notes
|
|
117
|
+
}' >> "$RUN_LOG_FILE"
|
|
118
|
+
|
|
119
|
+
echo "Logged eval run: $run_id"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
summary_for_week() {
|
|
123
|
+
local week="$1"
|
|
124
|
+
ensure_eval_files
|
|
125
|
+
|
|
126
|
+
jq -rc '. as $o | $o + {week:(($o.ts|fromdateiso8601)*1000)}' "$RUN_LOG_FILE" 2>/dev/null | while IFS= read -r line; do
|
|
127
|
+
[[ -z "$line" ]] && continue
|
|
128
|
+
local ts_ms
|
|
129
|
+
ts_ms=$(echo "$line" | jq -r '.week')
|
|
130
|
+
local w
|
|
131
|
+
w=$(week_for_ts "$ts_ms")
|
|
132
|
+
if [[ "$w" == "$week" ]]; then
|
|
133
|
+
echo "$line" | jq -c 'del(.week)'
|
|
134
|
+
fi
|
|
135
|
+
done
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
print_summary() {
|
|
139
|
+
local week="$1"
|
|
140
|
+
local entries
|
|
141
|
+
entries=$(summary_for_week "$week" || true)
|
|
142
|
+
if [[ -z "$entries" ]]; then
|
|
143
|
+
echo "No eval entries for week $week"
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
local total ok errors med_dur retries cost manual
|
|
148
|
+
total=$(echo "$entries" | wc -l | tr -d ' ')
|
|
149
|
+
ok=$(echo "$entries" | jq -r 'select(.status=="ok") | .runId' | wc -l | tr -d ' ')
|
|
150
|
+
errors=$((total - ok))
|
|
151
|
+
med_dur=$(echo "$entries" | jq -s 'map(.durationMs) | sort | .[(length/2|floor)]')
|
|
152
|
+
retries=$(echo "$entries" | jq -s 'map(.retries) | add')
|
|
153
|
+
cost=$(echo "$entries" | jq -s 'map(.costUsd) | add')
|
|
154
|
+
manual=$(echo "$entries" | jq -s 'map(.manualInterventions) | add')
|
|
155
|
+
|
|
156
|
+
echo "Week: $week"
|
|
157
|
+
echo "Runs: $total"
|
|
158
|
+
echo "Success: $ok/$total"
|
|
159
|
+
echo "Errors: $errors"
|
|
160
|
+
echo "Median duration ms: $med_dur"
|
|
161
|
+
echo "Total retries: $retries"
|
|
162
|
+
echo "Total cost usd: $cost"
|
|
163
|
+
echo "Manual interventions: $manual"
|
|
164
|
+
|
|
165
|
+
echo ""
|
|
166
|
+
echo "By command:"
|
|
167
|
+
echo "$entries" | jq -s -r 'group_by(.command)[] | "- \(.[0].command): \(length) runs, \([.[]|select(.status=="ok")]|length) ok"'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
cmd_weekly() {
|
|
171
|
+
local week
|
|
172
|
+
week=$(current_week)
|
|
173
|
+
while [[ $# -gt 0 ]]; do
|
|
174
|
+
case "$1" in
|
|
175
|
+
--week) week="$2"; shift 2 ;;
|
|
176
|
+
--help|-h) usage; exit 0 ;;
|
|
177
|
+
*) log_error "Unknown weekly option: $1"; usage; exit 1 ;;
|
|
178
|
+
esac
|
|
179
|
+
done
|
|
180
|
+
print_summary "$week"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cmd_compare() {
|
|
184
|
+
local wa="" wb=""
|
|
185
|
+
while [[ $# -gt 0 ]]; do
|
|
186
|
+
case "$1" in
|
|
187
|
+
--week-a) wa="$2"; shift 2 ;;
|
|
188
|
+
--week-b) wb="$2"; shift 2 ;;
|
|
189
|
+
--help|-h) usage; exit 0 ;;
|
|
190
|
+
*) log_error "Unknown compare option: $1"; usage; exit 1 ;;
|
|
191
|
+
esac
|
|
192
|
+
done
|
|
193
|
+
if [[ -z "$wa" || -z "$wb" ]]; then
|
|
194
|
+
log_error "compare requires --week-a and --week-b"
|
|
195
|
+
usage
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
echo "=== Week A ($wa) ==="
|
|
200
|
+
print_summary "$wa"
|
|
201
|
+
echo ""
|
|
202
|
+
echo "=== Week B ($wb) ==="
|
|
203
|
+
print_summary "$wb"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case "${1:-}" in
|
|
207
|
+
log) shift; cmd_log "$@" ;;
|
|
208
|
+
weekly) shift; cmd_weekly "$@" ;;
|
|
209
|
+
compare) shift; cmd_compare "$@" ;;
|
|
210
|
+
paths)
|
|
211
|
+
ensure_eval_files
|
|
212
|
+
echo "run_log=$RUN_LOG_FILE"
|
|
213
|
+
echo "scorecard=$SCORECARD_FILE"
|
|
214
|
+
;;
|
|
215
|
+
help|--help|-h|"") usage ;;
|
|
216
|
+
*) log_error "Unknown eval command: $1"; usage; exit 1 ;;
|
|
217
|
+
esac
|
package/bin/history.sh
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# history.sh — Show completed task history
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
HISTORY_FILE="${CLAWFORGE_DIR}/registry/completed-tasks.jsonl"
|
|
9
|
+
|
|
10
|
+
# ── Help ───────────────────────────────────────────────────────────────
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<EOF
|
|
13
|
+
Usage: history.sh [options]
|
|
14
|
+
|
|
15
|
+
Show completed task history.
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--repo <name> Filter by repo name
|
|
19
|
+
--mode <mode> Filter by mode (sprint, swarm, review)
|
|
20
|
+
--limit <n> Number of entries to show (default: 10)
|
|
21
|
+
--all Show all entries (no limit)
|
|
22
|
+
--help Show this help
|
|
23
|
+
EOF
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# ── Parse args ────────────────────────────────────────────────────────
|
|
27
|
+
REPO_FILTER="" MODE_FILTER="" LIMIT=10 SHOW_ALL=false
|
|
28
|
+
|
|
29
|
+
while [[ $# -gt 0 ]]; do
|
|
30
|
+
case "$1" in
|
|
31
|
+
--repo) REPO_FILTER="$2"; shift 2 ;;
|
|
32
|
+
--mode) MODE_FILTER="$2"; shift 2 ;;
|
|
33
|
+
--limit) LIMIT="$2"; shift 2 ;;
|
|
34
|
+
--all) SHOW_ALL=true; shift ;;
|
|
35
|
+
--help|-h) usage; exit 0 ;;
|
|
36
|
+
*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# ── Check file ────────────────────────────────────────────────────────
|
|
41
|
+
if [[ ! -f "$HISTORY_FILE" ]] || [[ ! -s "$HISTORY_FILE" ]]; then
|
|
42
|
+
echo "No completed tasks yet."
|
|
43
|
+
echo "Tasks are recorded when cleaned via 'clawforge clean'."
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# ── Build jq filter ──────────────────────────────────────────────────
|
|
48
|
+
JQ_FILTER="."
|
|
49
|
+
if [[ -n "$REPO_FILTER" ]]; then
|
|
50
|
+
JQ_FILTER="$JQ_FILTER | select(.repo | test(\"$REPO_FILTER\"; \"i\"))"
|
|
51
|
+
fi
|
|
52
|
+
if [[ -n "$MODE_FILTER" ]]; then
|
|
53
|
+
JQ_FILTER="$JQ_FILTER | select(.mode == \"$MODE_FILTER\")"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ── Collect and limit ────────────────────────────────────────────────
|
|
57
|
+
ENTRIES=$(jq -c "$JQ_FILTER" "$HISTORY_FILE" 2>/dev/null || true)
|
|
58
|
+
|
|
59
|
+
if [[ -z "$ENTRIES" ]]; then
|
|
60
|
+
echo "No matching tasks found."
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if ! $SHOW_ALL; then
|
|
65
|
+
ENTRIES=$(echo "$ENTRIES" | tail -"$LIMIT")
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
TOTAL=$(echo "$ENTRIES" | wc -l | tr -d ' ')
|
|
69
|
+
|
|
70
|
+
# ── Print table ──────────────────────────────────────────────────────
|
|
71
|
+
printf "%-12s %-8s %-40s %-8s %-8s %-8s %s\n" "Date" "Mode" "Task" "Status" "Dur" "Cost" "PR"
|
|
72
|
+
printf "%-12s %-8s %-40s %-8s %-8s %-8s %s\n" "────────────" "────────" "────────────────────────────────────────" "────────" "────────" "────────" "──────"
|
|
73
|
+
|
|
74
|
+
echo "$ENTRIES" | while IFS= read -r line; do
|
|
75
|
+
date=$(echo "$line" | jq -r '.completedAt // .cleanedAt // .timestamp // 0' | \
|
|
76
|
+
python3 -c "import sys,datetime; t=int(sys.stdin.read().strip()); print(datetime.datetime.fromtimestamp(t/1000).strftime('%Y-%m-%d') if t > 0 else '—')" 2>/dev/null || echo "—")
|
|
77
|
+
mode=$(echo "$line" | jq -r '.mode // "—"')
|
|
78
|
+
task=$(echo "$line" | jq -r '.description // "—"' | cut -c1-40)
|
|
79
|
+
status=$(echo "$line" | jq -r '.status // "—"')
|
|
80
|
+
dur_min=$(echo "$line" | jq -r '.duration_minutes // "—"')
|
|
81
|
+
[[ "$dur_min" != "—" && "$dur_min" != "0" && "$dur_min" != "null" ]] && dur="${dur_min}m" || dur="—"
|
|
82
|
+
cost=$(echo "$line" | jq -r '.cost // "—"')
|
|
83
|
+
[[ "$cost" == "null" ]] && cost="—"
|
|
84
|
+
pr=$(echo "$line" | jq -r '.pr // "—"')
|
|
85
|
+
[[ "$pr" == "null" ]] && pr="—"
|
|
86
|
+
|
|
87
|
+
printf "%-12s %-8s %-40s %-8s %-8s %-8s %s\n" "$date" "$mode" "$task" "$status" "$dur" "$cost" "$pr"
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo "Showing $TOTAL entries."
|
package/bin/init.sh
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# init.sh — Scan project and generate initial memory entries
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
7
|
+
|
|
8
|
+
MEMORY_BASE="$HOME/.clawforge/memory"
|
|
9
|
+
|
|
10
|
+
# ── Help ───────────────────────────────────────────────────────────────
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<EOF
|
|
13
|
+
Usage: init.sh [options]
|
|
14
|
+
|
|
15
|
+
Scans the current directory for project structure and generates
|
|
16
|
+
initial memory entries for agent context.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--claude-md Also create CLAUDE.md if missing
|
|
20
|
+
--help Show this help
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# ── Parse args ────────────────────────────────────────────────────────
|
|
25
|
+
CREATE_CLAUDE_MD=false
|
|
26
|
+
|
|
27
|
+
while [[ $# -gt 0 ]]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--claude-md) CREATE_CLAUDE_MD=true; shift ;;
|
|
30
|
+
--help|-h) usage; exit 0 ;;
|
|
31
|
+
*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
# ── Detect repo name ─────────────────────────────────────────────────
|
|
36
|
+
REPO_NAME=$(basename "$(pwd)")
|
|
37
|
+
REMOTE_URL=$(git config --get remote.origin.url 2>/dev/null || true)
|
|
38
|
+
[[ -n "$REMOTE_URL" ]] && REPO_NAME=$(basename "$REMOTE_URL" .git)
|
|
39
|
+
|
|
40
|
+
MEMORY_FILE="${MEMORY_BASE}/${REPO_NAME}.jsonl"
|
|
41
|
+
mkdir -p "$MEMORY_BASE"
|
|
42
|
+
|
|
43
|
+
# ── Helper: add memory ───────────────────────────────────────────────
|
|
44
|
+
ENTRY_COUNT=0
|
|
45
|
+
add_memory() {
|
|
46
|
+
local text="$1"
|
|
47
|
+
local tags="${2:-init}"
|
|
48
|
+
local tags_json
|
|
49
|
+
tags_json=$(echo "$tags" | tr ',' '\n' | jq -R . | jq -s .)
|
|
50
|
+
local entry
|
|
51
|
+
entry=$(jq -cn \
|
|
52
|
+
--arg id "init-$(date +%s)-${ENTRY_COUNT}" \
|
|
53
|
+
--arg text "$text" \
|
|
54
|
+
--argjson tags "$tags_json" \
|
|
55
|
+
--arg created "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
56
|
+
--arg source "init" \
|
|
57
|
+
'{id:$id, text:$text, tags:$tags, created:$created, source:$source}')
|
|
58
|
+
echo "$entry" >> "$MEMORY_FILE"
|
|
59
|
+
ENTRY_COUNT=$((ENTRY_COUNT + 1))
|
|
60
|
+
echo " + $text"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
echo "Scanning: $(pwd)"
|
|
64
|
+
echo "Repo: $REPO_NAME"
|
|
65
|
+
echo ""
|
|
66
|
+
|
|
67
|
+
OBSERVATIONS=()
|
|
68
|
+
|
|
69
|
+
# ── Language / package manager detection ─────────────────────────────
|
|
70
|
+
if [[ -f "package.json" ]]; then
|
|
71
|
+
add_memory "Node.js project (package.json)" "lang,node"
|
|
72
|
+
# Check for specific package managers
|
|
73
|
+
if [[ -f "pnpm-lock.yaml" ]]; then
|
|
74
|
+
add_memory "Uses pnpm as package manager" "tooling,pnpm"
|
|
75
|
+
elif [[ -f "bun.lockb" ]] || [[ -f "bun.lock" ]]; then
|
|
76
|
+
add_memory "Uses bun as package manager" "tooling,bun"
|
|
77
|
+
elif [[ -f "yarn.lock" ]]; then
|
|
78
|
+
add_memory "Uses yarn as package manager" "tooling,yarn"
|
|
79
|
+
elif [[ -f "package-lock.json" ]]; then
|
|
80
|
+
add_memory "Uses npm as package manager" "tooling,npm"
|
|
81
|
+
fi
|
|
82
|
+
# Detect test runner from package.json
|
|
83
|
+
if jq -e '.scripts.test' package.json >/dev/null 2>&1; then
|
|
84
|
+
TEST_CMD=$(jq -r '.scripts.test' package.json)
|
|
85
|
+
if echo "$TEST_CMD" | grep -q "vitest"; then
|
|
86
|
+
add_memory "Uses vitest for testing" "testing,vitest"
|
|
87
|
+
elif echo "$TEST_CMD" | grep -q "jest"; then
|
|
88
|
+
add_memory "Uses jest for testing" "testing,jest"
|
|
89
|
+
elif echo "$TEST_CMD" | grep -q "mocha"; then
|
|
90
|
+
add_memory "Uses mocha for testing" "testing,mocha"
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
# Detect framework from dependencies
|
|
94
|
+
if jq -e '.dependencies.next // .devDependencies.next' package.json >/dev/null 2>&1; then
|
|
95
|
+
add_memory "Next.js framework detected" "framework,nextjs"
|
|
96
|
+
elif jq -e '.dependencies.react // .devDependencies.react' package.json >/dev/null 2>&1; then
|
|
97
|
+
add_memory "React project" "framework,react"
|
|
98
|
+
fi
|
|
99
|
+
if jq -e '.dependencies.express // .devDependencies.express' package.json >/dev/null 2>&1; then
|
|
100
|
+
add_memory "Uses Express.js" "framework,express"
|
|
101
|
+
fi
|
|
102
|
+
# Detect TypeScript
|
|
103
|
+
if [[ -f "tsconfig.json" ]]; then
|
|
104
|
+
add_memory "TypeScript project" "lang,typescript"
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
if [[ -f "go.mod" ]]; then
|
|
109
|
+
MODULE=$(head -1 go.mod | sed 's/^module //')
|
|
110
|
+
add_memory "Go project (module: $MODULE)" "lang,go"
|
|
111
|
+
if [[ -f "go.sum" ]]; then
|
|
112
|
+
add_memory "Go dependencies managed with go modules" "tooling,go"
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [[ -f "Cargo.toml" ]]; then
|
|
117
|
+
add_memory "Rust project (Cargo.toml)" "lang,rust"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ -f "pyproject.toml" ]]; then
|
|
121
|
+
add_memory "Python project (pyproject.toml)" "lang,python"
|
|
122
|
+
if grep -q "pytest" pyproject.toml 2>/dev/null; then
|
|
123
|
+
add_memory "Uses pytest for testing" "testing,pytest"
|
|
124
|
+
fi
|
|
125
|
+
elif [[ -f "setup.py" ]] || [[ -f "setup.cfg" ]]; then
|
|
126
|
+
add_memory "Python project" "lang,python"
|
|
127
|
+
elif [[ -f "requirements.txt" ]]; then
|
|
128
|
+
add_memory "Python project (requirements.txt)" "lang,python"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# ── Build tools ──────────────────────────────────────────────────────
|
|
132
|
+
if [[ -f "Makefile" ]]; then
|
|
133
|
+
add_memory "Has Makefile for build tasks" "tooling,make"
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [[ -f "Dockerfile" ]] || [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
|
|
137
|
+
add_memory "Docker setup present" "tooling,docker"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# ── CI detection ─────────────────────────────────────────────────────
|
|
141
|
+
if [[ -d ".github/workflows" ]]; then
|
|
142
|
+
WF_COUNT=$(find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null | wc -l | tr -d ' ')
|
|
143
|
+
add_memory "GitHub Actions CI ($WF_COUNT workflow files)" "ci,github-actions"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if [[ -f ".gitlab-ci.yml" ]]; then
|
|
147
|
+
add_memory "GitLab CI configured" "ci,gitlab"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# ── Misc ─────────────────────────────────────────────────────────────
|
|
151
|
+
if [[ -f "CLAUDE.md" ]]; then
|
|
152
|
+
add_memory "Has CLAUDE.md project instructions" "config"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if [[ -f ".env.example" ]] || [[ -f ".env.local" ]]; then
|
|
156
|
+
add_memory "Uses environment variables (.env)" "config"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# ── Optional: create CLAUDE.md ───────────────────────────────────────
|
|
160
|
+
if $CREATE_CLAUDE_MD && [[ ! -f "CLAUDE.md" ]]; then
|
|
161
|
+
cat > CLAUDE.md <<'CLAUDEEOF'
|
|
162
|
+
# Project Instructions
|
|
163
|
+
|
|
164
|
+
## Overview
|
|
165
|
+
<!-- Describe your project here -->
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
<!-- Build, test, and run commands -->
|
|
169
|
+
|
|
170
|
+
## Conventions
|
|
171
|
+
<!-- Code style, naming conventions, etc. -->
|
|
172
|
+
CLAUDEEOF
|
|
173
|
+
echo ""
|
|
174
|
+
echo "Created CLAUDE.md (edit to add project instructions)"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
echo ""
|
|
178
|
+
if [[ $ENTRY_COUNT -eq 0 ]]; then
|
|
179
|
+
echo "No project files detected. Memory file not created."
|
|
180
|
+
else
|
|
181
|
+
echo "Wrote $ENTRY_COUNT memory entries to: $MEMORY_FILE"
|
|
182
|
+
fi
|