@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/templates.sh
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# templates.sh — Task templates: pre-configured workflow settings
|
|
3
|
+
# Usage: clawforge templates [list|new|show] [name]
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
source "${SCRIPT_DIR}/../lib/common.sh"
|
|
8
|
+
|
|
9
|
+
BUILTIN_DIR="${CLAWFORGE_DIR}/lib/templates"
|
|
10
|
+
USER_DIR="${HOME}/.clawforge/templates"
|
|
11
|
+
|
|
12
|
+
# ── Help ───────────────────────────────────────────────────────────────
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<EOF
|
|
15
|
+
Usage: clawforge templates [command] [name]
|
|
16
|
+
|
|
17
|
+
Manage task templates for pre-configured workflows.
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
clawforge templates List all available templates
|
|
21
|
+
clawforge templates show <name> Show template details
|
|
22
|
+
clawforge templates new <name> Create a new custom template
|
|
23
|
+
|
|
24
|
+
Template Usage:
|
|
25
|
+
clawforge sprint --template refactor "Refactor auth module"
|
|
26
|
+
clawforge swarm --template migration "Migrate to TypeScript"
|
|
27
|
+
|
|
28
|
+
Flags:
|
|
29
|
+
--json Output as JSON
|
|
30
|
+
--help Show this help
|
|
31
|
+
EOF
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# ── Parse args ─────────────────────────────────────────────────────────
|
|
35
|
+
COMMAND="" NAME="" JSON_OUTPUT=false
|
|
36
|
+
POSITIONAL=()
|
|
37
|
+
|
|
38
|
+
while [[ $# -gt 0 ]]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--json) JSON_OUTPUT=true; shift ;;
|
|
41
|
+
--help|-h) usage; exit 0 ;;
|
|
42
|
+
--*) log_error "Unknown option: $1"; usage; exit 1 ;;
|
|
43
|
+
*) POSITIONAL+=("$1"); shift ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
if [[ ${#POSITIONAL[@]} -gt 0 ]]; then
|
|
48
|
+
COMMAND="${POSITIONAL[0]}"
|
|
49
|
+
fi
|
|
50
|
+
if [[ ${#POSITIONAL[@]} -gt 1 ]]; then
|
|
51
|
+
NAME="${POSITIONAL[1]}"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# ── List templates ─────────────────────────────────────────────────────
|
|
55
|
+
_list_templates() {
|
|
56
|
+
if $JSON_OUTPUT; then
|
|
57
|
+
local result="[]"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if ! $JSON_OUTPUT; then
|
|
61
|
+
echo "=== Available Templates ==="
|
|
62
|
+
echo ""
|
|
63
|
+
echo " Built-in:"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Built-in templates
|
|
67
|
+
if [[ -d "$BUILTIN_DIR" ]]; then
|
|
68
|
+
for f in "$BUILTIN_DIR"/*.json; do
|
|
69
|
+
[[ -f "$f" ]] || continue
|
|
70
|
+
local name desc mode
|
|
71
|
+
name=$(basename "$f" .json)
|
|
72
|
+
desc=$(jq -r '.description // "No description"' "$f" 2>/dev/null)
|
|
73
|
+
mode=$(jq -r '.mode // "—"' "$f" 2>/dev/null)
|
|
74
|
+
if $JSON_OUTPUT; then
|
|
75
|
+
local entry
|
|
76
|
+
entry=$(jq -c --arg name "$name" --arg source "builtin" '. + {name: $name, source: $source}' "$f" 2>/dev/null)
|
|
77
|
+
result=$(echo "$result" | jq --argjson e "$entry" '. += [$e]')
|
|
78
|
+
else
|
|
79
|
+
printf " %-20s %-10s %s\n" "$name" "[$mode]" "$desc"
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# User templates
|
|
85
|
+
if [[ -d "$USER_DIR" ]]; then
|
|
86
|
+
if ! $JSON_OUTPUT; then
|
|
87
|
+
echo ""
|
|
88
|
+
echo " Custom:"
|
|
89
|
+
fi
|
|
90
|
+
local found=false
|
|
91
|
+
for f in "$USER_DIR"/*.json; do
|
|
92
|
+
[[ -f "$f" ]] || continue
|
|
93
|
+
found=true
|
|
94
|
+
local name desc mode
|
|
95
|
+
name=$(basename "$f" .json)
|
|
96
|
+
desc=$(jq -r '.description // "No description"' "$f" 2>/dev/null)
|
|
97
|
+
mode=$(jq -r '.mode // "—"' "$f" 2>/dev/null)
|
|
98
|
+
if $JSON_OUTPUT; then
|
|
99
|
+
local entry
|
|
100
|
+
entry=$(jq -c --arg name "$name" --arg source "custom" '. + {name: $name, source: $source}' "$f" 2>/dev/null)
|
|
101
|
+
result=$(echo "$result" | jq --argjson e "$entry" '. += [$e]')
|
|
102
|
+
else
|
|
103
|
+
printf " %-20s %-10s %s\n" "$name" "[$mode]" "$desc"
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
if ! $found && ! $JSON_OUTPUT; then
|
|
107
|
+
echo " (none — create with: clawforge templates new <name>)"
|
|
108
|
+
fi
|
|
109
|
+
elif ! $JSON_OUTPUT; then
|
|
110
|
+
echo ""
|
|
111
|
+
echo " Custom:"
|
|
112
|
+
echo " (none — create with: clawforge templates new <name>)"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if $JSON_OUTPUT; then
|
|
116
|
+
echo "$result" | jq '.'
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# ── Show template ──────────────────────────────────────────────────────
|
|
121
|
+
_show_template() {
|
|
122
|
+
local name="$1"
|
|
123
|
+
local file=""
|
|
124
|
+
|
|
125
|
+
# Check user templates first, then builtin
|
|
126
|
+
if [[ -f "${USER_DIR}/${name}.json" ]]; then
|
|
127
|
+
file="${USER_DIR}/${name}.json"
|
|
128
|
+
elif [[ -f "${BUILTIN_DIR}/${name}.json" ]]; then
|
|
129
|
+
file="${BUILTIN_DIR}/${name}.json"
|
|
130
|
+
else
|
|
131
|
+
log_error "Template '$name' not found"
|
|
132
|
+
echo "Available templates:"
|
|
133
|
+
_list_templates
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if $JSON_OUTPUT; then
|
|
138
|
+
jq --arg name "$name" '. + {name: $name}' "$file"
|
|
139
|
+
else
|
|
140
|
+
echo "=== Template: $name ==="
|
|
141
|
+
echo ""
|
|
142
|
+
jq -r 'to_entries[] | " \(.key): \(.value)"' "$file" 2>/dev/null
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ── Create new template ───────────────────────────────────────────────
|
|
147
|
+
_new_template() {
|
|
148
|
+
local name="$1"
|
|
149
|
+
mkdir -p "$USER_DIR"
|
|
150
|
+
|
|
151
|
+
local target="${USER_DIR}/${name}.json"
|
|
152
|
+
if [[ -f "$target" ]]; then
|
|
153
|
+
log_error "Template '$name' already exists at $target"
|
|
154
|
+
exit 1
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
echo "Creating template: $name"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
# Interactive prompts (or defaults if non-interactive)
|
|
161
|
+
local mode="sprint" max_agents=3 auto_merge=false ci_loop=false description=""
|
|
162
|
+
|
|
163
|
+
if [[ -t 0 ]]; then
|
|
164
|
+
printf " Mode [sprint/swarm/review] (sprint): "
|
|
165
|
+
read -r mode_input
|
|
166
|
+
[[ -n "$mode_input" ]] && mode="$mode_input"
|
|
167
|
+
|
|
168
|
+
if [[ "$mode" == "swarm" ]]; then
|
|
169
|
+
printf " Max agents (3): "
|
|
170
|
+
read -r agents_input
|
|
171
|
+
[[ -n "$agents_input" ]] && max_agents="$agents_input"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
printf " Auto-merge [true/false] (false): "
|
|
175
|
+
read -r merge_input
|
|
176
|
+
[[ "$merge_input" == "true" ]] && auto_merge=true
|
|
177
|
+
|
|
178
|
+
printf " CI loop [true/false] (false): "
|
|
179
|
+
read -r ci_input
|
|
180
|
+
[[ "$ci_input" == "true" ]] && ci_loop=true
|
|
181
|
+
|
|
182
|
+
printf " Description: "
|
|
183
|
+
read -r description
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# Build template JSON
|
|
187
|
+
local template
|
|
188
|
+
template=$(jq -cn \
|
|
189
|
+
--arg mode "$mode" \
|
|
190
|
+
--argjson maxAgents "$max_agents" \
|
|
191
|
+
--argjson autoMerge "$auto_merge" \
|
|
192
|
+
--argjson ciLoop "$ci_loop" \
|
|
193
|
+
--arg description "${description:-Custom template: $name}" \
|
|
194
|
+
'{
|
|
195
|
+
mode: $mode,
|
|
196
|
+
maxAgents: $maxAgents,
|
|
197
|
+
autoMerge: $autoMerge,
|
|
198
|
+
ciLoop: $ciLoop,
|
|
199
|
+
description: $description
|
|
200
|
+
}')
|
|
201
|
+
|
|
202
|
+
echo "$template" | jq '.' > "$target"
|
|
203
|
+
echo ""
|
|
204
|
+
echo "Template saved: $target"
|
|
205
|
+
echo "$template" | jq '.'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# ── Load template (called from sprint/swarm) ──────────────────────────
|
|
209
|
+
# Usage: source templates.sh && load_template <name>
|
|
210
|
+
# Returns JSON template to stdout
|
|
211
|
+
load_template() {
|
|
212
|
+
local name="$1"
|
|
213
|
+
local file=""
|
|
214
|
+
|
|
215
|
+
if [[ -f "${USER_DIR}/${name}.json" ]]; then
|
|
216
|
+
file="${USER_DIR}/${name}.json"
|
|
217
|
+
elif [[ -f "${BUILTIN_DIR}/${name}.json" ]]; then
|
|
218
|
+
file="${BUILTIN_DIR}/${name}.json"
|
|
219
|
+
else
|
|
220
|
+
log_error "Template '$name' not found"
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
cat "$file"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# ── Route ──────────────────────────────────────────────────────────────
|
|
228
|
+
case "${COMMAND:-list}" in
|
|
229
|
+
list|"")
|
|
230
|
+
_list_templates
|
|
231
|
+
;;
|
|
232
|
+
show)
|
|
233
|
+
[[ -z "$NAME" ]] && { log_error "Template name required"; usage; exit 1; }
|
|
234
|
+
_show_template "$NAME"
|
|
235
|
+
;;
|
|
236
|
+
new)
|
|
237
|
+
[[ -z "$NAME" ]] && { log_error "Template name required"; usage; exit 1; }
|
|
238
|
+
_new_template "$NAME"
|
|
239
|
+
;;
|
|
240
|
+
*)
|
|
241
|
+
# Treat as template name to show
|
|
242
|
+
_show_template "$COMMAND"
|
|
243
|
+
;;
|
|
244
|
+
esac
|
package/lib/common.sh
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# common.sh — Shared functions for clawforge
|
|
3
|
+
# Registry helpers, logging, config
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# ── Paths ──────────────────────────────────────────────────────────────
|
|
8
|
+
CLAWFORGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
REGISTRY_FILE="${CLAWFORGE_DIR}/registry/active-tasks.json"
|
|
10
|
+
CONFIG_FILE="${CLAWFORGE_DIR}/config/defaults.json"
|
|
11
|
+
|
|
12
|
+
# ── Logging ────────────────────────────────────────────────────────────
|
|
13
|
+
log_info() { echo "[INFO] $(date +%H:%M:%S) $*" >&2; }
|
|
14
|
+
log_warn() { echo "[WARN] $(date +%H:%M:%S) $*" >&2; }
|
|
15
|
+
log_error() { echo "[ERROR] $(date +%H:%M:%S) $*" >&2; }
|
|
16
|
+
log_debug() { [[ "${CLAWFORGE_DEBUG:-0}" == "1" ]] && echo "[DEBUG] $(date +%H:%M:%S) $*" >&2 || true; }
|
|
17
|
+
|
|
18
|
+
# ── Config ─────────────────────────────────────────────────────────────
|
|
19
|
+
USER_CONFIG_FILE="${HOME}/.clawforge/config.json"
|
|
20
|
+
|
|
21
|
+
config_get() {
|
|
22
|
+
local key="$1"
|
|
23
|
+
local default="${2:-}"
|
|
24
|
+
# User config takes priority
|
|
25
|
+
if [[ -f "$USER_CONFIG_FILE" ]]; then
|
|
26
|
+
local val
|
|
27
|
+
val=$(jq -r ".$key // empty" "$USER_CONFIG_FILE" 2>/dev/null)
|
|
28
|
+
if [[ -n "$val" ]]; then
|
|
29
|
+
echo "$val"
|
|
30
|
+
return
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
# Fall back to project defaults
|
|
34
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
35
|
+
local val
|
|
36
|
+
val=$(jq -r ".$key // empty" "$CONFIG_FILE" 2>/dev/null)
|
|
37
|
+
if [[ -n "$val" ]]; then
|
|
38
|
+
echo "$val"
|
|
39
|
+
return
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
echo "$default"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
config_set() {
|
|
46
|
+
local key="$1" value="$2"
|
|
47
|
+
mkdir -p "$(dirname "$USER_CONFIG_FILE")"
|
|
48
|
+
if [[ ! -f "$USER_CONFIG_FILE" ]]; then
|
|
49
|
+
echo '{}' > "$USER_CONFIG_FILE"
|
|
50
|
+
fi
|
|
51
|
+
local tmp
|
|
52
|
+
tmp=$(mktemp)
|
|
53
|
+
jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$USER_CONFIG_FILE" > "$tmp" && mv "$tmp" "$USER_CONFIG_FILE"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
config_list() {
|
|
57
|
+
echo "── User config (~/.clawforge/config.json) ──"
|
|
58
|
+
if [[ -f "$USER_CONFIG_FILE" ]]; then
|
|
59
|
+
jq '.' "$USER_CONFIG_FILE"
|
|
60
|
+
else
|
|
61
|
+
echo "(not created yet)"
|
|
62
|
+
fi
|
|
63
|
+
echo ""
|
|
64
|
+
echo "── Project defaults (config/defaults.json) ──"
|
|
65
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
66
|
+
jq '.' "$CONFIG_FILE"
|
|
67
|
+
else
|
|
68
|
+
echo "(not found)"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ── Registry helpers ───────────────────────────────────────────────────
|
|
73
|
+
_ensure_registry() {
|
|
74
|
+
mkdir -p "$(dirname "$REGISTRY_FILE")"
|
|
75
|
+
if [[ ! -f "$REGISTRY_FILE" ]]; then
|
|
76
|
+
echo '{"tasks":[]}' > "$REGISTRY_FILE"
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── Registry file locking ──────────────────────────────────────────────
|
|
82
|
+
REGISTRY_LOCK="${CLAWFORGE_DIR}/registry/.lock"
|
|
83
|
+
|
|
84
|
+
_with_lock() {
|
|
85
|
+
mkdir -p "$(dirname "$REGISTRY_LOCK")"
|
|
86
|
+
local fd=200
|
|
87
|
+
eval "exec ${fd}>"$REGISTRY_LOCK""
|
|
88
|
+
if ! flock -w 5 $fd 2>/dev/null; then
|
|
89
|
+
log_error "Registry lock timeout — another clawforge process may be writing"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_unlock() {
|
|
95
|
+
local fd=200
|
|
96
|
+
flock -u $fd 2>/dev/null || true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
registry_add() {
|
|
100
|
+
local task_json="$1"
|
|
101
|
+
_ensure_registry
|
|
102
|
+
local tmp
|
|
103
|
+
tmp=$(mktemp)
|
|
104
|
+
jq --argjson task "$task_json" '.tasks += [$task]' "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
|
|
105
|
+
log_info "Registry: added task $(echo "$task_json" | jq -r '.id')"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
registry_update() {
|
|
109
|
+
local id="$1" field="$2" value="$3"
|
|
110
|
+
_ensure_registry
|
|
111
|
+
local tmp
|
|
112
|
+
tmp=$(mktemp)
|
|
113
|
+
# Try to parse value as JSON; if it fails, treat as string
|
|
114
|
+
if echo "$value" | jq . >/dev/null 2>&1; then
|
|
115
|
+
jq --arg id "$id" --arg field "$field" --argjson val "$value" \
|
|
116
|
+
'(.tasks[] | select(.id == $id))[$field] = $val' "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
|
|
117
|
+
else
|
|
118
|
+
jq --arg id "$id" --arg field "$field" --arg val "$value" \
|
|
119
|
+
'(.tasks[] | select(.id == $id))[$field] = $val' "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
|
|
120
|
+
fi
|
|
121
|
+
log_debug "Registry: updated $id.$field"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
registry_get() {
|
|
125
|
+
local id="$1"
|
|
126
|
+
_ensure_registry
|
|
127
|
+
jq --arg id "$id" '.tasks[] | select(.id == $id)' "$REGISTRY_FILE"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
registry_list() {
|
|
131
|
+
_ensure_registry
|
|
132
|
+
local status_filter=""
|
|
133
|
+
while [[ $# -gt 0 ]]; do
|
|
134
|
+
case "$1" in
|
|
135
|
+
--status) status_filter="$2"; shift 2 ;;
|
|
136
|
+
*) shift ;;
|
|
137
|
+
esac
|
|
138
|
+
done
|
|
139
|
+
if [[ -n "$status_filter" ]]; then
|
|
140
|
+
jq --arg s "$status_filter" '[.tasks[] | select(.status == $s)]' "$REGISTRY_FILE"
|
|
141
|
+
else
|
|
142
|
+
jq '.tasks' "$REGISTRY_FILE"
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
registry_remove() {
|
|
147
|
+
local id="$1"
|
|
148
|
+
_ensure_registry
|
|
149
|
+
local tmp
|
|
150
|
+
tmp=$(mktemp)
|
|
151
|
+
jq --arg id "$id" '.tasks = [.tasks[] | select(.id != $id)]' "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
|
|
152
|
+
log_info "Registry: removed task $id"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# ── Agent detection ────────────────────────────────────────────────────
|
|
156
|
+
detect_agent() {
|
|
157
|
+
local preferred="${1:-}"
|
|
158
|
+
if [[ -n "$preferred" ]]; then
|
|
159
|
+
if command -v "$preferred" &>/dev/null; then
|
|
160
|
+
echo "$preferred"
|
|
161
|
+
return 0
|
|
162
|
+
else
|
|
163
|
+
log_error "Requested agent '$preferred' not found"
|
|
164
|
+
return 1
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
if command -v claude &>/dev/null; then
|
|
168
|
+
echo "claude"
|
|
169
|
+
elif command -v codex &>/dev/null; then
|
|
170
|
+
echo "codex"
|
|
171
|
+
else
|
|
172
|
+
log_error "No coding agent found (need claude or codex)"
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# ── Short ID management ───────────────────────────────────────────────
|
|
178
|
+
# Sequential short IDs (#1, #2, #3...) mapped in registry
|
|
179
|
+
_next_short_id() {
|
|
180
|
+
_ensure_registry
|
|
181
|
+
local max_id
|
|
182
|
+
max_id=$(jq '[.tasks[].short_id // 0] | max // 0' "$REGISTRY_FILE" 2>/dev/null || echo 0)
|
|
183
|
+
echo $((max_id + 1))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
resolve_task_id() {
|
|
187
|
+
# Accept short ID (#1, 1), sub-agent ID (3.2), or full UUID/slug
|
|
188
|
+
local input="$1"
|
|
189
|
+
_ensure_registry
|
|
190
|
+
|
|
191
|
+
# Strip leading # if present
|
|
192
|
+
input="${input#\#}"
|
|
193
|
+
|
|
194
|
+
# Check if it's a sub-agent reference (e.g., 3.2)
|
|
195
|
+
if [[ "$input" =~ ^([0-9]+)\.([0-9]+)$ ]]; then
|
|
196
|
+
local parent_short="${BASH_REMATCH[1]}"
|
|
197
|
+
local sub_index="${BASH_REMATCH[2]}"
|
|
198
|
+
local parent_id
|
|
199
|
+
parent_id=$(jq -r --argjson sid "$parent_short" '.tasks[] | select(.short_id == $sid) | .id' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
200
|
+
if [[ -n "$parent_id" ]]; then
|
|
201
|
+
# Find sub-agent by parent_id and sub_index
|
|
202
|
+
jq -r --arg pid "$parent_id" --argjson idx "$sub_index" \
|
|
203
|
+
'.tasks[] | select(.parent_id == $pid and .sub_index == $idx) | .id' "$REGISTRY_FILE" 2>/dev/null || true
|
|
204
|
+
return
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Check if it's a numeric short ID
|
|
209
|
+
if [[ "$input" =~ ^[0-9]+$ ]]; then
|
|
210
|
+
local resolved
|
|
211
|
+
resolved=$(jq -r --argjson sid "$input" '.tasks[] | select(.short_id == $sid) | .id' "$REGISTRY_FILE" 2>/dev/null || true)
|
|
212
|
+
if [[ -n "$resolved" ]]; then
|
|
213
|
+
echo "$resolved"
|
|
214
|
+
return
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Fall through: treat as full ID/slug
|
|
219
|
+
echo "$input"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# ── Auto-repo detection ──────────────────────────────────────────────
|
|
223
|
+
detect_repo() {
|
|
224
|
+
# Walk up from cwd (or given path) to find .git
|
|
225
|
+
local start="${1:-$(pwd)}"
|
|
226
|
+
local dir="$start"
|
|
227
|
+
while [[ "$dir" != "/" ]]; do
|
|
228
|
+
if [[ -d "$dir/.git" ]] || [[ -f "$dir/.git" ]]; then
|
|
229
|
+
echo "$dir"
|
|
230
|
+
return 0
|
|
231
|
+
fi
|
|
232
|
+
dir="$(dirname "$dir")"
|
|
233
|
+
done
|
|
234
|
+
log_error "No git repository found from $start"
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# ── Auto-branch naming ───────────────────────────────────────────────
|
|
239
|
+
slugify_task() {
|
|
240
|
+
# Convert task description to a URL-safe branch slug
|
|
241
|
+
local task="$1"
|
|
242
|
+
local max_len="${2:-40}"
|
|
243
|
+
echo "$task" \
|
|
244
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
245
|
+
| sed 's/[^a-z0-9 ]//g' \
|
|
246
|
+
| sed 's/^ *//;s/ *$//' \
|
|
247
|
+
| sed 's/ */ /g' \
|
|
248
|
+
| sed 's/ /-/g' \
|
|
249
|
+
| cut -c1-"$max_len" \
|
|
250
|
+
| sed 's/-$//'
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
auto_branch_name() {
|
|
254
|
+
# Generate branch name with mode prefix and collision detection
|
|
255
|
+
local mode="$1" # sprint, quick, swarm
|
|
256
|
+
local task="$2"
|
|
257
|
+
local repo="${3:-}"
|
|
258
|
+
|
|
259
|
+
local slug
|
|
260
|
+
slug=$(slugify_task "$task")
|
|
261
|
+
local prefix="${mode}/"
|
|
262
|
+
local candidate="${prefix}${slug}"
|
|
263
|
+
|
|
264
|
+
# Collision detection if repo is provided
|
|
265
|
+
if [[ -n "$repo" ]] && [[ -d "$repo/.git" ]]; then
|
|
266
|
+
local attempt=1
|
|
267
|
+
local base="$candidate"
|
|
268
|
+
while git -C "$repo" show-ref --verify --quiet "refs/heads/$candidate" 2>/dev/null; do
|
|
269
|
+
attempt=$((attempt + 1))
|
|
270
|
+
candidate="${base}-${attempt}"
|
|
271
|
+
done
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
echo "$candidate"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# ── Utilities ──────────────────────────────────────────────────────────
|
|
278
|
+
sanitize_branch() {
|
|
279
|
+
echo "$1" | sed 's|/|-|g' | sed 's|[^a-zA-Z0-9_-]|-|g'
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
epoch_ms() {
|
|
283
|
+
python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null || echo "$(date +%s)000"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# ── Disk space check ──────────────────────────────────────────────────
|
|
287
|
+
disk_check() {
|
|
288
|
+
local dir="${1:-.}"
|
|
289
|
+
local warn_gb="${2:-5}"
|
|
290
|
+
local error_gb="${3:-1}"
|
|
291
|
+
local avail_kb
|
|
292
|
+
avail_kb=$(df -k "$dir" 2>/dev/null | awk 'NR==2{print $4}')
|
|
293
|
+
[[ -z "$avail_kb" ]] && return 0
|
|
294
|
+
local avail_gb=$((avail_kb / 1048576))
|
|
295
|
+
if [[ $avail_gb -lt $error_gb ]]; then
|
|
296
|
+
log_error "Disk space critically low: ${avail_gb}GB free (need ${error_gb}GB minimum)"
|
|
297
|
+
return 1
|
|
298
|
+
elif [[ $avail_gb -lt $warn_gb ]]; then
|
|
299
|
+
log_warn "Disk space low: ${avail_gb}GB free (warning threshold: ${warn_gb}GB)"
|
|
300
|
+
fi
|
|
301
|
+
return 0
|
|
302
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cyperx/clawforge",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Multi-mode coding workflow CLI for orchestrating AI coding agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"@cyperx/clawforge": "./bin/clawforge"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "echo 'ClawForge installed. Run: clawforge help'"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/cyperx84/clawforge.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["cli", "coding", "agents", "orchestration", "tmux", "worktree"],
|
|
16
|
+
"author": "cyperx84",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"os": ["darwin", "linux"],
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/",
|
|
24
|
+
"lib/",
|
|
25
|
+
"tui/",
|
|
26
|
+
"registry/",
|
|
27
|
+
"VERSION",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
File without changes
|
|
File without changes
|