bigpowers 2.63.0 → 2.64.1
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/.pi/package.json +1 -1
- package/.pi/prompts/build-epic.md +2 -1
- package/.pi/prompts/develop-tdd.md +4 -0
- package/.pi/prompts/investigate-bug.md +5 -0
- package/.pi/prompts/plan-refactor.md +5 -0
- package/.pi/prompts/run-benchmark.md +67 -48
- package/.pi/prompts/security-review.md +4 -0
- package/.pi/prompts/setup-environment.md +15 -0
- package/.pi/prompts/wire-observability.md +4 -0
- package/.pi/skills/build-epic/SKILL.md +2 -1
- package/.pi/skills/develop-tdd/SKILL.md +4 -0
- package/.pi/skills/investigate-bug/SKILL.md +5 -0
- package/.pi/skills/plan-refactor/SKILL.md +5 -0
- package/.pi/skills/run-benchmark/SKILL.md +67 -48
- package/.pi/skills/security-review/SKILL.md +4 -0
- package/.pi/skills/setup-environment/SKILL.md +15 -0
- package/.pi/skills/wire-observability/SKILL.md +4 -0
- package/CHANGELOG.md +25 -0
- package/CLAUDE.md +4 -0
- package/CONVENTIONS.md +10 -0
- package/README.md +6 -0
- package/SKILL-INDEX.md +1 -1
- package/package.json +5 -2
- package/scripts/build-receipts.sh +107 -0
- package/scripts/build-skill-graph.sh +226 -0
- package/scripts/build-skill-index.sh +10 -20
- package/scripts/generate-skill-index.sh +2 -4
- package/scripts/record-cycle-time.sh +6 -4
- package/scripts/regenerate-lockfile.sh +10 -38
- package/scripts/sync-skills.sh +106 -79
- package/scripts/validate-okf.sh +28 -0
- package/skills/build-epic/SKILL.md +2 -1
- package/skills/develop-tdd/SKILL.md +4 -0
- package/skills/investigate-bug/SKILL.md +5 -0
- package/skills/plan-refactor/SKILL.md +5 -0
- package/skills/run-benchmark/SKILL.md +67 -49
- package/skills/security-review/SKILL.md +4 -0
- package/skills/setup-environment/SKILL.md +15 -0
- package/skills/wire-observability/SKILL.md +4 -0
- package/skills-lock.json +83 -83
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e41s01
|
|
3
|
+
# build-receipts.sh — aggregate all quality evidence into specs/receipts.json.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "build-receipts: not inside a git repo" >&2; exit 1; }
|
|
6
|
+
OUTFILE="$ROOT/specs/receipts.json"
|
|
7
|
+
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
8
|
+
emit_json() {
|
|
9
|
+
python3 -c "
|
|
10
|
+
import json, sys, os, subprocess, glob as gb
|
|
11
|
+
sections = {}
|
|
12
|
+
|
|
13
|
+
# compliance
|
|
14
|
+
comp = '$ROOT/specs/verifications/features/'
|
|
15
|
+
if os.path.isdir(comp):
|
|
16
|
+
try:
|
|
17
|
+
r = subprocess.run(['bash','scripts/audit-compliance.sh','specs/verifications/features/','--dry-run'],
|
|
18
|
+
cwd='$ROOT', capture_output=True, text=True, timeout=30)
|
|
19
|
+
score = None
|
|
20
|
+
for line in r.stdout.splitlines():
|
|
21
|
+
if 'SCORE:' in line and '%' in line:
|
|
22
|
+
p = line.split('SCORE:')[1].strip()
|
|
23
|
+
s = p.split('%')[0].strip()
|
|
24
|
+
try: score = int(s)
|
|
25
|
+
except ValueError: pass
|
|
26
|
+
break
|
|
27
|
+
if score is not None:
|
|
28
|
+
sections['compliance'] = {'value':score,'generated_at':'$TIMESTAMP','source':'measured','command':'bash scripts/audit-compliance.sh specs/verifications/features/'}
|
|
29
|
+
else:
|
|
30
|
+
sections['compliance'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'bash scripts/audit-compliance.sh specs/verifications/features/'}
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f'build-receipts: compliance error: {e}', file=sys.stderr)
|
|
33
|
+
sections['compliance'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'bash scripts/audit-compliance.sh specs/verifications/features/'}
|
|
34
|
+
else:
|
|
35
|
+
sections['compliance'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'bash scripts/audit-compliance.sh specs/verifications/features/'}
|
|
36
|
+
|
|
37
|
+
# golden
|
|
38
|
+
gd = '$ROOT/specs/benchmarks/reports/'
|
|
39
|
+
if os.path.isdir(gd):
|
|
40
|
+
reports = sorted(gb.glob(os.path.join(gd, 'GOLDEN-*.yaml')))
|
|
41
|
+
if reports:
|
|
42
|
+
try:
|
|
43
|
+
with open(reports[-1]) as f:
|
|
44
|
+
import yaml; data = yaml.safe_load(f)
|
|
45
|
+
sm = data.get('summary',{})
|
|
46
|
+
sections['golden_suite'] = {'value':{'pass_rate':sm.get('pass_rate'),'passed':sm.get('passed'),'total':sm.get('total'),'overall':sm.get('overall','unknown'),'timestamp':data.get('timestamp',''),'commit':data.get('git_commit','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/benchmarks/reports/GOLDEN-*.yaml (latest)'}
|
|
47
|
+
except Exception as e:
|
|
48
|
+
print(f'build-receipts: golden error: {e}', file=sys.stderr)
|
|
49
|
+
sections['golden_suite'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/benchmarks/reports/GOLDEN-*.yaml'}
|
|
50
|
+
else:
|
|
51
|
+
sections['golden_suite'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/benchmarks/reports/GOLDEN-*.yaml'}
|
|
52
|
+
else:
|
|
53
|
+
sections['golden_suite'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/benchmarks/reports/GOLDEN-*.yaml'}
|
|
54
|
+
|
|
55
|
+
# metrics
|
|
56
|
+
md = '$ROOT/specs/metrics/'
|
|
57
|
+
if os.path.isdir(md):
|
|
58
|
+
mfs = sorted(gb.glob(os.path.join(md, '*.okf.md')))
|
|
59
|
+
sc = 0; st = {'measured':0,'estimated':0,'backfilled':0}; te = 0.0; tb = 0
|
|
60
|
+
for mf in mfs:
|
|
61
|
+
try:
|
|
62
|
+
with open(mf) as f: content = f.read()
|
|
63
|
+
parts = content.split('---')
|
|
64
|
+
if len(parts) < 3: continue
|
|
65
|
+
import yaml; fm = yaml.safe_load(parts[1])
|
|
66
|
+
if fm is None or fm.get('okf_kind') != 'story-metrics': continue
|
|
67
|
+
sc += 1; src = fm.get('source','absent')
|
|
68
|
+
if src in st: st[src] += 1
|
|
69
|
+
e = (fm.get('effort') or {}).get('effort_hours')
|
|
70
|
+
if e is not None:
|
|
71
|
+
try: te += float(e)
|
|
72
|
+
except: pass
|
|
73
|
+
b = fm.get('bcps')
|
|
74
|
+
if b is not None:
|
|
75
|
+
try: tb += int(b)
|
|
76
|
+
except: pass
|
|
77
|
+
except: pass
|
|
78
|
+
if sc > 0:
|
|
79
|
+
sections['metrics'] = {'value':{'story_count':sc,'total_effort_hours':round(te,1),'total_bcps':tb,'source_breakdown':st},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/metrics/*.okf.md'}
|
|
80
|
+
else:
|
|
81
|
+
sections['metrics'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/metrics/*.okf.md'}
|
|
82
|
+
else:
|
|
83
|
+
sections['metrics'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/metrics/*.okf.md'}
|
|
84
|
+
|
|
85
|
+
# traceability
|
|
86
|
+
tf = '$ROOT/specs/traceability-matrix.json'
|
|
87
|
+
if os.path.isfile(tf):
|
|
88
|
+
try:
|
|
89
|
+
with open(tf) as f: matrix = json.load(f)
|
|
90
|
+
stories = matrix.get('stories',[])
|
|
91
|
+
ts = len(stories); ls = sum(1 for s in stories if s.get('link_count',0)>0); lc = sum(s.get('link_count',0) for s in stories)
|
|
92
|
+
sections['traceability'] = {'value':{'total_stories':ts,'linked_stories':ls,'total_links':lc,'coverage_pct':round(ls/ts*100,1) if ts>0 else 0,'generated_at':matrix.get('generated_at','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/traceability-matrix.json (trace-matrix.py)'}
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f'build-receipts: trace error: {e}', file=sys.stderr)
|
|
95
|
+
sections['traceability'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/traceability-matrix.json'}
|
|
96
|
+
else:
|
|
97
|
+
sections['traceability'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/traceability-matrix.json'}
|
|
98
|
+
|
|
99
|
+
with open('$OUTFILE','w') as f: json.dump(sections,f,indent=2)
|
|
100
|
+
print(f'build-receipts: wrote {len(sections)} sections to specs/receipts.json')
|
|
101
|
+
for key, sec in sections.items(): print(f' {key}: source={sec.get(\"source\",\"?\")}')
|
|
102
|
+
"
|
|
103
|
+
}
|
|
104
|
+
echo "build-receipts: aggregating quality evidence..."
|
|
105
|
+
emit_json
|
|
106
|
+
echo "build-receipts: done — $(date -u +%H:%M:%SZ)"
|
|
107
|
+
exit 0
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e39s01
|
|
3
|
+
# Consumes bigpowers-mcp build_skill_graph MCP tool output (graph.jsonl)
|
|
4
|
+
# and emits specs/skill-graph.json + specs/skills-wiki/ OKF concepts.
|
|
5
|
+
#
|
|
6
|
+
# Supersedes standalone SKILL.md parsing approach (2026-07-04).
|
|
7
|
+
# Graph must be built first: run MCP tool `build_skill_graph` or
|
|
8
|
+
# `node -e "..."` to generate bigpowers-mcp/graph.jsonl.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
14
|
+
GRAPH_IN="$REPO_ROOT/bigpowers-mcp/graph.jsonl"
|
|
15
|
+
GRAPH_OUT="$REPO_ROOT/specs/skill-graph.json"
|
|
16
|
+
WIKI_DIR="$REPO_ROOT/specs/skills-wiki/skills"
|
|
17
|
+
|
|
18
|
+
# --help
|
|
19
|
+
if [[ "${1:-}" == "--help" ]]; then
|
|
20
|
+
cat <<EOF
|
|
21
|
+
Usage: build-skill-graph.sh
|
|
22
|
+
|
|
23
|
+
Reads bigpowers-mcp/graph.jsonl (generated by e32 build_skill_graph MCP tool)
|
|
24
|
+
and produces:
|
|
25
|
+
specs/skill-graph.json — nodes + typed edges (depends_on, gates, references,
|
|
26
|
+
enforces, handoff_to)
|
|
27
|
+
specs/skills-wiki/ — one OKF concept markdown file per skill
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--help Show this message
|
|
31
|
+
--json Write only specs/skill-graph.json (skip OKF wiki)
|
|
32
|
+
--wiki Write only specs/skills-wiki/ (skip JSON graph)
|
|
33
|
+
|
|
34
|
+
Requires: bigpowers-mcp/graph.jsonl (run build_skill_graph MCP tool first)
|
|
35
|
+
EOF
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
MODE="${1:-both}"
|
|
40
|
+
case "$MODE" in
|
|
41
|
+
--json) DO_JSON=1; DO_WIKI=0 ;;
|
|
42
|
+
--wiki) DO_JSON=0; DO_WIKI=1 ;;
|
|
43
|
+
both) DO_JSON=1; DO_WIKI=1 ;;
|
|
44
|
+
*) echo "Unknown option: $MODE" >&2; exit 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# Pre-flight: graph.jsonl must exist
|
|
48
|
+
if [[ ! -f "$GRAPH_IN" ]]; then
|
|
49
|
+
cat >&2 <<EOF
|
|
50
|
+
ERROR: $GRAPH_IN not found.
|
|
51
|
+
|
|
52
|
+
The skill graph has not been built yet. Run the bigpowers-mcp
|
|
53
|
+
build_skill_graph tool to generate it:
|
|
54
|
+
|
|
55
|
+
# Option A: via MCP inspector
|
|
56
|
+
npx @modelcontextprotocol/inspector node bigpowers-mcp/build/index.js
|
|
57
|
+
# Then call build_skill_graph
|
|
58
|
+
|
|
59
|
+
# Option B: via Node.js directly
|
|
60
|
+
node -e "
|
|
61
|
+
const {discoverSkills,readSkillFile}=require('./bigpowers-mcp/build/lib/skill-parser.js');
|
|
62
|
+
const {buildGraphFromSkills}=require('./bigpowers-mcp/build/lib/graph/builder.js');
|
|
63
|
+
const {saveGraph}=require('./bigpowers-mcp/build/lib/graph/store.js');
|
|
64
|
+
const skills=discoverSkills(process.cwd(),()=>'');
|
|
65
|
+
const parsed=skills.map(s=>readSkillFile(process.cwd(),s.name));
|
|
66
|
+
saveGraph('bigpowers-mcp/graph.jsonl', buildGraphFromSkills(parsed));
|
|
67
|
+
"
|
|
68
|
+
EOF
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Parse graph.jsonl into nodes + edges
|
|
73
|
+
echo "Reading $GRAPH_IN ..." >&2
|
|
74
|
+
|
|
75
|
+
NODES_JSON="$REPO_ROOT/specs/.tmp-nodes.json"
|
|
76
|
+
EDGES_JSON="$REPO_ROOT/specs/.tmp-edges.json"
|
|
77
|
+
|
|
78
|
+
python3 - "$GRAPH_IN" "$NODES_JSON" "$EDGES_JSON" <<'PYEOF'
|
|
79
|
+
import json, sys
|
|
80
|
+
from pathlib import Path
|
|
81
|
+
|
|
82
|
+
graph_in = Path(sys.argv[1])
|
|
83
|
+
nodes_out = Path(sys.argv[2])
|
|
84
|
+
edges_out = Path(sys.argv[3])
|
|
85
|
+
|
|
86
|
+
nodes = []
|
|
87
|
+
edges = []
|
|
88
|
+
entity_count = 0
|
|
89
|
+
relation_count = 0
|
|
90
|
+
parse_errors = 0
|
|
91
|
+
|
|
92
|
+
with open(graph_in) as f:
|
|
93
|
+
for lineno, line in enumerate(f, 1):
|
|
94
|
+
line = line.strip()
|
|
95
|
+
if not line:
|
|
96
|
+
continue
|
|
97
|
+
try:
|
|
98
|
+
obj = json.loads(line)
|
|
99
|
+
except json.JSONDecodeError as e:
|
|
100
|
+
print(f"WARNING: {graph_in}:{lineno}: invalid JSON — {e}", file=sys.stderr)
|
|
101
|
+
parse_errors += 1
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if obj.get("type") == "entity":
|
|
105
|
+
nodes.append({
|
|
106
|
+
"name": obj["name"],
|
|
107
|
+
"entityType": obj.get("entityType", "Skill"),
|
|
108
|
+
"observations": obj.get("observations", []),
|
|
109
|
+
})
|
|
110
|
+
entity_count += 1
|
|
111
|
+
elif obj.get("type") == "relation":
|
|
112
|
+
edges.append({
|
|
113
|
+
"from": obj["from"],
|
|
114
|
+
"to": obj["to"],
|
|
115
|
+
"relationType": obj.get("relationType", "unknown"),
|
|
116
|
+
})
|
|
117
|
+
relation_count += 1
|
|
118
|
+
else:
|
|
119
|
+
print(f"WARNING: {graph_in}:{lineno}: unknown type {obj.get('type')}", file=sys.stderr)
|
|
120
|
+
|
|
121
|
+
with open(nodes_out, "w") as f:
|
|
122
|
+
json.dump(nodes, f, indent=2)
|
|
123
|
+
with open(edges_out, "w") as f:
|
|
124
|
+
json.dump(edges, f, indent=2)
|
|
125
|
+
|
|
126
|
+
print(f" Entities: {entity_count}, Relations: {relation_count}", file=sys.stderr)
|
|
127
|
+
if parse_errors:
|
|
128
|
+
print(f" Parse errors: {parse_errors}", file=sys.stderr)
|
|
129
|
+
PYEOF
|
|
130
|
+
|
|
131
|
+
# Emit specs/skill-graph.json
|
|
132
|
+
if [[ "$DO_JSON" -eq 1 ]]; then
|
|
133
|
+
echo "Writing $GRAPH_OUT ..." >&2
|
|
134
|
+
python3 - "$NODES_JSON" "$EDGES_JSON" "$GRAPH_OUT" <<'PYEOF'
|
|
135
|
+
import json, sys
|
|
136
|
+
|
|
137
|
+
with open(sys.argv[1]) as f:
|
|
138
|
+
nodes = json.load(f)
|
|
139
|
+
with open(sys.argv[2]) as f:
|
|
140
|
+
edges = json.load(f)
|
|
141
|
+
|
|
142
|
+
graph = {"nodes": nodes, "edges": edges}
|
|
143
|
+
with open(sys.argv[3], "w") as f:
|
|
144
|
+
json.dump(graph, f, indent=2)
|
|
145
|
+
|
|
146
|
+
print(f" nodes: {len(nodes)}, edges: {len(edges)}", file=sys.stderr)
|
|
147
|
+
PYEOF
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Emit specs/skills-wiki/ OKF concepts
|
|
151
|
+
if [[ "$DO_WIKI" -eq 1 ]]; then
|
|
152
|
+
echo "Writing $WIKI_DIR/ ..." >&2
|
|
153
|
+
rm -rf "$WIKI_DIR"
|
|
154
|
+
mkdir -p "$WIKI_DIR"
|
|
155
|
+
|
|
156
|
+
python3 - "$NODES_JSON" "$WIKI_DIR" <<'PYEOF'
|
|
157
|
+
import json, sys, re
|
|
158
|
+
from pathlib import Path
|
|
159
|
+
|
|
160
|
+
nodes_file = Path(sys.argv[1])
|
|
161
|
+
wiki_dir = Path(sys.argv[2])
|
|
162
|
+
|
|
163
|
+
with open(nodes_file) as f:
|
|
164
|
+
nodes = json.load(f)
|
|
165
|
+
|
|
166
|
+
count = 0
|
|
167
|
+
for node in nodes:
|
|
168
|
+
name = node["name"]
|
|
169
|
+
observations = node.get("observations", [])
|
|
170
|
+
|
|
171
|
+
model = ""
|
|
172
|
+
effort = ""
|
|
173
|
+
description = ""
|
|
174
|
+
for obs in observations:
|
|
175
|
+
if obs.startswith("model: "):
|
|
176
|
+
model = obs[7:]
|
|
177
|
+
elif obs.startswith("effort: "):
|
|
178
|
+
effort = obs[8:]
|
|
179
|
+
elif obs.startswith("description: "):
|
|
180
|
+
description = obs[13:]
|
|
181
|
+
|
|
182
|
+
# Derive phase from name convention (simplified)
|
|
183
|
+
phase = "utility"
|
|
184
|
+
critical_path = [
|
|
185
|
+
"survey-context", "elaborate-spec", "model-domain", "define-language",
|
|
186
|
+
"design-interface", "plan-work", "plan-release", "slice-tasks",
|
|
187
|
+
"kickoff-branch", "develop-tdd", "verify-work", "audit-code",
|
|
188
|
+
"commit-message", "release-branch", "security-review",
|
|
189
|
+
"assess-impact", "investigate-bug", "validate-fix",
|
|
190
|
+
]
|
|
191
|
+
if name in critical_path:
|
|
192
|
+
phase = "critical-path"
|
|
193
|
+
|
|
194
|
+
safe_desc = description.replace('"', '\\"')
|
|
195
|
+
md = f"""---
|
|
196
|
+
type: Skill
|
|
197
|
+
name: {name}
|
|
198
|
+
phase: {phase}
|
|
199
|
+
model: {model}
|
|
200
|
+
effort: {effort}
|
|
201
|
+
description: "{safe_desc}"
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
# {name}
|
|
205
|
+
|
|
206
|
+
**Phase:** {phase}
|
|
207
|
+
**Model:** {model}
|
|
208
|
+
**Effort:** {effort}
|
|
209
|
+
|
|
210
|
+
{description}
|
|
211
|
+
|
|
212
|
+
> Auto-generated by build-skill-graph.sh (e39s01) from bigpowers-mcp/graph.jsonl.
|
|
213
|
+
> Source of truth: skills/{name}/SKILL.md
|
|
214
|
+
"""
|
|
215
|
+
out_path = wiki_dir / f"{name}.md"
|
|
216
|
+
out_path.write_text(md)
|
|
217
|
+
count += 1
|
|
218
|
+
|
|
219
|
+
print(f" concepts written: {count}", file=sys.stderr)
|
|
220
|
+
PYEOF
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Cleanup
|
|
224
|
+
rm -f "$NODES_JSON" "$EDGES_JSON"
|
|
225
|
+
|
|
226
|
+
echo "Done." >&2
|
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
2
|
+
# story: e28s04
|
|
3
|
+
# build-skill-index.sh — lexical index for search-skills
|
|
3
4
|
set -euo pipefail
|
|
4
|
-
|
|
5
|
-
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
6
6
|
OUT="$REPO_ROOT/specs/SKILL-SEARCH-INDEX_LATEST.md"
|
|
7
7
|
mkdir -p "$(dirname "$OUT")"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
echo "# Skill Search Index (auto-generated)"
|
|
11
|
-
echo ""
|
|
12
|
-
echo "Regenerate: \`bash scripts/build-skill-index.sh\`"
|
|
13
|
-
echo ""
|
|
14
|
-
echo "| name | model | description |"
|
|
15
|
-
echo "|------|-------|-------------|"
|
|
16
|
-
for skill_dir in $(find "$REPO_ROOT" -maxdepth 1 -mindepth 1 -type d | sort); do
|
|
8
|
+
{ echo "# Skill Search Index (auto-generated)"; echo ""; echo "Regenerate: \`bash scripts/build-skill-index.sh\`"; echo ""; echo "| name | model | description |"; echo "|------|-------|-------------|"
|
|
9
|
+
while IFS= read -r skill_dir; do
|
|
17
10
|
skill_md="$skill_dir/SKILL.md"
|
|
18
|
-
|
|
19
|
-
name
|
|
20
|
-
|
|
21
|
-
desc=$(awk '/^---/{f++} f==1 && /^description:/{sub(/^description:[[:space:]]*/,""); print; exit}' "$skill_md" \
|
|
22
|
-
| tr -d '\n' | sed 's/|/\\|/g' | LC_ALL=C head -c 200)
|
|
11
|
+
parse_frontmatter "$skill_md" || continue
|
|
12
|
+
name="$_PF_NAME"; model="${_PF_MODEL:-sonnet}"
|
|
13
|
+
desc=$(echo "$_PF_DESCRIPTION" | sed 's/|/\\|/g' | LC_ALL=C head -c 200)
|
|
23
14
|
[[ -z "$name" ]] && continue
|
|
24
|
-
echo "| $name | $
|
|
25
|
-
done
|
|
15
|
+
echo "| $name | $model | $desc |"
|
|
16
|
+
done < <(iterate_skills)
|
|
26
17
|
} > "$OUT"
|
|
27
|
-
|
|
28
18
|
echo "build-skill-index: wrote $OUT"
|
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
# DO NOT edit SKILL-INDEX.md manually — it is auto-generated.
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
9
|
+
|
|
9
10
|
LOCKFILE="$REPO_ROOT/skills-lock.json"
|
|
10
11
|
INDEX_FILE="$REPO_ROOT/SKILL-INDEX.md"
|
|
11
|
-
# SKILLS_ROOT: use skills/ when it exists, fall back to repo root
|
|
12
|
-
SKILLS_ROOT="$REPO_ROOT"
|
|
13
|
-
[[ -d "$REPO_ROOT/skills" ]] && SKILLS_ROOT="$REPO_ROOT/skills"
|
|
14
12
|
|
|
15
13
|
if [[ ! -f "$LOCKFILE" ]]; then
|
|
16
14
|
echo "generate-skill-index: ERROR — skills-lock.json not found. Run regenerate-lockfile.sh first." >&2
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
# Usage:
|
|
38
38
|
# scripts/record-cycle-time.sh report [--range <git-range>] [--story-key <key>]
|
|
39
39
|
# scripts/record-cycle-time.sh append --story <id> --bcps <n> \
|
|
40
|
-
# [--range <git-range>] [--merged-at <epoch>] [--file <path>] [--story-key <key>]
|
|
40
|
+
# [--bcp-plus <n>] [--range <git-range>] [--merged-at <epoch>] [--file <path>] [--story-key <key>]
|
|
41
41
|
#
|
|
42
42
|
# `report` prints the per-story partition + an additivity self-check.
|
|
43
43
|
# `append` recomputes the partition and appends one row for <id> to the ledger.
|
|
@@ -59,9 +59,7 @@ Usage: scripts/record-cycle-time.sh <command> [flags]
|
|
|
59
59
|
Commands:
|
|
60
60
|
report Compute per-story effort partition + additivity self-check.
|
|
61
61
|
Flags: --range <git-range> --story-key <trailer-key>
|
|
62
|
-
append Recompute partition and append one row for a story
|
|
63
|
-
Flags: --story <id> --bcps <n> [--range <git-range>]
|
|
64
|
-
[--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
62
|
+
append Recompute partition and append one row for a story.\n Flags: --story <id> --bcps <n> [--bcp-plus <n>] [--range <git-range>]\n [--merged-at <epoch>] [--file <path>] [--story-key <trailer-key>]
|
|
65
63
|
self-test Run report, then exit 0 on additivity PASS; exit 1 on FAIL.
|
|
66
64
|
Flags: --range <git-range> --story-key <trailer-key>
|
|
67
65
|
help Show this message.
|
|
@@ -133,6 +131,7 @@ cmd_append() {
|
|
|
133
131
|
case "$1" in
|
|
134
132
|
--story) story="$2"; shift 2 ;;
|
|
135
133
|
--bcps) bcps="$2"; shift 2 ;;
|
|
134
|
+
--bcp-plus) bcp_plus="$2"; shift 2 ;;
|
|
136
135
|
--range) range="$2"; shift 2 ;;
|
|
137
136
|
--merged-at) merged_at="$2"; shift 2 ;;
|
|
138
137
|
--file) file="$2"; shift 2 ;;
|
|
@@ -216,6 +215,9 @@ cmd_append() {
|
|
|
216
215
|
printf -- 'id: %s\n' "$story"
|
|
217
216
|
printf -- 'epic: %s\n' "${story%%s*}" # e40s03 → e40
|
|
218
217
|
printf -- 'bcps: %s\n' "$bcps"
|
|
218
|
+
if [ -n "$bcp_plus" ]; then
|
|
219
|
+
printf -- 'bcp_plus: %s\n' "$bcp_plus"
|
|
220
|
+
fi
|
|
219
221
|
printf -- 'commit_range: "%s..%s"\n' "${range_lo:-$range_hi}" "$range_hi"
|
|
220
222
|
printf -- 'source: measured\n'
|
|
221
223
|
printf -- 'generated_at: %s\n' "$generated_at"
|
|
@@ -1,49 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# story: e28s04
|
|
2
3
|
# regenerate-lockfile.sh — Scan all SKILL.md files and regenerate skills-lock.json
|
|
3
|
-
# Output: Canonical catalog with name, description, sha256, path for every skill.
|
|
4
|
-
# Run this after adding, removing, or updating any skill.
|
|
5
4
|
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
8
6
|
LOCKFILE="$REPO_ROOT/skills-lock.json"
|
|
9
|
-
# SKILLS_ROOT: use skills/ when it exists, fall back to repo root
|
|
10
|
-
SKILLS_ROOT="$REPO_ROOT"
|
|
11
|
-
[[ -d "$REPO_ROOT/skills" ]] && SKILLS_ROOT="$REPO_ROOT/skills"
|
|
12
|
-
|
|
13
|
-
# Build JSON using jq from individual skill records
|
|
14
|
-
# We pipe each skill as a JSON object, then jq -s merges them into an object keyed by name
|
|
15
7
|
records=()
|
|
16
|
-
|
|
8
|
+
while IFS= read -r skill_dir; do
|
|
17
9
|
skill_md="$skill_dir/SKILL.md"
|
|
18
|
-
[[ -f "$skill_md" ]] || continue
|
|
19
|
-
|
|
20
10
|
name=$(basename "$skill_dir")
|
|
21
|
-
|
|
22
|
-
description
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Compute SHA-256 of full SKILL.md content (first 16 hex chars for compact uniqueness)
|
|
26
|
-
# Falls back to shasum for macOS where sha256sum is not installed by default
|
|
27
|
-
if command -v sha256sum &>/dev/null; then
|
|
28
|
-
sha256=$(sha256sum "$skill_md" | cut -c1-16)
|
|
29
|
-
else
|
|
30
|
-
sha256=$(shasum -a 256 "$skill_md" | cut -c1-16)
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# Relative path from repo root
|
|
11
|
+
parse_frontmatter "$skill_md" || continue
|
|
12
|
+
description="$_PF_DESCRIPTION"
|
|
13
|
+
if command -v sha256sum &>/dev/null; then sha256=$(sha256sum "$skill_md" | cut -c1-16)
|
|
14
|
+
else sha256=$(shasum -a 256 "$skill_md" | cut -c1-16); fi
|
|
34
15
|
relpath="${skill_dir#$REPO_ROOT/}SKILL.md"
|
|
35
|
-
|
|
36
|
-
# Emit JSON record for this skill
|
|
37
|
-
records+=("$(jq -n --arg name "$name" \
|
|
38
|
-
--arg desc "$description" \
|
|
39
|
-
--arg sha "$sha256" \
|
|
40
|
-
--arg path "$relpath" \
|
|
16
|
+
records+=("$(jq -n --arg name "$name" --arg desc "$description" --arg sha "$sha256" --arg path "$relpath" \
|
|
41
17
|
'{($name): {description: $desc, sha256: $sha, path: $path}}')")
|
|
42
|
-
done
|
|
43
|
-
|
|
44
|
-
# Merge all records into a single "skills" object with version
|
|
45
|
-
jq -n --argjson skills "$(printf '%s\n' "${records[@]}" | jq -s 'add')" \
|
|
46
|
-
'{version: 1, skills: $skills}' > "$LOCKFILE"
|
|
47
|
-
|
|
18
|
+
done < <(iterate_skills)
|
|
19
|
+
jq -n --argjson skills "$(printf '%s\n' "${records[@]}" | jq -s 'add')" '{version: 1, skills: $skills}' > "$LOCKFILE"
|
|
48
20
|
count=$(jq '.skills | length' "$LOCKFILE")
|
|
49
21
|
echo "regenerate-lockfile: $count skills written to skills-lock.json"
|