@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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/VERSION +1 -0
  4. package/bin/attach.sh +98 -0
  5. package/bin/check-agents.sh +343 -0
  6. package/bin/clawforge +257 -0
  7. package/bin/clawforge-dashboard +0 -0
  8. package/bin/clean.sh +257 -0
  9. package/bin/config.sh +111 -0
  10. package/bin/conflicts.sh +224 -0
  11. package/bin/cost.sh +273 -0
  12. package/bin/dashboard.sh +557 -0
  13. package/bin/diff.sh +109 -0
  14. package/bin/doctor.sh +196 -0
  15. package/bin/eval.sh +217 -0
  16. package/bin/history.sh +91 -0
  17. package/bin/init.sh +182 -0
  18. package/bin/learn.sh +230 -0
  19. package/bin/logs.sh +126 -0
  20. package/bin/memory.sh +207 -0
  21. package/bin/merge-helper.sh +174 -0
  22. package/bin/multi-review.sh +215 -0
  23. package/bin/notify.sh +93 -0
  24. package/bin/on-complete.sh +149 -0
  25. package/bin/parse-cost.sh +205 -0
  26. package/bin/pr.sh +167 -0
  27. package/bin/resume.sh +183 -0
  28. package/bin/review-mode.sh +163 -0
  29. package/bin/review-pr.sh +145 -0
  30. package/bin/routing.sh +88 -0
  31. package/bin/scope-task.sh +169 -0
  32. package/bin/spawn-agent.sh +190 -0
  33. package/bin/sprint.sh +320 -0
  34. package/bin/steer.sh +107 -0
  35. package/bin/stop.sh +136 -0
  36. package/bin/summary.sh +182 -0
  37. package/bin/swarm.sh +525 -0
  38. package/bin/templates.sh +244 -0
  39. package/lib/common.sh +302 -0
  40. package/lib/templates/bugfix.json +6 -0
  41. package/lib/templates/migration.json +7 -0
  42. package/lib/templates/refactor.json +6 -0
  43. package/lib/templates/security-audit.json +5 -0
  44. package/lib/templates/test-coverage.json +6 -0
  45. package/package.json +31 -0
  46. package/registry/conflicts.jsonl +0 -0
  47. package/registry/costs.jsonl +0 -0
  48. package/tui/PRD.md +106 -0
  49. package/tui/agent.go +266 -0
  50. package/tui/animation.go +192 -0
  51. package/tui/dashboard.go +219 -0
  52. package/tui/filter.go +68 -0
  53. package/tui/go.mod +25 -0
  54. package/tui/go.sum +46 -0
  55. package/tui/keybindings.go +229 -0
  56. package/tui/main.go +61 -0
  57. package/tui/model.go +166 -0
  58. package/tui/steer.go +69 -0
  59. 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