singularity-claude 0.1.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/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +21 -0
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/agents/gap-detector.md +74 -0
- package/agents/skill-assessor.md +55 -0
- package/hooks/hooks.json +16 -0
- package/hooks/run-hook.cmd +5 -0
- package/hooks/session-start +97 -0
- package/package.json +29 -0
- package/scripts/score-manager.sh +301 -0
- package/scripts/telemetry-writer.sh +191 -0
- package/skills/creating-skills/SKILL.md +113 -0
- package/skills/creating-skills/references/scoring-template.json +17 -0
- package/skills/creating-skills/references/skill-template.md +36 -0
- package/skills/crystallizing/SKILL.md +91 -0
- package/skills/dashboard/SKILL.md +67 -0
- package/skills/repairing/SKILL.md +109 -0
- package/skills/reviewing/SKILL.md +85 -0
- package/skills/scoring/SKILL.md +81 -0
- package/skills/scoring/references/scoring-rubric.md +75 -0
- package/skills/using-singularity/SKILL.md +60 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# score-manager.sh — CLI for managing singularity skill scores
|
|
3
|
+
# Uses jq if available, falls back to node -e for JSON manipulation
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SINGULARITY_DATA="${HOME}/.claude/singularity"
|
|
8
|
+
SCORES_DIR="${SINGULARITY_DATA}/scores"
|
|
9
|
+
|
|
10
|
+
# JSON tool detection
|
|
11
|
+
json_tool=""
|
|
12
|
+
if command -v jq &>/dev/null; then
|
|
13
|
+
json_tool="jq"
|
|
14
|
+
elif command -v node &>/dev/null; then
|
|
15
|
+
json_tool="node"
|
|
16
|
+
else
|
|
17
|
+
echo "Error: Neither jq nor node found. Install one to use score-manager." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Atomic write: write to temp file, then rename
|
|
22
|
+
atomic_write() {
|
|
23
|
+
local target="$1"
|
|
24
|
+
local content="$2"
|
|
25
|
+
local tmp="${target}.tmp.$$"
|
|
26
|
+
printf '%s' "$content" > "$tmp"
|
|
27
|
+
mv "$tmp" "$target"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Read JSON file and extract with jq or node
|
|
31
|
+
json_query() {
|
|
32
|
+
local file="$1"
|
|
33
|
+
local query="$2"
|
|
34
|
+
if [ "$json_tool" = "jq" ]; then
|
|
35
|
+
jq -r "$query" "$file"
|
|
36
|
+
else
|
|
37
|
+
node -e "const d=JSON.parse(require('fs').readFileSync('${file}','utf8')); const q=${query}; console.log(typeof q==='object'?JSON.stringify(q,null,2):q)"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
usage() {
|
|
42
|
+
cat <<'EOF'
|
|
43
|
+
Usage: score-manager.sh <command> <skill-name> [options]
|
|
44
|
+
|
|
45
|
+
Commands:
|
|
46
|
+
init <skill> Create score file for a new skill
|
|
47
|
+
add <skill> <score> Record a score (0-100)
|
|
48
|
+
[--version <ver>] Version (auto-detected from git tag or v1.0.0)
|
|
49
|
+
[--context <text>] What the skill was used for
|
|
50
|
+
[--strengths <json-array>] What went well
|
|
51
|
+
[--weaknesses <json-array>] What needs improvement
|
|
52
|
+
[--edge-cases <json-array>] Edge cases encountered
|
|
53
|
+
list <skill> Show score history
|
|
54
|
+
average <skill> [--version <v>] Get average score
|
|
55
|
+
trend <skill> Show score trend across versions
|
|
56
|
+
maturity <skill> Get current maturity level
|
|
57
|
+
EOF
|
|
58
|
+
exit 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
cmd_init() {
|
|
62
|
+
local skill="$1"
|
|
63
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
64
|
+
|
|
65
|
+
if [ -f "$file" ]; then
|
|
66
|
+
echo "Score file already exists for '${skill}'" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
mkdir -p "${SCORES_DIR}"
|
|
71
|
+
local now
|
|
72
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
73
|
+
|
|
74
|
+
local content
|
|
75
|
+
content=$(cat <<INIT
|
|
76
|
+
{
|
|
77
|
+
"\$schema": "singularity-score-v1",
|
|
78
|
+
"skillName": "${skill}",
|
|
79
|
+
"versions": [
|
|
80
|
+
{
|
|
81
|
+
"version": "v1.0.0",
|
|
82
|
+
"gitTag": "singularity/${skill}/v1.0.0",
|
|
83
|
+
"scores": [],
|
|
84
|
+
"averageScore": 0,
|
|
85
|
+
"executionCount": 0,
|
|
86
|
+
"maturity": "draft"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"currentVersion": "v1.0.0",
|
|
90
|
+
"createdAt": "${now}",
|
|
91
|
+
"lastScoredAt": null
|
|
92
|
+
}
|
|
93
|
+
INIT
|
|
94
|
+
)
|
|
95
|
+
atomic_write "$file" "$content"
|
|
96
|
+
echo "Initialized score file for '${skill}'"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cmd_add() {
|
|
100
|
+
local skill="$1"
|
|
101
|
+
local score="$2"
|
|
102
|
+
shift 2
|
|
103
|
+
|
|
104
|
+
# Parse optional args
|
|
105
|
+
local version="" context="" strengths="[]" weaknesses="[]" edge_cases="[]"
|
|
106
|
+
while [ $# -gt 0 ]; do
|
|
107
|
+
case "$1" in
|
|
108
|
+
--version) version="$2"; shift 2 ;;
|
|
109
|
+
--context) context="$2"; shift 2 ;;
|
|
110
|
+
--strengths) strengths="$2"; shift 2 ;;
|
|
111
|
+
--weaknesses) weaknesses="$2"; shift 2 ;;
|
|
112
|
+
--edge-cases) edge_cases="$2"; shift 2 ;;
|
|
113
|
+
*) shift ;;
|
|
114
|
+
esac
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
# Validate score
|
|
118
|
+
if ! [[ "$score" =~ ^[0-9]+$ ]] || [ "$score" -lt 0 ] || [ "$score" -gt 100 ]; then
|
|
119
|
+
echo "Error: Score must be 0-100" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
124
|
+
if [ ! -f "$file" ]; then
|
|
125
|
+
echo "Error: No score file for '${skill}'. Run 'init' first." >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
local now
|
|
130
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
131
|
+
|
|
132
|
+
# Use current version if not specified
|
|
133
|
+
if [ -z "$version" ]; then
|
|
134
|
+
version=$(jq -r '.currentVersion' "$file" 2>/dev/null || echo "v1.0.0")
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ "$json_tool" = "jq" ]; then
|
|
138
|
+
local updated
|
|
139
|
+
updated=$(jq --arg ver "$version" --argjson score "$score" --arg ts "$now" \
|
|
140
|
+
--arg ctx "$context" --argjson str "$strengths" --argjson weak "$weaknesses" \
|
|
141
|
+
--argjson edge "$edge_cases" '
|
|
142
|
+
.lastScoredAt = $ts |
|
|
143
|
+
(.versions[] | select(.version == $ver)) |= (
|
|
144
|
+
.scores += [{
|
|
145
|
+
"timestamp": $ts,
|
|
146
|
+
"score": $score,
|
|
147
|
+
"context": $ctx,
|
|
148
|
+
"strengths": $str,
|
|
149
|
+
"weaknesses": $weak,
|
|
150
|
+
"edgeCasesEncountered": $edge
|
|
151
|
+
}] |
|
|
152
|
+
.executionCount = (.scores | length) |
|
|
153
|
+
.averageScore = ((.scores | map(.score) | add) / (.scores | length) | floor)
|
|
154
|
+
)
|
|
155
|
+
' "$file")
|
|
156
|
+
atomic_write "$file" "$updated"
|
|
157
|
+
else
|
|
158
|
+
node -e "
|
|
159
|
+
const fs = require('fs');
|
|
160
|
+
const d = JSON.parse(fs.readFileSync('${file}', 'utf8'));
|
|
161
|
+
const v = d.versions.find(v => v.version === '${version}');
|
|
162
|
+
if (!v) { console.error('Version not found'); process.exit(1); }
|
|
163
|
+
v.scores.push({
|
|
164
|
+
timestamp: '${now}', score: ${score}, context: '${context}',
|
|
165
|
+
strengths: ${strengths}, weaknesses: ${weaknesses},
|
|
166
|
+
edgeCasesEncountered: ${edge_cases}
|
|
167
|
+
});
|
|
168
|
+
v.executionCount = v.scores.length;
|
|
169
|
+
v.averageScore = Math.floor(v.scores.reduce((s,e) => s + e.score, 0) / v.scores.length);
|
|
170
|
+
d.lastScoredAt = '${now}';
|
|
171
|
+
fs.writeFileSync('${file}', JSON.stringify(d, null, 2));
|
|
172
|
+
"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Compute and update maturity
|
|
176
|
+
_update_maturity "$skill" "$version"
|
|
177
|
+
|
|
178
|
+
# Show result
|
|
179
|
+
local avg
|
|
180
|
+
avg=$(jq -r --arg ver "$version" '.versions[] | select(.version == $ver) | .averageScore' "$file" 2>/dev/null || echo "?")
|
|
181
|
+
local count
|
|
182
|
+
count=$(jq -r --arg ver "$version" '.versions[] | select(.version == $ver) | .executionCount' "$file" 2>/dev/null || echo "?")
|
|
183
|
+
echo "Recorded score ${score} for ${skill} ${version} (avg: ${avg}/100, ${count} runs)"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_update_maturity() {
|
|
187
|
+
local skill="$1"
|
|
188
|
+
local version="$2"
|
|
189
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
190
|
+
|
|
191
|
+
if [ "$json_tool" = "jq" ]; then
|
|
192
|
+
local updated
|
|
193
|
+
updated=$(jq --arg ver "$version" '
|
|
194
|
+
(.versions[] | select(.version == $ver)) |= (
|
|
195
|
+
.maturity = (
|
|
196
|
+
if .maturity == "crystallized" then "crystallized"
|
|
197
|
+
elif .executionCount >= 5 and .averageScore >= 80 and (.scores | map(.edgeCasesEncountered // []) | flatten | length) > 0 then "hardened"
|
|
198
|
+
elif .executionCount >= 3 and .averageScore >= 60 then "tested"
|
|
199
|
+
else "draft"
|
|
200
|
+
end
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
' "$file")
|
|
204
|
+
atomic_write "$file" "$updated"
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
cmd_list() {
|
|
209
|
+
local skill="$1"
|
|
210
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
211
|
+
|
|
212
|
+
if [ ! -f "$file" ]; then
|
|
213
|
+
echo "No score file for '${skill}'" >&2
|
|
214
|
+
exit 1
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
if [ "$json_tool" = "jq" ]; then
|
|
218
|
+
jq -r '
|
|
219
|
+
.versions[] |
|
|
220
|
+
"Version: \(.version) | Maturity: \(.maturity) | Avg: \(.averageScore)/100 | Runs: \(.executionCount)",
|
|
221
|
+
(.scores[] | " [\(.timestamp)] Score: \(.score) — \(.context // "no context")")
|
|
222
|
+
' "$file"
|
|
223
|
+
else
|
|
224
|
+
node -e "
|
|
225
|
+
const d = JSON.parse(require('fs').readFileSync('${file}', 'utf8'));
|
|
226
|
+
d.versions.forEach(v => {
|
|
227
|
+
console.log('Version: ' + v.version + ' | Maturity: ' + v.maturity + ' | Avg: ' + v.averageScore + '/100 | Runs: ' + v.executionCount);
|
|
228
|
+
v.scores.forEach(s => console.log(' [' + s.timestamp + '] Score: ' + s.score + ' — ' + (s.context || 'no context')));
|
|
229
|
+
});
|
|
230
|
+
"
|
|
231
|
+
fi
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
cmd_average() {
|
|
235
|
+
local skill="$1"
|
|
236
|
+
shift
|
|
237
|
+
local version=""
|
|
238
|
+
while [ $# -gt 0 ]; do
|
|
239
|
+
case "$1" in
|
|
240
|
+
--version) version="$2"; shift 2 ;;
|
|
241
|
+
*) shift ;;
|
|
242
|
+
esac
|
|
243
|
+
done
|
|
244
|
+
|
|
245
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
246
|
+
if [ ! -f "$file" ]; then
|
|
247
|
+
echo "No score file for '${skill}'" >&2
|
|
248
|
+
exit 1
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
if [ -n "$version" ]; then
|
|
252
|
+
jq -r --arg ver "$version" '.versions[] | select(.version == $ver) | .averageScore' "$file"
|
|
253
|
+
else
|
|
254
|
+
jq -r '.versions[-1].averageScore' "$file"
|
|
255
|
+
fi
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
cmd_trend() {
|
|
259
|
+
local skill="$1"
|
|
260
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
261
|
+
|
|
262
|
+
if [ ! -f "$file" ]; then
|
|
263
|
+
echo "No score file for '${skill}'" >&2
|
|
264
|
+
exit 1
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
jq -r '
|
|
268
|
+
.versions | to_entries[] |
|
|
269
|
+
"\(.value.version)\t\(.value.averageScore)\t\(.value.executionCount)\t\(.value.maturity)"
|
|
270
|
+
' "$file" | while IFS=$'\t' read -r ver avg count mat; do
|
|
271
|
+
printf "%-10s avg: %3s/100 runs: %s maturity: %s\n" "$ver" "$avg" "$count" "$mat"
|
|
272
|
+
done
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
cmd_maturity() {
|
|
276
|
+
local skill="$1"
|
|
277
|
+
local file="${SCORES_DIR}/${skill}.json"
|
|
278
|
+
|
|
279
|
+
if [ ! -f "$file" ]; then
|
|
280
|
+
echo "No score file for '${skill}'" >&2
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
jq -r '.versions[-1].maturity' "$file"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Main dispatch
|
|
288
|
+
[ $# -lt 2 ] && usage
|
|
289
|
+
|
|
290
|
+
cmd="$1"
|
|
291
|
+
shift
|
|
292
|
+
|
|
293
|
+
case "$cmd" in
|
|
294
|
+
init) cmd_init "$@" ;;
|
|
295
|
+
add) cmd_add "$@" ;;
|
|
296
|
+
list) cmd_list "$@" ;;
|
|
297
|
+
average) cmd_average "$@" ;;
|
|
298
|
+
trend) cmd_trend "$@" ;;
|
|
299
|
+
maturity) cmd_maturity "$@" ;;
|
|
300
|
+
*) usage ;;
|
|
301
|
+
esac
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# telemetry-writer.sh — CLI for singularity skill telemetry
|
|
3
|
+
# Writes structured JSON logs for skill execution auditing
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SINGULARITY_DATA="${HOME}/.claude/singularity"
|
|
8
|
+
TELEMETRY_DIR="${SINGULARITY_DATA}/telemetry"
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<'EOF'
|
|
12
|
+
Usage: telemetry-writer.sh <command> <skill-name> [options]
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
log <skill> [options] Record a telemetry entry
|
|
16
|
+
--trigger <type> How the skill was invoked (user-invoked, auto-repair, gap-detected)
|
|
17
|
+
--version <ver> Skill version (default: from registry)
|
|
18
|
+
--summary <text> What happened
|
|
19
|
+
--score <n> Score if available
|
|
20
|
+
--error <text> Error message if failed
|
|
21
|
+
--edge-case <text> Edge case encountered
|
|
22
|
+
--files-created <json-array> Files created
|
|
23
|
+
--files-modified <json-array> Files modified
|
|
24
|
+
--duration <ms> Execution duration in milliseconds
|
|
25
|
+
|
|
26
|
+
list <skill> [--last <n>] Show recent telemetry entries (default: 10)
|
|
27
|
+
|
|
28
|
+
replay <skill> <timestamp> Show full telemetry entry
|
|
29
|
+
|
|
30
|
+
prune [--days <n>] Remove entries older than n days (default: 90)
|
|
31
|
+
EOF
|
|
32
|
+
exit 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
cmd_log() {
|
|
36
|
+
local skill="$1"
|
|
37
|
+
shift
|
|
38
|
+
|
|
39
|
+
# Parse options
|
|
40
|
+
local trigger="user-invoked" version="" summary="" score="" error="" edge_case=""
|
|
41
|
+
local files_created="[]" files_modified="[]" duration="0"
|
|
42
|
+
|
|
43
|
+
while [ $# -gt 0 ]; do
|
|
44
|
+
case "$1" in
|
|
45
|
+
--trigger) trigger="$2"; shift 2 ;;
|
|
46
|
+
--version) version="$2"; shift 2 ;;
|
|
47
|
+
--summary) summary="$2"; shift 2 ;;
|
|
48
|
+
--score) score="$2"; shift 2 ;;
|
|
49
|
+
--error) error="$2"; shift 2 ;;
|
|
50
|
+
--edge-case) edge_case="$2"; shift 2 ;;
|
|
51
|
+
--files-created) files_created="$2"; shift 2 ;;
|
|
52
|
+
--files-modified) files_modified="$2"; shift 2 ;;
|
|
53
|
+
--duration) duration="$2"; shift 2 ;;
|
|
54
|
+
*) shift ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
# Auto-detect version from registry
|
|
59
|
+
if [ -z "$version" ] && [ -f "${SINGULARITY_DATA}/registry.json" ] && command -v jq &>/dev/null; then
|
|
60
|
+
version=$(jq -r --arg s "$skill" '.skills[$s].currentVersion // "v1.0.0"' "${SINGULARITY_DATA}/registry.json" 2>/dev/null || echo "v1.0.0")
|
|
61
|
+
fi
|
|
62
|
+
[ -z "$version" ] && version="v1.0.0"
|
|
63
|
+
|
|
64
|
+
local now
|
|
65
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
66
|
+
local short_id
|
|
67
|
+
short_id=$(head -c 4 /dev/urandom | xxd -p | head -c 8)
|
|
68
|
+
local date_part
|
|
69
|
+
date_part=$(date -u +"%Y-%m-%d-%H-%M-%S")
|
|
70
|
+
|
|
71
|
+
local skill_dir="${TELEMETRY_DIR}/${skill}"
|
|
72
|
+
mkdir -p "$skill_dir"
|
|
73
|
+
|
|
74
|
+
local filename="${date_part}-${short_id}.json"
|
|
75
|
+
local filepath="${skill_dir}/${filename}"
|
|
76
|
+
|
|
77
|
+
# Build score field
|
|
78
|
+
local score_field="null"
|
|
79
|
+
[ -n "$score" ] && score_field="$score"
|
|
80
|
+
|
|
81
|
+
# Build errors array
|
|
82
|
+
local errors_field="[]"
|
|
83
|
+
[ -n "$error" ] && errors_field="[\"$(printf '%s' "$error" | sed 's/"/\\"/g')\"]"
|
|
84
|
+
|
|
85
|
+
# Build edge cases array
|
|
86
|
+
local edge_cases_field="[]"
|
|
87
|
+
[ -n "$edge_case" ] && edge_cases_field="[\"$(printf '%s' "$edge_case" | sed 's/"/\\"/g')\"]"
|
|
88
|
+
|
|
89
|
+
cat > "$filepath" << ENTRY
|
|
90
|
+
{
|
|
91
|
+
"\$schema": "singularity-telemetry-v1",
|
|
92
|
+
"skillName": "${skill}",
|
|
93
|
+
"version": "${version}",
|
|
94
|
+
"timestamp": "${now}",
|
|
95
|
+
"trigger": "${trigger}",
|
|
96
|
+
"inputs": {},
|
|
97
|
+
"outputs": {
|
|
98
|
+
"filesCreated": ${files_created},
|
|
99
|
+
"filesModified": ${files_modified},
|
|
100
|
+
"summary": "$(printf '%s' "$summary" | sed 's/"/\\"/g')"
|
|
101
|
+
},
|
|
102
|
+
"duration_ms": ${duration},
|
|
103
|
+
"score": ${score_field},
|
|
104
|
+
"errors": ${errors_field},
|
|
105
|
+
"edgeCases": ${edge_cases_field},
|
|
106
|
+
"repairTriggered": false
|
|
107
|
+
}
|
|
108
|
+
ENTRY
|
|
109
|
+
|
|
110
|
+
echo "Telemetry logged: ${filepath}"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
cmd_list() {
|
|
114
|
+
local skill="$1"
|
|
115
|
+
shift
|
|
116
|
+
local last=10
|
|
117
|
+
|
|
118
|
+
while [ $# -gt 0 ]; do
|
|
119
|
+
case "$1" in
|
|
120
|
+
--last) last="$2"; shift 2 ;;
|
|
121
|
+
*) shift ;;
|
|
122
|
+
esac
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
local skill_dir="${TELEMETRY_DIR}/${skill}"
|
|
126
|
+
if [ ! -d "$skill_dir" ]; then
|
|
127
|
+
echo "No telemetry for '${skill}'" >&2
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# List most recent entries
|
|
132
|
+
ls -1t "$skill_dir"/*.json 2>/dev/null | head -n "$last" | while read -r f; do
|
|
133
|
+
if command -v jq &>/dev/null; then
|
|
134
|
+
jq -r '"[\(.timestamp)] \(.trigger) | score: \(.score // "n/a") | \(.outputs.summary // "no summary")"' "$f"
|
|
135
|
+
else
|
|
136
|
+
basename "$f"
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
cmd_replay() {
|
|
142
|
+
local skill="$1"
|
|
143
|
+
local timestamp="$2"
|
|
144
|
+
local skill_dir="${TELEMETRY_DIR}/${skill}"
|
|
145
|
+
|
|
146
|
+
# Find matching file
|
|
147
|
+
local match
|
|
148
|
+
match=$(ls -1 "${skill_dir}/"*"${timestamp}"* 2>/dev/null | head -1)
|
|
149
|
+
|
|
150
|
+
if [ -z "$match" ]; then
|
|
151
|
+
echo "No telemetry entry matching '${timestamp}' for '${skill}'" >&2
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if command -v jq &>/dev/null; then
|
|
156
|
+
jq '.' "$match"
|
|
157
|
+
else
|
|
158
|
+
cat "$match"
|
|
159
|
+
fi
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
cmd_prune() {
|
|
163
|
+
local days=90
|
|
164
|
+
while [ $# -gt 0 ]; do
|
|
165
|
+
case "$1" in
|
|
166
|
+
--days) days="$2"; shift 2 ;;
|
|
167
|
+
*) shift ;;
|
|
168
|
+
esac
|
|
169
|
+
done
|
|
170
|
+
|
|
171
|
+
local count=0
|
|
172
|
+
find "$TELEMETRY_DIR" -name "*.json" -mtime +"$days" -print0 2>/dev/null | while IFS= read -r -d '' f; do
|
|
173
|
+
rm "$f"
|
|
174
|
+
count=$((count + 1))
|
|
175
|
+
done
|
|
176
|
+
echo "Pruned ${count} entries older than ${days} days"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Main dispatch
|
|
180
|
+
[ $# -lt 1 ] && usage
|
|
181
|
+
|
|
182
|
+
cmd="$1"
|
|
183
|
+
shift
|
|
184
|
+
|
|
185
|
+
case "$cmd" in
|
|
186
|
+
log) [ $# -lt 1 ] && usage; cmd_log "$@" ;;
|
|
187
|
+
list) [ $# -lt 1 ] && usage; cmd_list "$@" ;;
|
|
188
|
+
replay) [ $# -lt 2 ] && usage; cmd_replay "$@" ;;
|
|
189
|
+
prune) cmd_prune "$@" ;;
|
|
190
|
+
*) usage ;;
|
|
191
|
+
esac
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: creating-skills
|
|
3
|
+
description: Use when a new reusable skill needs to be built, either detected by gap analysis, requested by the user, or triggered by /singularity-create
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a New Skill
|
|
7
|
+
|
|
8
|
+
Build a new Claude Code skill through a structured workflow that ensures quality, testability, and integration with the singularity evolution engine.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
- Read `superpowers:writing-skills` if available — it defines SKILL.md authoring best practices
|
|
13
|
+
- This skill creates standard Claude Code skills usable by any plugin
|
|
14
|
+
|
|
15
|
+
## Workflow
|
|
16
|
+
|
|
17
|
+
### Step 1: Requirements Gathering
|
|
18
|
+
|
|
19
|
+
Ask the user ONE question at a time:
|
|
20
|
+
|
|
21
|
+
1. **What** does this skill do? (core purpose in one sentence)
|
|
22
|
+
2. **When** should it trigger? (symptoms, conditions, error messages)
|
|
23
|
+
3. **What tools** does it need? (Read, Write, Edit, Bash, Agent, etc.)
|
|
24
|
+
4. **What's the output?** (files created, code modified, analysis produced)
|
|
25
|
+
|
|
26
|
+
### Step 2: Check Registry
|
|
27
|
+
|
|
28
|
+
Read `~/.claude/singularity/registry.json` to verify no duplicate or overlapping skill exists.
|
|
29
|
+
|
|
30
|
+
If a similar skill exists, ask: *"A skill called '<name>' already covers <overlap>. Should I extend it or create a separate skill?"*
|
|
31
|
+
|
|
32
|
+
### Step 3: Generate SKILL.md
|
|
33
|
+
|
|
34
|
+
Create the skill following these conventions:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
---
|
|
38
|
+
name: <skill-name> # kebab-case, verb-first (e.g., creating-api-clients)
|
|
39
|
+
description: Use when <triggering conditions> # Max 500 chars, start with "Use when"
|
|
40
|
+
---
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Content structure:**
|
|
44
|
+
- Overview (1-2 sentences)
|
|
45
|
+
- When to use / when NOT to use
|
|
46
|
+
- Workflow steps (numbered, actionable)
|
|
47
|
+
- Common mistakes / red flags
|
|
48
|
+
- Integration with other skills (if applicable)
|
|
49
|
+
|
|
50
|
+
**Rules:**
|
|
51
|
+
- Description = triggering conditions ONLY, not what the skill does
|
|
52
|
+
- Keep under 500 words for frequently-used skills
|
|
53
|
+
- Use cross-references (`superpowers:skill-name`) not file paths
|
|
54
|
+
- Include a "When NOT to use" section
|
|
55
|
+
|
|
56
|
+
### Step 4: Write the Skill
|
|
57
|
+
|
|
58
|
+
Write to `~/.claude/skills/<skill-name>/SKILL.md`
|
|
59
|
+
|
|
60
|
+
If the skill needs supporting files (templates, rubrics, scripts), create them in:
|
|
61
|
+
`~/.claude/skills/<skill-name>/references/`
|
|
62
|
+
|
|
63
|
+
### Step 5: Register
|
|
64
|
+
|
|
65
|
+
Update `~/.claude/singularity/registry.json`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Using score-manager to initialize scoring
|
|
69
|
+
"${CLAUDE_PLUGIN_ROOT}/scripts/score-manager.sh" init <skill-name>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then update registry.json to add the skill entry:
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"skills": {
|
|
76
|
+
"<skill-name>": {
|
|
77
|
+
"location": "~/.claude/skills/<skill-name>/SKILL.md",
|
|
78
|
+
"createdBy": "singularity-claude:creating-skills",
|
|
79
|
+
"createdAt": "<ISO-8601>",
|
|
80
|
+
"currentVersion": "v1.0.0",
|
|
81
|
+
"maturity": "draft",
|
|
82
|
+
"tags": ["<relevant>", "<tags>"],
|
|
83
|
+
"lastExecuted": null,
|
|
84
|
+
"executionCount": 0,
|
|
85
|
+
"averageScore": 0
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 6: Initial Test
|
|
92
|
+
|
|
93
|
+
Invoke the newly created skill via the `Skill` tool to verify it loads correctly.
|
|
94
|
+
|
|
95
|
+
### Step 7: Initial Score
|
|
96
|
+
|
|
97
|
+
Run `singularity-claude:scoring` on the test output to establish a baseline score.
|
|
98
|
+
|
|
99
|
+
### Step 8: Log Telemetry
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
"${CLAUDE_PLUGIN_ROOT}/scripts/telemetry-writer.sh" log <skill-name> \
|
|
103
|
+
--trigger "creation" \
|
|
104
|
+
--summary "Created new skill: <description>"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Output
|
|
108
|
+
|
|
109
|
+
After creation, report:
|
|
110
|
+
- Skill location: `~/.claude/skills/<skill-name>/SKILL.md`
|
|
111
|
+
- Initial version: v1.0.0
|
|
112
|
+
- Maturity: draft
|
|
113
|
+
- Next steps: use the skill, then score with `/singularity-score`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "singularity-score-v1",
|
|
3
|
+
"skillName": "{{SKILL_NAME}}",
|
|
4
|
+
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "v1.0.0",
|
|
7
|
+
"gitTag": "singularity/{{SKILL_NAME}}/v1.0.0",
|
|
8
|
+
"scores": [],
|
|
9
|
+
"averageScore": 0,
|
|
10
|
+
"executionCount": 0,
|
|
11
|
+
"maturity": "draft"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"currentVersion": "v1.0.0",
|
|
15
|
+
"createdAt": "{{TIMESTAMP}}",
|
|
16
|
+
"lastScoredAt": null
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{SKILL_NAME}}
|
|
3
|
+
description: Use when {{TRIGGERING_CONDITIONS}}
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{SKILL_TITLE}}
|
|
7
|
+
|
|
8
|
+
{{OVERVIEW — 1-2 sentences describing the core purpose}}
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- {{Symptom or condition 1}}
|
|
13
|
+
- {{Symptom or condition 2}}
|
|
14
|
+
|
|
15
|
+
## When NOT to Use
|
|
16
|
+
|
|
17
|
+
- {{Anti-pattern or wrong context}}
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
### Step 1: {{First action}}
|
|
22
|
+
|
|
23
|
+
{{Instructions}}
|
|
24
|
+
|
|
25
|
+
### Step 2: {{Second action}}
|
|
26
|
+
|
|
27
|
+
{{Instructions}}
|
|
28
|
+
|
|
29
|
+
## Common Mistakes
|
|
30
|
+
|
|
31
|
+
- **{{Mistake}}** — {{Why it's wrong and what to do instead}}
|
|
32
|
+
|
|
33
|
+
## Red Flags
|
|
34
|
+
|
|
35
|
+
- Never {{dangerous action}}
|
|
36
|
+
- Always {{safety measure}}
|