clawpowers 1.0.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 (42) hide show
  1. package/.claude-plugin/manifest.json +19 -0
  2. package/.codex/INSTALL.md +36 -0
  3. package/.cursor-plugin/manifest.json +21 -0
  4. package/.opencode/INSTALL.md +52 -0
  5. package/ARCHITECTURE.md +69 -0
  6. package/README.md +381 -0
  7. package/bin/clawpowers.js +390 -0
  8. package/bin/clawpowers.sh +91 -0
  9. package/gemini-extension.json +32 -0
  10. package/hooks/session-start +205 -0
  11. package/hooks/session-start.cmd +43 -0
  12. package/hooks/session-start.js +163 -0
  13. package/package.json +54 -0
  14. package/runtime/feedback/analyze.js +621 -0
  15. package/runtime/feedback/analyze.sh +546 -0
  16. package/runtime/init.js +172 -0
  17. package/runtime/init.sh +145 -0
  18. package/runtime/metrics/collector.js +361 -0
  19. package/runtime/metrics/collector.sh +308 -0
  20. package/runtime/persistence/store.js +433 -0
  21. package/runtime/persistence/store.sh +303 -0
  22. package/skill.json +74 -0
  23. package/skills/agent-payments/SKILL.md +411 -0
  24. package/skills/brainstorming/SKILL.md +233 -0
  25. package/skills/content-pipeline/SKILL.md +282 -0
  26. package/skills/dispatching-parallel-agents/SKILL.md +305 -0
  27. package/skills/executing-plans/SKILL.md +255 -0
  28. package/skills/finishing-a-development-branch/SKILL.md +260 -0
  29. package/skills/learn-how-to-learn/SKILL.md +235 -0
  30. package/skills/market-intelligence/SKILL.md +288 -0
  31. package/skills/prospecting/SKILL.md +313 -0
  32. package/skills/receiving-code-review/SKILL.md +225 -0
  33. package/skills/requesting-code-review/SKILL.md +206 -0
  34. package/skills/security-audit/SKILL.md +308 -0
  35. package/skills/subagent-driven-development/SKILL.md +244 -0
  36. package/skills/systematic-debugging/SKILL.md +279 -0
  37. package/skills/test-driven-development/SKILL.md +299 -0
  38. package/skills/using-clawpowers/SKILL.md +137 -0
  39. package/skills/using-git-worktrees/SKILL.md +261 -0
  40. package/skills/verification-before-completion/SKILL.md +254 -0
  41. package/skills/writing-plans/SKILL.md +276 -0
  42. package/skills/writing-skills/SKILL.md +260 -0
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env bash
2
+ # runtime/metrics/collector.sh — Skill execution outcome tracking
3
+ #
4
+ # Appends one JSON line per skill execution to ~/.clawpowers/metrics/YYYY-MM.jsonl
5
+ # Each line records: skill name, timestamp, duration, outcome, and notes.
6
+ #
7
+ # Usage:
8
+ # collector.sh record --skill <name> --outcome <success|failure|partial|skipped> [options]
9
+ # collector.sh show [--skill <name>] [--limit <n>]
10
+ # collector.sh summary [--skill <name>]
11
+ #
12
+ # Options for record:
13
+ # --skill <name> Skill name (required)
14
+ # --outcome <result> success, failure, partial, or skipped (required)
15
+ # --duration <seconds> Execution duration in seconds (optional)
16
+ # --notes <text> Free-text notes about this execution (optional)
17
+ # --session-id <id> Session identifier for grouping (optional)
18
+ #
19
+ # Output format (one JSON line per execution):
20
+ # {"ts":"ISO8601","skill":"name","outcome":"success","duration_s":47,"notes":"...","session":"..."}
21
+ set -euo pipefail
22
+
23
+ ## === Configuration ===
24
+
25
+ # Metrics directory — override parent with CLAWPOWERS_DIR env var for testing
26
+ METRICS_DIR="${CLAWPOWERS_DIR:-$HOME/.clawpowers}/metrics"
27
+
28
+ ## === Internal Utilities ===
29
+
30
+ # Creates the metrics directory if it doesn't already exist.
31
+ # Mode 700 ensures log files are accessible only to the current user.
32
+ ensure_dir() {
33
+ if [[ ! -d "$METRICS_DIR" ]]; then
34
+ mkdir -p "$METRICS_DIR"
35
+ chmod 700 "$METRICS_DIR"
36
+ fi
37
+ }
38
+
39
+ # Returns the path to the current month's JSONL log file.
40
+ # Files are named YYYY-MM.jsonl and rotated automatically each month.
41
+ # Monthly rotation keeps individual files manageable without any cleanup overhead.
42
+ current_logfile() {
43
+ local month
44
+ month=$(date +%Y-%m)
45
+ echo "$METRICS_DIR/${month}.jsonl"
46
+ }
47
+
48
+ # Escapes a string for safe embedding in a JSON double-quoted value.
49
+ # Order matters: backslashes must be escaped before quotes, then control characters.
50
+ json_string() {
51
+ local s="$1"
52
+ s="${s//\\/\\\\}" # Escape backslashes first (must be before quote escaping)
53
+ s="${s//\"/\\\"}" # Escape double quotes
54
+ s="${s//$'\n'/\\n}" # Escape newlines (notes may span multiple lines)
55
+ s="${s//$'\r'/\\r}" # Escape carriage returns (Windows line endings)
56
+ s="${s//$'\t'/\\t}" # Escape tabs
57
+ echo "$s"
58
+ }
59
+
60
+ ## === Command Implementations ===
61
+
62
+ # record — Append one JSONL record to the current month's log file.
63
+ # Parses --flag value style arguments and validates required fields before writing.
64
+ cmd_record() {
65
+ local skill="" outcome="" duration="" notes="" session_id=""
66
+
67
+ ## --- Argument Parsing ---
68
+ # Parse --key value style flags; reject unknown arguments
69
+ while [[ $# -gt 0 ]]; do
70
+ case "$1" in
71
+ --skill) skill="$2"; shift 2 ;;
72
+ --outcome) outcome="$2"; shift 2 ;;
73
+ --duration) duration="$2"; shift 2 ;;
74
+ --notes) notes="$2"; shift 2 ;;
75
+ --session-id) session_id="$2"; shift 2 ;;
76
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
77
+ esac
78
+ done
79
+
80
+ ## --- Validation ---
81
+ if [[ -z "$skill" ]]; then
82
+ echo "Error: --skill is required" >&2
83
+ exit 1
84
+ fi
85
+ if [[ -z "$outcome" ]]; then
86
+ echo "Error: --outcome is required (success|failure|partial|skipped)" >&2
87
+ exit 1
88
+ fi
89
+ # Only allow the three defined outcome values to maintain data consistency
90
+ if [[ ! "$outcome" =~ ^(success|failure|partial|skipped)$ ]]; then
91
+ echo "Error: --outcome must be success, failure, or partial" >&2
92
+ exit 1
93
+ fi
94
+ # Duration must be a non-negative number (integer or decimal seconds)
95
+ if [[ -n "$duration" ]] && ! [[ "$duration" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
96
+ echo "Error: --duration must be a number (seconds)" >&2
97
+ exit 1
98
+ fi
99
+
100
+ ensure_dir
101
+
102
+ local ts
103
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
104
+
105
+ ## --- Optional JSON Field Fragments ---
106
+ # Build optional field strings only when values are present; empty fields
107
+ # are omitted entirely from the JSON record to keep file sizes minimal
108
+
109
+ local session_part=""
110
+ if [[ -n "$session_id" ]]; then
111
+ session_part=',"session":"'"$(json_string "$session_id")"'"'
112
+ fi
113
+
114
+ local duration_part=""
115
+ if [[ -n "$duration" ]]; then
116
+ # duration_s is a JSON number, not a string — no quotes around the value
117
+ duration_part=',"duration_s":'"$duration"
118
+ fi
119
+
120
+ local notes_part=""
121
+ if [[ -n "$notes" ]]; then
122
+ notes_part=',"notes":"'"$(json_string "$notes")"'"'
123
+ fi
124
+
125
+ local logfile
126
+ logfile=$(current_logfile)
127
+
128
+ # Construct the JSON line manually (no jq dependency — pure bash string ops)
129
+ local json_line="{\"ts\":\"${ts}\",\"skill\":\"$(json_string "$skill")\",\"outcome\":\"${outcome}\"${duration_part}${notes_part}${session_part}}"
130
+
131
+ # appendFileSync equivalent — each append is a complete JSON line
132
+ echo "$json_line" >> "$logfile"
133
+ # Restrict log file permissions to owner-only after every write
134
+ chmod 600 "$logfile"
135
+
136
+ echo "Recorded: $skill → $outcome ($(basename "$logfile"))"
137
+ }
138
+
139
+ # show — Print recent execution records as raw JSON lines to stdout.
140
+ # Supports optional skill filter (--skill) and record count limit (--limit).
141
+ cmd_show() {
142
+ local skill_filter="" limit=20
143
+
144
+ while [[ $# -gt 0 ]]; do
145
+ case "$1" in
146
+ --skill) skill_filter="$2"; shift 2 ;;
147
+ --limit) limit="$2"; shift 2 ;;
148
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
149
+ esac
150
+ done
151
+
152
+ ensure_dir
153
+
154
+ ## --- Read All Records Into Array ---
155
+ # Collect all JSONL files sorted by filename (YYYY-MM.jsonl = chronological)
156
+ local lines=()
157
+ for f in "$METRICS_DIR"/*.jsonl; do
158
+ [[ -f "$f" ]] || continue # Skip if glob expands to literal string (no files)
159
+ while IFS= read -r line; do
160
+ [[ -z "$line" ]] && continue # Skip blank separator lines
161
+ if [[ -n "$skill_filter" ]]; then
162
+ # Grep-based filter avoids jq dependency — exact JSON field match
163
+ echo "$line" | grep -q "\"skill\":\"${skill_filter}\"" || continue
164
+ fi
165
+ lines+=("$line")
166
+ done < "$f"
167
+ done
168
+
169
+ ## --- Output Last N Records ---
170
+ # Tail semantics: show the most recent `limit` records
171
+ local total=${#lines[@]}
172
+ local start=$((total - limit))
173
+ [[ $start -lt 0 ]] && start=0
174
+
175
+ for ((i=start; i<total; i++)); do
176
+ echo "${lines[$i]}"
177
+ done
178
+ }
179
+
180
+ # summary — Print aggregated statistics across all recorded executions.
181
+ # Uses awk for JSON field extraction without requiring jq or python.
182
+ # When no skill filter is provided, also shows a per-skill breakdown.
183
+ cmd_summary() {
184
+ local skill_filter=""
185
+
186
+ while [[ $# -gt 0 ]]; do
187
+ case "$1" in
188
+ --skill) skill_filter="$2"; shift 2 ;;
189
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
190
+ esac
191
+ done
192
+
193
+ ensure_dir
194
+
195
+ ## --- Collect All Matching Records ---
196
+ # Accumulate all matching JSON lines into a single variable for awk processing
197
+ local all_lines=""
198
+ for f in "$METRICS_DIR"/*.jsonl; do
199
+ [[ -f "$f" ]] || continue
200
+ while IFS= read -r line; do
201
+ [[ -z "$line" ]] && continue
202
+ if [[ -n "$skill_filter" ]]; then
203
+ echo "$line" | grep -q "\"skill\":\"${skill_filter}\"" || continue
204
+ fi
205
+ all_lines+="$line"$'\n'
206
+ done < "$f"
207
+ done
208
+
209
+ if [[ -z "$all_lines" ]]; then
210
+ echo "No metrics recorded${skill_filter:+ for skill: $skill_filter}"
211
+ return 0
212
+ fi
213
+
214
+ ## --- Compute and Print Stats via awk ---
215
+ # Pure awk JSON parsing — extracts outcome counts, duration averages, and skill breakdown
216
+ # without any external dependencies beyond the POSIX awk that's on every Unix system.
217
+ echo "$all_lines" | awk '
218
+ BEGIN {
219
+ total = 0; success = 0; failure = 0; partial = 0
220
+ total_duration = 0; duration_count = 0
221
+ split("", skill_counts)
222
+ }
223
+ # Count outcomes by matching the JSON "outcome" field value
224
+ /\"outcome\":\"success\"/ { success++ }
225
+ /\"outcome\":\"failure\"/ { failure++ }
226
+ /\"outcome\":\"partial\"/ { partial++ }
227
+ # Extract duration_s numeric value using string operations (no regex group captures in awk)
228
+ /\"duration_s\":/ {
229
+ p = index($0, "\"duration_s\":")
230
+ if (p > 0) {
231
+ rest = substr($0, p + 13)
232
+ val = rest + 0
233
+ if (val > 0 || substr(rest, 1, 1) == "0") {
234
+ total_duration += val
235
+ duration_count++
236
+ }
237
+ }
238
+ }
239
+ # Extract skill name for the per-skill breakdown table
240
+ /\"skill\":/ {
241
+ p = index($0, "\"skill\":\"")
242
+ if (p > 0) {
243
+ rest = substr($0, p + 9)
244
+ q = index(rest, "\"")
245
+ if (q > 0) skill_counts[substr(rest, 1, q - 1)]++
246
+ }
247
+ }
248
+ { total++ }
249
+ END {
250
+ print "Total executions:", total
251
+ print " Success:", success, "(" int(success/total*100+0.5) "%)"
252
+ print " Failure:", failure, "(" int(failure/total*100+0.5) "%)"
253
+ print " Partial:", partial, "(" int(partial/total*100+0.5) "%)"
254
+ if (duration_count > 0) {
255
+ print "Avg duration:", int(total_duration/duration_count+0.5) "s"
256
+ }
257
+ # Show skill breakdown only when no skill filter was applied
258
+ if (!'"$([ -n "$skill_filter" ] && echo 1 || echo 0)"') {
259
+ print "\nSkill breakdown:"
260
+ for (s in skill_counts) {
261
+ print " " s ": " skill_counts[s]
262
+ }
263
+ }
264
+ }
265
+ '
266
+ }
267
+
268
+ ## === Usage ===
269
+
270
+ cmd_usage() {
271
+ cat << 'EOF'
272
+ Usage: collector.sh <command> [options]
273
+
274
+ Commands:
275
+ record Record a skill execution outcome
276
+ show Show recent execution records
277
+ summary Show aggregated statistics
278
+
279
+ record options:
280
+ --skill <name> Skill name (required)
281
+ --outcome <result> success | failure | partial (required)
282
+ --duration <seconds> Execution time in seconds
283
+ --notes <text> Notes about this execution
284
+ --session-id <id> Session identifier
285
+
286
+ Examples:
287
+ collector.sh record --skill systematic-debugging --outcome success --duration 1800 \
288
+ --notes "payment-pool: 3 hypotheses, root cause found in git bisect"
289
+
290
+ collector.sh show --skill test-driven-development --limit 10
291
+
292
+ collector.sh summary
293
+ collector.sh summary --skill systematic-debugging
294
+ EOF
295
+ }
296
+
297
+ ## === Main Dispatch ===
298
+
299
+ # Route the first positional argument to the appropriate command function.
300
+ # Arguments after the command name are forwarded with `shift` before each call.
301
+ case "${1:-}" in
302
+ record) shift; cmd_record "$@" ;;
303
+ show) shift; cmd_show "$@" ;;
304
+ summary) shift; cmd_summary "$@" ;;
305
+ help|-h|--help) cmd_usage ;;
306
+ "") cmd_usage; exit 1 ;;
307
+ *) echo "Unknown command: $1"; cmd_usage; exit 1 ;;
308
+ esac