bigpowers 2.73.2 → 2.73.4
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/gate-trace.md +1 -1
- package/.pi/prompts/maintain-wiki.md +1 -1
- package/.pi/prompts/security-review.md +1 -1
- package/.pi/skills/gate-trace/SKILL.md +1 -1
- package/.pi/skills/maintain-wiki/SKILL.md +1 -1
- package/.pi/skills/security-review/SKILL.md +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/scripts/lib/skill-common.sh +21 -0
- package/scripts/lib/sync-post.sh +162 -0
- package/scripts/lib/sync-render.sh +126 -0
- package/scripts/lib/trace-stories.py +9 -28
- package/scripts/okf-add-references.py +79 -0
- package/scripts/sync-skills.sh +12 -612
package/.pi/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Deterministic traceability quality gate — reads coverage matrix + blind-spot data, applies decision rules with oracle confidence downgrade, emits PASS/CONCERNS/FAIL/WAIVED verdict. Use before release-branch to gate merges on traceability."
|
|
2
|
+
description: "Deterministic traceability quality gate — reads coverage matrix + blind-spot data, applies decision rules with oracle confidence downgrade, emits PASS/CONCERNS/FAIL/WAIVED verdict. Use before release-branch to gate merges on traceability."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Agent-maintained OKF wiki — INGEST source docs, LINT for issues, QUERY across concept pages. Run as part of build-epic Step 8 and verify-work Phase 3."
|
|
2
|
+
description: "Agent-maintained OKF wiki — INGEST source docs, LINT for issues, QUERY across concept pages. Run as part of build-epic Step 8 and verify-work Phase 3."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: >
|
|
2
|
+
description: >
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gate-trace
|
|
3
|
-
description: "\"Deterministic traceability quality gate — reads coverage matrix + blind-spot data, applies decision rules with oracle confidence downgrade, emits PASS/CONCERNS/FAIL/WAIVED verdict. Use before release-branch to gate merges on traceability.\"
|
|
3
|
+
description: "\"Deterministic traceability quality gate — reads coverage matrix + blind-spot data, applies decision rules with oracle confidence downgrade, emits PASS/CONCERNS/FAIL/WAIVED verdict. Use before release-branch to gate merges on traceability.\""
|
|
4
4
|
model: haiku
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maintain-wiki
|
|
3
|
-
description: "\"Agent-maintained OKF wiki — INGEST source docs, LINT for issues, QUERY across concept pages. Run as part of build-epic Step 8 and verify-work Phase 3.\"
|
|
3
|
+
description: "\"Agent-maintained OKF wiki — INGEST source docs, LINT for issues, QUERY across concept pages. Run as part of build-epic Step 8 and verify-work Phase 3.\""
|
|
4
4
|
model: haiku
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: security-review
|
|
3
|
-
description: ">
|
|
3
|
+
description: ">"
|
|
4
4
|
model: sonnet
|
|
5
5
|
---
|
|
6
6
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.73.4](https://github.com/danielvm-git/bigpowers/compare/v2.73.3...v2.73.4) (2026-07-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **sync:** split sync-skills.sh into lib modules under 300-line cap ([#59](https://github.com/danielvm-git/bigpowers/issues/59)) ([eed704f](https://github.com/danielvm-git/bigpowers/commit/eed704fe87a9f89dec211bbc6c6695afee884e52))
|
|
7
|
+
|
|
8
|
+
## [2.73.3](https://github.com/danielvm-git/bigpowers/compare/v2.73.2...v2.73.3) (2026-07-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **trace:** trim trace-stories.py under 300-line cap ([879bb8a](https://github.com/danielvm-git/bigpowers/commit/879bb8a87940a4bf337dfed7406359f744e09a31))
|
|
14
|
+
|
|
1
15
|
## [2.73.2](https://github.com/danielvm-git/bigpowers/compare/v2.73.1...v2.73.2) (2026-07-06)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -91,6 +91,27 @@ parse_frontmatter() {
|
|
|
91
91
|
export SKILL_NAME SKILL_MODEL SKILL_DESC _PF_NAME _PF_MODEL _PF_DESCRIPTION
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
# parse_frontmatter_okf — like parse_frontmatter but also extracts effort field
|
|
95
|
+
parse_frontmatter_okf() {
|
|
96
|
+
local file="$1"
|
|
97
|
+
_PF_EFFORT=""
|
|
98
|
+
|
|
99
|
+
if [[ ! -f "$file" ]] || ! grep -q '^---$' "$file"; then
|
|
100
|
+
return 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
_PF_NAME=$(awk '/^---/{f++} f==1 && /^name:/{print; exit}' "$file" | sed 's/^name:[[:space:]]*//')
|
|
104
|
+
_PF_MODEL=$(awk '/^---/{f++} f==1 && /^model:/{print; exit}' "$file" | sed 's/^model:[[:space:]]*//')
|
|
105
|
+
_PF_EFFORT=$(awk '/^---/{f++} f==1 && /^effort:/{print; exit}' "$file" | sed 's/^effort:[[:space:]]*//')
|
|
106
|
+
_PF_DESCRIPTION=$(awk '/^---/{f++; next} f==1 && /^description:/{p=1; sub(/^description:[[:space:]]*/,""); print; next} f==1 && p && /^[a-z]+:/{exit} f==1 && p{print}' "$file" \
|
|
107
|
+
| tr -d '\n' \
|
|
108
|
+
| sed -E 's/[[:space:]]+/ /g')
|
|
109
|
+
|
|
110
|
+
[[ -z "$_PF_NAME" ]] && return 1
|
|
111
|
+
export _PF_NAME _PF_MODEL _PF_EFFORT _PF_DESCRIPTION
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
|
|
94
115
|
# iterate_skills — list skill directories containing SKILL.md, stable sorted order
|
|
95
116
|
iterate_skills() {
|
|
96
117
|
resolve_repo_root 2>/dev/null || true
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e13s03 e28s03
|
|
3
|
+
# Post-sync pipeline for sync-skills.sh — manifests, lockfiles, guards, OKF wikis.
|
|
4
|
+
# Args: $1=skill_count $2=opencode_count (optional, default 0)
|
|
5
|
+
|
|
6
|
+
if [ -n "${SYNC_POST_LOADED:-}" ]; then
|
|
7
|
+
return 0
|
|
8
|
+
fi
|
|
9
|
+
SYNC_POST_LOADED=1
|
|
10
|
+
|
|
11
|
+
sync_post_run() {
|
|
12
|
+
local skill_count="${1:-0}"
|
|
13
|
+
local opencode_count="${2:-0}"
|
|
14
|
+
local pkg_version pkg_desc readme readme_tmp trace_mdc manifest ext_ver pkg_ver validate_script
|
|
15
|
+
|
|
16
|
+
pkg_version=$(jq -r '.version' "$REPO_ROOT/package.json")
|
|
17
|
+
pkg_desc=$(jq -r '.description' "$REPO_ROOT/package.json")
|
|
18
|
+
|
|
19
|
+
jq -n --arg name "bigpowers" \
|
|
20
|
+
--arg version "$pkg_version" \
|
|
21
|
+
--arg desc "${skill_count} skills — ${pkg_desc}" \
|
|
22
|
+
'{name: $name, version: $version, description: $desc}' > "$GEMINI_MANIFEST"
|
|
23
|
+
|
|
24
|
+
jq -n --arg version "$pkg_version" \
|
|
25
|
+
--arg desc "${skill_count} skills — ${pkg_desc}" \
|
|
26
|
+
'{
|
|
27
|
+
"name": "bigpowers",
|
|
28
|
+
"version": $version,
|
|
29
|
+
"description": $desc,
|
|
30
|
+
"keywords": ["pi-package"],
|
|
31
|
+
"pi": {
|
|
32
|
+
"skills": ["./skills"],
|
|
33
|
+
"prompts": ["./prompts"]
|
|
34
|
+
}
|
|
35
|
+
}' > "$PI_PACKAGE_JSON"
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
echo "{"
|
|
39
|
+
echo " \"\$schema\": \"https://opencode.ai/config.json\","
|
|
40
|
+
echo " \"instructions\": [\"CLAUDE.md\", \"CONVENTIONS.md\"]"
|
|
41
|
+
echo "}"
|
|
42
|
+
} > "$REPO_ROOT/opencode.json"
|
|
43
|
+
|
|
44
|
+
if [[ -x "$REPO_ROOT/scripts/regenerate-lockfile.sh" ]]; then
|
|
45
|
+
bash "$REPO_ROOT/scripts/regenerate-lockfile.sh" || {
|
|
46
|
+
echo "sync-skills: FAIL — lockfile regeneration failed" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [[ -x "$REPO_ROOT/scripts/generate-skill-index.sh" ]]; then
|
|
52
|
+
bash "$REPO_ROOT/scripts/generate-skill-index.sh" || {
|
|
53
|
+
echo "sync-skills: FAIL — SKILL-INDEX.md generation failed" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
}
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [[ -x "$REPO_ROOT/scripts/build-skill-index.sh" ]]; then
|
|
59
|
+
bash "$REPO_ROOT/scripts/build-skill-index.sh" || true
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
local CURSOR_KEEP="context7.mdc" mdc mdc_base
|
|
63
|
+
for mdc in "$CURSOR_RULES"/*.mdc; do
|
|
64
|
+
[[ -e "$mdc" ]] || continue
|
|
65
|
+
mdc_base=$(basename "$mdc")
|
|
66
|
+
case " $CURSOR_KEEP " in *" $mdc_base "*) continue ;; esac
|
|
67
|
+
if [[ ! -f "$SKILLS_ROOT/${mdc_base%.mdc}/SKILL.md" ]]; then
|
|
68
|
+
rm "$mdc"
|
|
69
|
+
echo " → pruned orphan cursor rule: $mdc_base"
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
readme="$REPO_ROOT/README.md"
|
|
74
|
+
if [[ -f "$readme" ]] && grep -q 'badge/skills-' "$readme"; then
|
|
75
|
+
readme_tmp=$(mktemp)
|
|
76
|
+
sed -E "s|(badge/skills-)[0-9]+(-brightgreen)|\1${skill_count}\2|" "$readme" > "$readme_tmp"
|
|
77
|
+
mv "$readme_tmp" "$readme"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
echo "sync-skills: $skill_count skills synced"
|
|
81
|
+
echo " → .cursor/rules/ ($skill_count .mdc files)"
|
|
82
|
+
echo " → .gemini/extensions/bigpowers/skills/ (Agent Skills)"
|
|
83
|
+
echo " → .gemini/extensions/bigpowers/commands/ (Slash Commands)"
|
|
84
|
+
echo " → .gemini/extensions/bigpowers/gemini-extension.json"
|
|
85
|
+
echo " → .pi/skills/ ($skill_count skill dirs — pi Agent Skills)"
|
|
86
|
+
echo " → .pi/prompts/ ($skill_count prompt templates — pi slash commands)"
|
|
87
|
+
echo " → .pi/package.json (pi package manifest)"
|
|
88
|
+
echo " → skills-lock.json (catalog with SHA-256 hashes)"
|
|
89
|
+
echo " → SKILL-INDEX.md (auto-generated skill reference)"
|
|
90
|
+
echo " → opencode.json (CLAUDE.md + CONVENTIONS.md instructions)"
|
|
91
|
+
[[ -n "${OPN_TARGET:-}" ]] && echo " → bigpowers-opencode: $opencode_count skills"
|
|
92
|
+
|
|
93
|
+
sync_post_regression_guards
|
|
94
|
+
|
|
95
|
+
if [[ -d "$REPO_ROOT/.git" ]]; then
|
|
96
|
+
bash "$REPO_ROOT/scripts/generate-reference-tables.sh"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
echo ""
|
|
100
|
+
echo "Generating OKF wikis..."
|
|
101
|
+
bash "$REPO_ROOT/scripts/generate-epics-wiki.sh"
|
|
102
|
+
bash "$REPO_ROOT/scripts/generate-adr-wiki.sh"
|
|
103
|
+
echo "sync-skills: OKF wikis regenerated"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sync_post_regression_guards() {
|
|
107
|
+
local trace_mdc manifest ext_ver pkg_ver validate_script
|
|
108
|
+
|
|
109
|
+
trace_mdc="$REPO_ROOT/.cursor/rules/trace-requirement.mdc"
|
|
110
|
+
if [[ -f "$trace_mdc" ]] && ! grep -q 'release-plan.yaml + epic' "$trace_mdc"; then
|
|
111
|
+
echo "sync-skills: FAIL — '+' missing from trace-requirement; use sed -E for whitespace collapse" >&2
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
manifest="$REPO_ROOT/.gemini/extensions/bigpowers/gemini-extension.json"
|
|
116
|
+
if [[ -f "$manifest" ]]; then
|
|
117
|
+
ext_ver=$(jq -r '.version // empty' "$manifest")
|
|
118
|
+
pkg_ver=$(jq -r '.version // empty' "$REPO_ROOT/package.json")
|
|
119
|
+
if [[ -n "$ext_ver" && -n "$pkg_ver" && "$ext_ver" != "$pkg_ver" ]]; then
|
|
120
|
+
echo "sync-skills: FAIL — gemini-extension.json version ($ext_ver) != package.json ($pkg_ver)" >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
validate_script="$REPO_ROOT/scripts/validate-skill-yaml.py"
|
|
126
|
+
if [[ -f "$validate_script" ]] && command -v $PYTHON &>/dev/null; then
|
|
127
|
+
if ! $PYTHON "$validate_script" > /dev/null 2>&1; then
|
|
128
|
+
echo "sync-skills: FAIL — YAML frontmatter validation failed" >&2
|
|
129
|
+
$PYTHON "$validate_script" >&2
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if grep -rn '^declare -A\|^[[:space:]]*declare -A' \
|
|
135
|
+
scripts/sync-skills.sh scripts/generate-skill-index.sh \
|
|
136
|
+
scripts/regenerate-lockfile.sh scripts/build-skill-index.sh 2>/dev/null; then
|
|
137
|
+
echo "sync-skills: FAIL — bash 4+ features detected in install-chain scripts (not macOS-compatible)" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
sync_opencode_copy() {
|
|
143
|
+
local opencode_count=0 skill_dir skill_md skill_name
|
|
144
|
+
SYNC_OPENCODE_COUNT=0
|
|
145
|
+
|
|
146
|
+
[[ -z "${OPN_TARGET:-}" || ! -d "$OPN_TARGET" ]] && return 0
|
|
147
|
+
|
|
148
|
+
echo ""
|
|
149
|
+
echo "Syncing skills to opencode repo: $OPN_TARGET"
|
|
150
|
+
OPN_SKILLS="$OPN_TARGET/skills"
|
|
151
|
+
mkdir -p "$OPN_SKILLS"
|
|
152
|
+
for skill_dir in "$SKILLS_ROOT"/*/; do
|
|
153
|
+
skill_md="$skill_dir/SKILL.md"
|
|
154
|
+
[[ -f "$skill_md" ]] || continue
|
|
155
|
+
skill_name=$(basename "$skill_dir")
|
|
156
|
+
mkdir -p "$OPN_SKILLS/$skill_name"
|
|
157
|
+
cp "$skill_md" "$OPN_SKILLS/$skill_name/SKILL.md"
|
|
158
|
+
opencode_count=$((opencode_count + 1))
|
|
159
|
+
done
|
|
160
|
+
echo " → $opencode_count skills copied to $OPN_SKILLS/"
|
|
161
|
+
SYNC_OPENCODE_COUNT=$opencode_count
|
|
162
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e13s03 e39s04
|
|
3
|
+
# Render helpers for sync-skills.sh — build_body + per-target render_* functions.
|
|
4
|
+
# Source after skill-common.sh; expects CURSOR_RULES, GEMINI_*, PI_*, OKF_WIKI_* globals.
|
|
5
|
+
|
|
6
|
+
if [ -n "${SYNC_RENDER_LOADED:-}" ]; then
|
|
7
|
+
return 0
|
|
8
|
+
fi
|
|
9
|
+
SYNC_RENDER_LOADED=1
|
|
10
|
+
|
|
11
|
+
build_body() {
|
|
12
|
+
local skill_md="$1"
|
|
13
|
+
local skill_dir="$2"
|
|
14
|
+
local body extra_md
|
|
15
|
+
|
|
16
|
+
body=$(awk '/^---/{f++; next} f>=2{print}' "$skill_md")
|
|
17
|
+
for extra_md in $(find "$skill_dir" -maxdepth 1 -name "*.md" ! -name "SKILL.md" | LC_ALL=C sort); do
|
|
18
|
+
body="$body"$'\n\n'"---"$'\n\n'"$(cat "$extra_md")"
|
|
19
|
+
done
|
|
20
|
+
echo "$body" | grep -v 'disable-model-invocation'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render_cursor() {
|
|
24
|
+
local cursor_file="$CURSOR_RULES/$IR_NAME.mdc"
|
|
25
|
+
{
|
|
26
|
+
echo "---"
|
|
27
|
+
echo "description: \"$IR_DESC_ESCAPED\""
|
|
28
|
+
echo "alwaysApply: false"
|
|
29
|
+
echo "---"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "$IR_BODY"
|
|
32
|
+
} > "$cursor_file"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render_gemini_skill() {
|
|
36
|
+
mkdir -p "$GEMINI_SKILLS/$IR_NAME"
|
|
37
|
+
{
|
|
38
|
+
echo "---"
|
|
39
|
+
echo "name: $IR_NAME"
|
|
40
|
+
echo "description: \"$IR_DESC_ESCAPED\""
|
|
41
|
+
echo "---"
|
|
42
|
+
echo ""
|
|
43
|
+
echo "$IR_BODY"
|
|
44
|
+
} > "$GEMINI_SKILLS/$IR_NAME/SKILL.md"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render_gemini_command() {
|
|
48
|
+
mkdir -p "$GEMINI_COMMANDS/prompts"
|
|
49
|
+
local prompt_file="commands/prompts/$IR_NAME.md"
|
|
50
|
+
echo "$IR_BODY" > "$GEMINI_EXT_DIR/$prompt_file"
|
|
51
|
+
{
|
|
52
|
+
echo "description = \"$IR_DESC_ESCAPED\""
|
|
53
|
+
echo "prompt = \"@{$prompt_file}\""
|
|
54
|
+
} > "$GEMINI_COMMANDS/$IR_NAME.toml"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render_pi_skill() {
|
|
58
|
+
mkdir -p "$PI_SKILLS/$IR_NAME"
|
|
59
|
+
{
|
|
60
|
+
echo "---"
|
|
61
|
+
echo "name: $IR_NAME"
|
|
62
|
+
echo "description: \"$IR_DESC_ESCAPED\""
|
|
63
|
+
[[ -n "$IR_MODEL" ]] && echo "model: $IR_MODEL"
|
|
64
|
+
echo "---"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "$IR_BODY"
|
|
67
|
+
} > "$PI_SKILLS/$IR_NAME/SKILL.md"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render_pi_prompt() {
|
|
71
|
+
{
|
|
72
|
+
echo "---"
|
|
73
|
+
echo "description: $IR_DESCRIPTION"
|
|
74
|
+
echo "---"
|
|
75
|
+
echo ""
|
|
76
|
+
echo "$IR_BODY"
|
|
77
|
+
} > "$PI_PROMPTS/$IR_NAME.md"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render_okf_concept() {
|
|
81
|
+
mkdir -p "$OKF_WIKI_SKILLS"
|
|
82
|
+
local okf_file="$OKF_WIKI_SKILLS/$IR_NAME.md"
|
|
83
|
+
local phase="utility" title desc_escaped short_desc effort_val="${_PF_EFFORT:-standard}"
|
|
84
|
+
|
|
85
|
+
case "$IR_NAME" in
|
|
86
|
+
survey-context|elaborate-spec|model-domain|define-language|\
|
|
87
|
+
design-interface|plan-work|plan-release|slice-tasks|\
|
|
88
|
+
kickoff-branch|develop-tdd|verify-work|audit-code|\
|
|
89
|
+
commit-message|release-branch|security-review|\
|
|
90
|
+
assess-impact|investigate-bug|validate-fix|dispatch-agents|\
|
|
91
|
+
request-review|respond-review|hook-commits|guard-git|\
|
|
92
|
+
wire-observability|wire-ci|smoke-test|deploy|enforce-first)
|
|
93
|
+
phase="critical-path" ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
title=$(echo "$IR_NAME" | sed -E 's/-/ /g; s/\b(.)/\U\1/g')
|
|
97
|
+
desc_escaped=$(echo "$IR_DESCRIPTION" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
98
|
+
short_desc="$IR_DESCRIPTION"
|
|
99
|
+
[[ ${#short_desc} -gt 200 ]] && short_desc="${short_desc:0:197}..."
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
echo "---"
|
|
103
|
+
echo "okf_kind: concept"
|
|
104
|
+
echo "type: Skill"
|
|
105
|
+
echo "id: $IR_NAME"
|
|
106
|
+
echo "title: \"$title\""
|
|
107
|
+
echo "name: $IR_NAME"
|
|
108
|
+
echo "category: skills"
|
|
109
|
+
[[ -n "$IR_MODEL" ]] && echo "model: $IR_MODEL"
|
|
110
|
+
echo "effort: $effort_val"
|
|
111
|
+
echo "phase: $phase"
|
|
112
|
+
echo "description: \"$desc_escaped\""
|
|
113
|
+
echo "references: []"
|
|
114
|
+
echo "---"
|
|
115
|
+
echo ""
|
|
116
|
+
echo "# $title"
|
|
117
|
+
echo ""
|
|
118
|
+
echo "**Phase:** $phase"
|
|
119
|
+
[[ -n "$IR_MODEL" ]] && echo "**Model:** $IR_MODEL"
|
|
120
|
+
echo "**Effort:** $effort_val"
|
|
121
|
+
echo ""
|
|
122
|
+
echo "$IR_DESCRIPTION"
|
|
123
|
+
echo ""
|
|
124
|
+
echo "> Auto-generated by sync-skills.sh --okf (e39s04) from $SKILLS_ROOT/$IR_NAME/SKILL.md"
|
|
125
|
+
} > "$okf_file"
|
|
126
|
+
}
|
|
@@ -8,10 +8,7 @@ Usage: called by scripts/trace-stories.sh with positional args:
|
|
|
8
8
|
python3 scripts/lib/trace-stories.py <repo_root> <matrix_json> <trace_md>
|
|
9
9
|
<okf_dir> <strict> <mode>
|
|
10
10
|
|
|
11
|
-
Oracle tiers (
|
|
12
|
-
Tier 1: explicit story tag → confidence high
|
|
13
|
-
Tier 2: file-name heuristic match → confidence medium
|
|
14
|
-
Tier 3: epic capsule task reference → confidence low
|
|
11
|
+
Oracle tiers: Tier 1 explicit tag (high), Tier 2 file heuristic (medium), Tier 3 task ref (low).
|
|
15
12
|
"""
|
|
16
13
|
|
|
17
14
|
import json, os, re, subprocess, sys
|
|
@@ -26,9 +23,7 @@ OKF_DIR = Path(sys.argv[4])
|
|
|
26
23
|
STRICT = int(sys.argv[5])
|
|
27
24
|
MODE = sys.argv[6]
|
|
28
25
|
|
|
29
|
-
#
|
|
30
|
-
# 1. Parse YAML files → story inventory
|
|
31
|
-
# -----------------------------------------------------------------------
|
|
26
|
+
# --- 1. Parse YAML files → story inventory
|
|
32
27
|
_MIN_STORY_BASELINE = 50 # floor assertion: --strict FAILs if story count drops below this
|
|
33
28
|
_STRICT_UNIMPLEMENTED_STATUSES = frozenset({"backlog", "todo", "planned"})
|
|
34
29
|
|
|
@@ -45,9 +40,7 @@ release = _load_yaml(ROOT / "specs" / "release-plan.yaml")
|
|
|
45
40
|
exec_status = _load_yaml(ROOT / "specs" / "execution-status.yaml")
|
|
46
41
|
dev_status = exec_status.get("development_status", {})
|
|
47
42
|
|
|
48
|
-
#
|
|
49
|
-
# 2. Build story inventory
|
|
50
|
-
# -----------------------------------------------------------------------
|
|
43
|
+
# --- 2. Build story inventory
|
|
51
44
|
stories: dict[str, dict] = {}
|
|
52
45
|
epics = release.get("epics", [])
|
|
53
46
|
if not isinstance(epics, list):
|
|
@@ -82,9 +75,7 @@ for epic in epics:
|
|
|
82
75
|
"bcp": s.get("bcp", 0), "wsjf": float(ewsjf),
|
|
83
76
|
"description": s.get("description", "")}
|
|
84
77
|
|
|
85
|
-
#
|
|
86
|
-
# 3. Grep codebase for story tags
|
|
87
|
-
# -----------------------------------------------------------------------
|
|
78
|
+
# --- 3. Grep codebase for story tags
|
|
88
79
|
result = subprocess.run(
|
|
89
80
|
["grep", "-rn", "--include=*.md", "--include=*.sh", "--include=*.py",
|
|
90
81
|
"--include=*.js", "--include=*.ts", "--include=*.yaml", "--include=*.yml",
|
|
@@ -104,9 +95,7 @@ for line in result.stdout.splitlines():
|
|
|
104
95
|
tagged_sids.add(sid)
|
|
105
96
|
tag_index.setdefault(sid, []).append({"file": fpath, "line": fline})
|
|
106
97
|
|
|
107
|
-
#
|
|
108
|
-
# 4. Oracle tiers
|
|
109
|
-
# -----------------------------------------------------------------------
|
|
98
|
+
# --- 4. Oracle tiers
|
|
110
99
|
EXCLUDE_DIRS = {".git", "node_modules", ".cursor", ".gemini", ".pi", ".venv", "venv", ".tox", "__pycache__", "site-packages", ".mypy_cache", ".pytest_cache", ".ruff_cache"}
|
|
111
100
|
EXCLUDE_PREFIXES = ("specs/archive/", "specs/codebase-wiki/")
|
|
112
101
|
|
|
@@ -177,9 +166,7 @@ for sid, sinfo in sorted(stories.items()):
|
|
|
177
166
|
orphan_tags = [sid for sid in sorted(tagged_sids) if sid not in stories]
|
|
178
167
|
stale_tags = [sid for sid in sorted(tagged_sids) if sid in stories and dev_status.get(sid) == "done"]
|
|
179
168
|
|
|
180
|
-
#
|
|
181
|
-
# 5. Emit matrix JSON
|
|
182
|
-
# -----------------------------------------------------------------------
|
|
169
|
+
# --- 5. Emit matrix JSON
|
|
183
170
|
matrix = {
|
|
184
171
|
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
185
172
|
"matrix_version": "1.0",
|
|
@@ -200,9 +187,7 @@ matrix = {
|
|
|
200
187
|
MATRIX_JSON.parent.mkdir(parents=True, exist_ok=True)
|
|
201
188
|
MATRIX_JSON.write_text(json.dumps(matrix, indent=2), encoding="utf-8")
|
|
202
189
|
|
|
203
|
-
#
|
|
204
|
-
# 6. Emit TRACEABILITY_LATEST.md
|
|
205
|
-
# -----------------------------------------------------------------------
|
|
190
|
+
# --- 6. Emit TRACEABILITY_LATEST.md
|
|
206
191
|
lines = ["# Traceability Matrix", "",
|
|
207
192
|
f"**Generated:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}",
|
|
208
193
|
f"**Total stories:** {len(stories)}",
|
|
@@ -237,9 +222,7 @@ if stale_tags:
|
|
|
237
222
|
TRACE_MD.parent.mkdir(parents=True, exist_ok=True)
|
|
238
223
|
TRACE_MD.write_text("\n".join(lines), encoding="utf-8")
|
|
239
224
|
|
|
240
|
-
#
|
|
241
|
-
# 7. Emit OKF bundle (specs/codebase-wiki/)
|
|
242
|
-
# -----------------------------------------------------------------------
|
|
225
|
+
# --- 7. Emit OKF bundle (specs/codebase-wiki/)
|
|
243
226
|
OKF_DIR.mkdir(parents=True, exist_ok=True)
|
|
244
227
|
idx_lines = ["---", "type: Index",
|
|
245
228
|
f"generated_at: {datetime.now(timezone.utc).isoformat()}",
|
|
@@ -278,9 +261,7 @@ for s in matrix_stories:
|
|
|
278
261
|
concept.append("")
|
|
279
262
|
(OKF_DIR / f"{s['id']}.md").write_text("\n".join(concept), encoding="utf-8")
|
|
280
263
|
|
|
281
|
-
#
|
|
282
|
-
# 8. Strict mode
|
|
283
|
-
# -----------------------------------------------------------------------
|
|
264
|
+
# --- 8. Strict mode
|
|
284
265
|
if STRICT:
|
|
285
266
|
# Floor assertion — anti-vacuity guard: if story count drops below baseline
|
|
286
267
|
# the matrix is degenerate and --strict must fail open.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# story: e39s04
|
|
3
|
+
"""Add cross-references from skill-graph.json into OKF concept files."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> int:
|
|
13
|
+
graph_json = Path(sys.argv[1])
|
|
14
|
+
wiki_skills = Path(sys.argv[2])
|
|
15
|
+
|
|
16
|
+
with open(graph_json, encoding="utf-8") as f:
|
|
17
|
+
graph = json.load(f)
|
|
18
|
+
|
|
19
|
+
edges = graph.get("edges", [])
|
|
20
|
+
nodes = {n.get("id") or n.get("name") for n in graph.get("nodes", [])}
|
|
21
|
+
nodes.discard(None)
|
|
22
|
+
|
|
23
|
+
refs: dict[str, list[dict[str, str]]] = {}
|
|
24
|
+
for edge in edges:
|
|
25
|
+
src = edge.get("from") or edge.get("source")
|
|
26
|
+
tgt = edge.get("to") or edge.get("target")
|
|
27
|
+
rel = edge.get("relationType") or edge.get("relation") or "references"
|
|
28
|
+
if src in nodes and tgt in nodes:
|
|
29
|
+
refs.setdefault(src, []).append({"concept": tgt, "type": rel})
|
|
30
|
+
|
|
31
|
+
skill_names = {f.stem for f in wiki_skills.glob("*.md")}
|
|
32
|
+
count_updated = 0
|
|
33
|
+
|
|
34
|
+
for path in wiki_skills.glob("*.md"):
|
|
35
|
+
name = path.stem
|
|
36
|
+
content = path.read_text(encoding="utf-8")
|
|
37
|
+
concept_refs = list(refs.get(name, []))
|
|
38
|
+
|
|
39
|
+
desc_match = re.search(r'description:\s*"(.*?)"', content, re.DOTALL)
|
|
40
|
+
if desc_match:
|
|
41
|
+
desc = desc_match.group(1)
|
|
42
|
+
for skill_name in skill_names:
|
|
43
|
+
if (
|
|
44
|
+
skill_name != name
|
|
45
|
+
and skill_name in desc
|
|
46
|
+
and not any(r["concept"] == skill_name for r in concept_refs)
|
|
47
|
+
):
|
|
48
|
+
concept_refs.append({"concept": skill_name, "type": "references"})
|
|
49
|
+
|
|
50
|
+
seen: set[tuple[str, str]] = set()
|
|
51
|
+
unique_refs: list[dict[str, str]] = []
|
|
52
|
+
for ref in concept_refs:
|
|
53
|
+
key = (ref["concept"], ref["type"])
|
|
54
|
+
if key not in seen:
|
|
55
|
+
seen.add(key)
|
|
56
|
+
unique_refs.append(ref)
|
|
57
|
+
|
|
58
|
+
if unique_refs:
|
|
59
|
+
ref_lines = []
|
|
60
|
+
for ref in unique_refs:
|
|
61
|
+
ref_lines.append(f' - concept: {ref["concept"]}')
|
|
62
|
+
ref_lines.append(f' type: {ref["type"]}')
|
|
63
|
+
ref_block = "\n".join(ref_lines)
|
|
64
|
+
content = re.sub(r"references:\s*\[\]", f"references:\n{ref_block}", content)
|
|
65
|
+
else:
|
|
66
|
+
content = re.sub(
|
|
67
|
+
r"references:\s*\[\]",
|
|
68
|
+
"references:\n - concept: skills-wiki\n type: belongs_to",
|
|
69
|
+
content,
|
|
70
|
+
)
|
|
71
|
+
count_updated += 1
|
|
72
|
+
path.write_text(content, encoding="utf-8")
|
|
73
|
+
|
|
74
|
+
print(f" concepts with cross-references: {count_updated}", file=sys.stderr)
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
raise SystemExit(main())
|
package/scripts/sync-skills.sh
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
12
12
|
|
|
13
|
-
# ── Parse CLI flags (must happen before source to allow early --help) ─
|
|
14
13
|
OKF_MODE=0
|
|
15
14
|
OPN_TARGET=""
|
|
16
15
|
|
|
@@ -31,7 +30,10 @@ for arg in "$@"; do
|
|
|
31
30
|
esac
|
|
32
31
|
done
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
_LIB_DIR="$(dirname "${BASH_SOURCE[0]}")/lib"
|
|
34
|
+
source "$_LIB_DIR/skill-common.sh"
|
|
35
|
+
source "$_LIB_DIR/sync-render.sh"
|
|
36
|
+
source "$_LIB_DIR/sync-post.sh"
|
|
35
37
|
resolve_repo_root
|
|
36
38
|
|
|
37
39
|
CURSOR_RULES="$REPO_ROOT/.cursor/rules"
|
|
@@ -42,220 +44,14 @@ GEMINI_MANIFEST="$GEMINI_EXT_DIR/gemini-extension.json"
|
|
|
42
44
|
PI_SKILLS="$REPO_ROOT/.pi/skills"
|
|
43
45
|
PI_PROMPTS="$REPO_ROOT/.pi/prompts"
|
|
44
46
|
PI_PACKAGE_JSON="$REPO_ROOT/.pi/package.json"
|
|
45
|
-
|
|
46
|
-
mkdir -p "$CURSOR_RULES" "$GEMINI_SKILLS" "$GEMINI_COMMANDS" "$PI_SKILLS" "$PI_PROMPTS"
|
|
47
|
-
|
|
48
|
-
# Clear old artifacts to ensure a clean sync
|
|
49
|
-
rm -rf "${GEMINI_SKILLS:?}"/*
|
|
50
|
-
rm -rf "${GEMINI_COMMANDS:?}"/*
|
|
51
|
-
rm -rf "${PI_SKILLS:?}"/*
|
|
52
|
-
rm -rf "${PI_PROMPTS:?}"/*
|
|
53
|
-
|
|
54
|
-
# ── IR: shared intermediate representation ──────────────────────────
|
|
55
|
-
# These globals are set by the main loop per skill and consumed by render functions.
|
|
56
|
-
IR_NAME=""
|
|
57
|
-
IR_MODEL=""
|
|
58
|
-
IR_DESCRIPTION=""
|
|
59
|
-
IR_DESC_ESCAPED=""
|
|
60
|
-
IR_BODY=""
|
|
61
|
-
|
|
62
|
-
# ── build_body ──────────────────────────────────────────────────────
|
|
63
|
-
# Builds the concatenated content: SKILL.md body + all other *.md files.
|
|
64
|
-
# Args: $1 = path to SKILL.md, $2 = skill directory
|
|
65
|
-
build_body() {
|
|
66
|
-
local skill_md="$1"
|
|
67
|
-
local skill_dir="$2"
|
|
68
|
-
local body
|
|
69
|
-
|
|
70
|
-
# Strip frontmatter from SKILL.md (content after second ---)
|
|
71
|
-
body=$(awk '/^---/{f++; next} f>=2{print}' "$skill_md")
|
|
72
|
-
|
|
73
|
-
# Append extra *.md files alphabetically
|
|
74
|
-
local extra_md
|
|
75
|
-
for extra_md in $(find "$skill_dir" -maxdepth 1 -name "*.md" ! -name "SKILL.md" | LC_ALL=C sort); do
|
|
76
|
-
body="$body"$'\n\n'"---"$'\n\n'"$(cat "$extra_md")"
|
|
77
|
-
done
|
|
78
|
-
|
|
79
|
-
# Strip disable-model-invocation lines
|
|
80
|
-
echo "$body" | grep -v 'disable-model-invocation'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# ── Render target: Cursor ───────────────────────────────────────────
|
|
84
|
-
# Writes .cursor/rules/<name>.mdc with YAML frontmatter + body.
|
|
85
|
-
render_cursor() {
|
|
86
|
-
local cursor_file="$CURSOR_RULES/$IR_NAME.mdc"
|
|
87
|
-
{
|
|
88
|
-
echo "---"
|
|
89
|
-
echo "description: \"$IR_DESC_ESCAPED\""
|
|
90
|
-
echo "alwaysApply: false"
|
|
91
|
-
echo "---"
|
|
92
|
-
echo ""
|
|
93
|
-
echo "$IR_BODY"
|
|
94
|
-
} > "$cursor_file"
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
# ── Render target: Gemini Agent Skill ───────────────────────────────
|
|
98
|
-
# Writes .gemini/extensions/bigpowers/skills/<name>/SKILL.md
|
|
99
|
-
render_gemini_skill() {
|
|
100
|
-
mkdir -p "$GEMINI_SKILLS/$IR_NAME"
|
|
101
|
-
{
|
|
102
|
-
echo "---"
|
|
103
|
-
echo "name: $IR_NAME"
|
|
104
|
-
echo "description: \"$IR_DESC_ESCAPED\""
|
|
105
|
-
echo "---"
|
|
106
|
-
echo ""
|
|
107
|
-
echo "$IR_BODY"
|
|
108
|
-
} > "$GEMINI_SKILLS/$IR_NAME/SKILL.md"
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
# ── Render target: Gemini Slash Command ─────────────────────────────
|
|
112
|
-
# Writes .gemini/extensions/bigpowers/commands/<name>.toml + prompt file
|
|
113
|
-
render_gemini_command() {
|
|
114
|
-
mkdir -p "$GEMINI_COMMANDS/prompts"
|
|
115
|
-
local prompt_file="commands/prompts/$IR_NAME.md"
|
|
116
|
-
echo "$IR_BODY" > "$GEMINI_EXT_DIR/$prompt_file"
|
|
117
|
-
{
|
|
118
|
-
echo "description = \"$IR_DESC_ESCAPED\""
|
|
119
|
-
echo "prompt = \"@{$prompt_file}\""
|
|
120
|
-
} > "$GEMINI_COMMANDS/$IR_NAME.toml"
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
# ── Render target: pi Agent Skill ───────────────────────────────────
|
|
124
|
-
# Writes .pi/skills/<name>/SKILL.md
|
|
125
|
-
render_pi_skill() {
|
|
126
|
-
mkdir -p "$PI_SKILLS/$IR_NAME"
|
|
127
|
-
{
|
|
128
|
-
echo "---"
|
|
129
|
-
echo "name: $IR_NAME"
|
|
130
|
-
echo "description: \"$IR_DESC_ESCAPED\""
|
|
131
|
-
[[ -n "$IR_MODEL" ]] && echo "model: $IR_MODEL"
|
|
132
|
-
echo "---"
|
|
133
|
-
echo ""
|
|
134
|
-
echo "$IR_BODY"
|
|
135
|
-
} > "$PI_SKILLS/$IR_NAME/SKILL.md"
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
# ── Render target: pi Prompt Template ───────────────────────────────
|
|
139
|
-
# Writes .pi/prompts/<name>.md
|
|
140
|
-
render_pi_prompt() {
|
|
141
|
-
{
|
|
142
|
-
echo "---"
|
|
143
|
-
echo "description: $IR_DESCRIPTION"
|
|
144
|
-
echo "---"
|
|
145
|
-
echo ""
|
|
146
|
-
echo "$IR_BODY"
|
|
147
|
-
} > "$PI_PROMPTS/$IR_NAME.md"
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
# ── Render target: OKF Concept (e39s04) ──────────────────────────────
|
|
151
|
-
# Writes specs/skills-wiki/skills/<name>.md with OKF concept frontmatter.
|
|
152
|
-
# Globals used: IR_NAME, IR_MODEL, IR_DESCRIPTION, IR_DESC_ESCAPED, IR_BODY
|
|
153
|
-
# Also reads _PF_EFFORT from the extended parse_frontmatter.
|
|
154
47
|
OKF_WIKI_SKILLS="$REPO_ROOT/specs/skills-wiki/skills"
|
|
155
48
|
OKF_WIKI_DIR="$REPO_ROOT/specs/skills-wiki"
|
|
156
49
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
local okf_file="$OKF_WIKI_SKILLS/$IR_NAME.md"
|
|
160
|
-
|
|
161
|
-
# Determine phase: critical-path skills per the bigpowers core loop
|
|
162
|
-
local phase="utility"
|
|
163
|
-
case "$IR_NAME" in
|
|
164
|
-
survey-context|elaborate-spec|model-domain|define-language|\
|
|
165
|
-
design-interface|plan-work|plan-release|slice-tasks|\
|
|
166
|
-
kickoff-branch|develop-tdd|verify-work|audit-code|\
|
|
167
|
-
commit-message|release-branch|security-review|\
|
|
168
|
-
assess-impact|investigate-bug|validate-fix|dispatch-agents|\
|
|
169
|
-
request-review|respond-review|hook-commits|guard-git|\
|
|
170
|
-
wire-observability|wire-ci|smoke-test|deploy|enforce-first)
|
|
171
|
-
phase="critical-path" ;;
|
|
172
|
-
esac
|
|
173
|
-
|
|
174
|
-
# Build title from name: kebab-case → Title Case
|
|
175
|
-
local title
|
|
176
|
-
title=$(echo "$IR_NAME" | sed -E 's/-/ /g; s/\b(.)/\U\1/g')
|
|
177
|
-
|
|
178
|
-
# Escape description for YAML double-quoted string
|
|
179
|
-
local desc_escaped
|
|
180
|
-
desc_escaped=$(echo "$IR_DESCRIPTION" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
181
|
-
|
|
182
|
-
# Truncate description to avoid overly long lines (OKF convention)
|
|
183
|
-
local short_desc="$IR_DESCRIPTION"
|
|
184
|
-
if [[ ${#short_desc} -gt 200 ]]; then
|
|
185
|
-
short_desc="${short_desc:0:197}..."
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
local effort_val="${_PF_EFFORT:-standard}"
|
|
189
|
-
|
|
190
|
-
{
|
|
191
|
-
echo "---"
|
|
192
|
-
echo "okf_kind: concept"
|
|
193
|
-
echo "type: Skill"
|
|
194
|
-
echo "id: $IR_NAME"
|
|
195
|
-
echo "title: \"$title\""
|
|
196
|
-
echo "name: $IR_NAME"
|
|
197
|
-
echo "category: skills"
|
|
198
|
-
[[ -n "$IR_MODEL" ]] && echo "model: $IR_MODEL"
|
|
199
|
-
echo "effort: $effort_val"
|
|
200
|
-
echo "phase: $phase"
|
|
201
|
-
echo "description: \"$desc_escaped\""
|
|
202
|
-
echo "references: []"
|
|
203
|
-
echo "---"
|
|
204
|
-
echo ""
|
|
205
|
-
echo "# $title"
|
|
206
|
-
echo ""
|
|
207
|
-
echo "**Phase:** $phase"
|
|
208
|
-
[[ -n "$IR_MODEL" ]] && echo "**Model:** $IR_MODEL"
|
|
209
|
-
echo "**Effort:** $effort_val"
|
|
210
|
-
echo ""
|
|
211
|
-
echo "$IR_DESCRIPTION"
|
|
212
|
-
echo ""
|
|
213
|
-
echo "> Auto-generated by sync-skills.sh --okf (e39s04) from $SKILLS_ROOT/$IR_NAME/SKILL.md"
|
|
214
|
-
} > "$okf_file"
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
# ── Parse extended frontmatter for OKF (includes effort) ────────────
|
|
218
|
-
# Wrapper for library parse_frontmatter — sets _PF_* compat vars
|
|
219
|
-
parse_frontmatter() {
|
|
220
|
-
# Delegate to library which now sets _PF_* vars
|
|
221
|
-
if command -v skill_common_parse >/dev/null 2>&1 || [ -n "${SKILL_COMMON_LOADED:-}" ]; then
|
|
222
|
-
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh" 2>/dev/null
|
|
223
|
-
fi
|
|
224
|
-
# Call library version (defined in skill-common.sh, sets SKILL_NAME and _PF_*)
|
|
225
|
-
# But since we have the same name, we need the inline version
|
|
226
|
-
_PF_NAME=$(awk '/^---/{f++} f==1 && /^name:/{print; exit}' "$1" | sed 's/^name:[[:space:]]*//')
|
|
227
|
-
_PF_MODEL=$(awk '/^---/{f++} f==1 && /^model:/{print; exit}' "$1" | sed 's/^model:[[:space:]]*//')
|
|
228
|
-
_PF_DESCRIPTION=$(awk '/^---/{f++; next} f==1 && /^description:/{p=1; sub(/^description:[[:space:]]*/,""); print; next} f==1 && p && /^[a-z]+:/{exit} f==1 && p{print}' "$1" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
|
229
|
-
[[ -z "$_PF_NAME" ]] && return 1
|
|
230
|
-
return 0
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
# Extended parse_frontmatter for OKF mode (adds _PF_EFFORT)
|
|
234
|
-
parse_frontmatter_okf() {
|
|
235
|
-
local file="$1"
|
|
236
|
-
_PF_EFFORT=""
|
|
237
|
-
|
|
238
|
-
if [[ ! -f "$file" ]]; then
|
|
239
|
-
return 1
|
|
240
|
-
fi
|
|
241
|
-
|
|
242
|
-
if ! grep -q '^---$' "$file"; then
|
|
243
|
-
return 1
|
|
244
|
-
fi
|
|
245
|
-
|
|
246
|
-
_PF_NAME=$(awk '/^---/{f++} f==1 && /^name:/{print; exit}' "$file" | sed 's/^name:[[:space:]]*//')
|
|
247
|
-
_PF_MODEL=$(awk '/^---/{f++} f==1 && /^model:/{print; exit}' "$file" | sed 's/^model:[[:space:]]*//')
|
|
248
|
-
_PF_EFFORT=$(awk '/^---/{f++} f==1 && /^effort:/{print; exit}' "$file" | sed 's/^effort:[[:space:]]*//')
|
|
249
|
-
_PF_DESCRIPTION=$(awk '/^---/{f++; next} f==1 && /^description:/{p=1; sub(/^description:[[:space:]]*/,""); print; next} f==1 && p && /^[a-z]+:/{exit} f==1 && p{print}' "$file" \
|
|
250
|
-
| tr -d '\n' \
|
|
251
|
-
| sed -E 's/[[:space:]]+/ /g')
|
|
50
|
+
mkdir -p "$CURSOR_RULES" "$GEMINI_SKILLS" "$GEMINI_COMMANDS" "$PI_SKILLS" "$PI_PROMPTS"
|
|
51
|
+
rm -rf "${GEMINI_SKILLS:?}"/* "${GEMINI_COMMANDS:?}"/* "${PI_SKILLS:?}"/* "${PI_PROMPTS:?}"/*
|
|
252
52
|
|
|
253
|
-
|
|
254
|
-
return 0
|
|
255
|
-
}
|
|
53
|
+
IR_NAME="" IR_MODEL="" IR_DESCRIPTION="" IR_DESC_ESCAPED="" IR_BODY=""
|
|
256
54
|
|
|
257
|
-
# ── Target registry (e37s07) ────────────────────────────────────────
|
|
258
|
-
# Load skill adapters from scripts/targets.yaml — no hardcoded target list.
|
|
259
55
|
TARGETS_FILE="$REPO_ROOT/scripts/targets.yaml"
|
|
260
56
|
SKILL_ADAPTERS=()
|
|
261
57
|
if [[ -f "$TARGETS_FILE" ]] && command -v yq >/dev/null 2>&1; then
|
|
@@ -269,8 +65,6 @@ else
|
|
|
269
65
|
fi
|
|
270
66
|
|
|
271
67
|
skill_count=0
|
|
272
|
-
|
|
273
|
-
# ── Main loop: Parse → IR → Render per skill ────────────────────────
|
|
274
68
|
while IFS= read -r skill_dir; do
|
|
275
69
|
skill_md="$skill_dir/SKILL.md"
|
|
276
70
|
|
|
@@ -283,16 +77,12 @@ while IFS= read -r skill_dir; do
|
|
|
283
77
|
IR_NAME="$_PF_NAME"
|
|
284
78
|
IR_MODEL="$_PF_MODEL"
|
|
285
79
|
IR_DESCRIPTION="$_PF_DESCRIPTION"
|
|
286
|
-
|
|
287
|
-
# Escape double quotes and backslashes for safe double-quoted YAML output
|
|
288
80
|
IR_DESC_ESCAPED=$(echo "$IR_DESCRIPTION" | sed 's/\\/\\\\/g; s/\"/\\"/g')
|
|
289
|
-
|
|
290
81
|
[[ -z "$IR_NAME" ]] && continue
|
|
291
82
|
|
|
292
83
|
IR_BODY=$(build_body "$skill_md" "$skill_dir")
|
|
293
84
|
SKILL_MD_PATH="$skill_md"
|
|
294
85
|
|
|
295
|
-
# Dispatch to each skill adapter from registry (e37s07)
|
|
296
86
|
for adapter_name in "${SKILL_ADAPTERS[@]}"; do
|
|
297
87
|
adapter_script="$REPO_ROOT/scripts/adapters/${adapter_name}.sh"
|
|
298
88
|
# shellcheck source=/dev/null
|
|
@@ -300,408 +90,18 @@ while IFS= read -r skill_dir; do
|
|
|
300
90
|
render_skill
|
|
301
91
|
done
|
|
302
92
|
|
|
303
|
-
|
|
304
|
-
render_okf_concept
|
|
305
|
-
fi
|
|
306
|
-
|
|
93
|
+
[[ "$OKF_MODE" -eq 1 ]] && render_okf_concept
|
|
307
94
|
skill_count=$((skill_count + 1))
|
|
308
95
|
done < <(iterate_skills)
|
|
309
96
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
# Assemble final gemini-extension.json
|
|
313
|
-
pkg_version=$(jq -r '.version' "$REPO_ROOT/package.json")
|
|
314
|
-
pkg_desc=$(jq -r '.description' "$REPO_ROOT/package.json")
|
|
315
|
-
|
|
316
|
-
jq -n --arg name "bigpowers" \
|
|
317
|
-
--arg version "$pkg_version" \
|
|
318
|
-
--arg desc "${skill_count} skills — ${pkg_desc}" \
|
|
319
|
-
'{name: $name, version: $version, description: $desc}' > "$GEMINI_MANIFEST"
|
|
320
|
-
|
|
321
|
-
# Write pi package config: .pi/package.json
|
|
322
|
-
jq -n --arg version "$pkg_version" \
|
|
323
|
-
--arg desc "${skill_count} skills — ${pkg_desc}" \
|
|
324
|
-
'{
|
|
325
|
-
"name": "bigpowers",
|
|
326
|
-
"version": $version,
|
|
327
|
-
"description": $desc,
|
|
328
|
-
"keywords": ["pi-package"],
|
|
329
|
-
"pi": {
|
|
330
|
-
"skills": ["./skills"],
|
|
331
|
-
"prompts": ["./prompts"]
|
|
332
|
-
}
|
|
333
|
-
}' > "$PI_PACKAGE_JSON"
|
|
334
|
-
|
|
335
|
-
# Write OpenCode configuration: opencode.json
|
|
336
|
-
{
|
|
337
|
-
echo "{"
|
|
338
|
-
echo " \"\$schema\": \"https://opencode.ai/config.json\","
|
|
339
|
-
echo " \"instructions\": [\"CLAUDE.md\", \"CONVENTIONS.md\"]"
|
|
340
|
-
echo "}"
|
|
341
|
-
} > "$REPO_ROOT/opencode.json"
|
|
342
|
-
|
|
343
|
-
# Sync to bigpowers-opencode repo (if --opencode path is provided)
|
|
344
|
-
if [[ -n "$OPN_TARGET" ]] && [[ -d "$OPN_TARGET" ]]; then
|
|
345
|
-
echo ""
|
|
346
|
-
echo "Syncing skills to opencode repo: $OPN_TARGET"
|
|
347
|
-
OPN_SKILLS="$OPN_TARGET/skills"
|
|
348
|
-
mkdir -p "$OPN_SKILLS"
|
|
349
|
-
opencode_count=0
|
|
350
|
-
for skill_dir in "$SKILLS_ROOT"/*/; do
|
|
351
|
-
skill_md="$skill_dir/SKILL.md"
|
|
352
|
-
[[ -f "$skill_md" ]] || continue
|
|
353
|
-
skill_name=$(basename "$skill_dir")
|
|
354
|
-
mkdir -p "$OPN_SKILLS/$skill_name"
|
|
355
|
-
cp "$skill_md" "$OPN_SKILLS/$skill_name/SKILL.md"
|
|
356
|
-
opencode_count=$((opencode_count + 1))
|
|
357
|
-
done
|
|
358
|
-
echo " → $opencode_count skills copied to $OPN_SKILLS/"
|
|
359
|
-
fi
|
|
360
|
-
|
|
361
|
-
# Regenerate skills-lock.json catalog
|
|
362
|
-
if [[ -x "$REPO_ROOT/scripts/regenerate-lockfile.sh" ]]; then
|
|
363
|
-
bash "$REPO_ROOT/scripts/regenerate-lockfile.sh" || { echo "sync-skills: FAIL — lockfile regeneration failed" >&2; exit 1; }
|
|
364
|
-
fi
|
|
365
|
-
|
|
366
|
-
# Regenerate SKILL-INDEX.md from lockfile + frontmatter
|
|
367
|
-
if [[ -x "$REPO_ROOT/scripts/generate-skill-index.sh" ]]; then
|
|
368
|
-
bash "$REPO_ROOT/scripts/generate-skill-index.sh" || { echo "sync-skills: FAIL — SKILL-INDEX.md generation failed" >&2; exit 1; }
|
|
369
|
-
fi
|
|
370
|
-
|
|
371
|
-
# Regenerate lexical skill index for search-skills
|
|
372
|
-
if [[ -x "$REPO_ROOT/scripts/build-skill-index.sh" ]]; then
|
|
373
|
-
bash "$REPO_ROOT/scripts/build-skill-index.sh" || true
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
# Prune orphan cursor rules
|
|
377
|
-
CURSOR_KEEP="context7.mdc"
|
|
378
|
-
for mdc in "$CURSOR_RULES"/*.mdc; do
|
|
379
|
-
[[ -e "$mdc" ]] || continue
|
|
380
|
-
mdc_base=$(basename "$mdc")
|
|
381
|
-
case " $CURSOR_KEEP " in *" $mdc_base "*) continue ;; esac
|
|
382
|
-
if [[ ! -f "$SKILLS_ROOT/${mdc_base%.mdc}/SKILL.md" ]]; then
|
|
383
|
-
rm "$mdc"
|
|
384
|
-
echo " → pruned orphan cursor rule: $mdc_base"
|
|
385
|
-
fi
|
|
386
|
-
done
|
|
387
|
-
|
|
388
|
-
# Stamp the README skill-count badge
|
|
389
|
-
readme="$REPO_ROOT/README.md"
|
|
390
|
-
if [[ -f "$readme" ]] && grep -q 'badge/skills-' "$readme"; then
|
|
391
|
-
readme_tmp=$(mktemp)
|
|
392
|
-
sed -E "s|(badge/skills-)[0-9]+(-brightgreen)|\1${skill_count}\2|" "$readme" > "$readme_tmp"
|
|
393
|
-
mv "$readme_tmp" "$readme"
|
|
394
|
-
fi
|
|
395
|
-
|
|
396
|
-
echo "sync-skills: $skill_count skills synced"
|
|
397
|
-
echo " → .cursor/rules/ ($skill_count .mdc files)"
|
|
398
|
-
echo " → .gemini/extensions/bigpowers/skills/ (Agent Skills)"
|
|
399
|
-
echo " → .gemini/extensions/bigpowers/commands/ (Slash Commands)"
|
|
400
|
-
echo " → .gemini/extensions/bigpowers/gemini-extension.json"
|
|
401
|
-
echo " → .pi/skills/ ($skill_count skill dirs — pi Agent Skills)"
|
|
402
|
-
echo " → .pi/prompts/ ($skill_count prompt templates — pi slash commands)"
|
|
403
|
-
echo " → .pi/package.json (pi package manifest)"
|
|
404
|
-
echo " → skills-lock.json (catalog with SHA-256 hashes)"
|
|
405
|
-
echo " → SKILL-INDEX.md (auto-generated skill reference)"
|
|
406
|
-
echo " → opencode.json (CLAUDE.md + CONVENTIONS.md instructions)"
|
|
407
|
-
[[ -n "$OPN_TARGET" ]] && echo " → bigpowers-opencode: $opencode_count skills"
|
|
97
|
+
opencode_count=0
|
|
98
|
+
[[ -n "$OPN_TARGET" ]] && sync_opencode_copy && opencode_count="${SYNC_OPENCODE_COUNT:-0}"
|
|
408
99
|
|
|
409
|
-
# ── OKF post-sync pipeline (e39s04) ──────────────────────────────────
|
|
410
100
|
if [[ "$OKF_MODE" -eq 1 ]]; then
|
|
411
101
|
echo ""
|
|
412
102
|
echo "--- OKF skills-wiki post-sync ---"
|
|
413
|
-
|
|
414
|
-
# Step 2: Run build-skill-graph.sh to get cross-references in skill-graph.json
|
|
415
|
-
GRAPH_JSON="$REPO_ROOT/specs/skill-graph.json"
|
|
416
|
-
if [[ -x "$REPO_ROOT/scripts/build-skill-graph.sh" ]]; then
|
|
417
|
-
echo "Running build-skill-graph.sh for cross-references..."
|
|
418
|
-
bash "$REPO_ROOT/scripts/build-skill-graph.sh" --json || {
|
|
419
|
-
echo "sync-skills: WARNING — build-skill-graph.sh --json failed; cross-references will be empty" >&2
|
|
420
|
-
}
|
|
421
|
-
else
|
|
422
|
-
echo "sync-skills: WARNING — build-skill-graph.sh not found; skipping cross-references" >&2
|
|
423
|
-
fi
|
|
424
|
-
|
|
425
|
-
# Step 2b: Add cross-references from skill-graph.json edges into OKF concepts
|
|
426
|
-
if [[ -f "$GRAPH_JSON" ]]; then
|
|
427
|
-
echo "Adding cross-references from skill-graph.json..."
|
|
428
|
-
$PYTHON - "$GRAPH_JSON" "$OKF_WIKI_SKILLS" <<'PYEOF'
|
|
429
|
-
import json, sys, re
|
|
430
|
-
from pathlib import Path
|
|
431
|
-
|
|
432
|
-
graph_json = Path(sys.argv[1])
|
|
433
|
-
wiki_skills = Path(sys.argv[2])
|
|
434
|
-
|
|
435
|
-
# Load graph edges
|
|
436
|
-
with open(graph_json) as f:
|
|
437
|
-
graph = json.load(f)
|
|
438
|
-
|
|
439
|
-
edges = graph.get("edges", [])
|
|
440
|
-
nodes = {n["name"] for n in graph.get("nodes", [])}
|
|
441
|
-
|
|
442
|
-
# Build adjacency: skill_name -> list of {concept, type} refs
|
|
443
|
-
refs = {}
|
|
444
|
-
for e in edges:
|
|
445
|
-
src, tgt, rel = e["from"], e["to"], e.get("relationType", "references")
|
|
446
|
-
# Only include edges where both ends are known skills
|
|
447
|
-
if src in nodes and tgt in nodes:
|
|
448
|
-
refs.setdefault(src, []).append({"concept": tgt, "type": rel})
|
|
449
|
-
|
|
450
|
-
# Also detect intra-description references: scan description for skill names
|
|
451
|
-
skill_names = set()
|
|
452
|
-
for f in wiki_skills.glob("*.md"):
|
|
453
|
-
skill_names.add(f.stem)
|
|
454
|
-
|
|
455
|
-
# Update each concept file with references
|
|
456
|
-
count_updated = 0
|
|
457
|
-
for f in wiki_skills.glob("*.md"):
|
|
458
|
-
name = f.stem
|
|
459
|
-
content = f.read_text()
|
|
460
|
-
|
|
461
|
-
# Collect references for this skill
|
|
462
|
-
concept_refs = refs.get(name, [])
|
|
463
|
-
|
|
464
|
-
# Also scan description for mentions of other skill names
|
|
465
|
-
desc_match = re.search(r'description:\s*"(.*?)"', content, re.DOTALL)
|
|
466
|
-
if desc_match:
|
|
467
|
-
desc = desc_match.group(1)
|
|
468
|
-
for sn in skill_names:
|
|
469
|
-
if sn != name and sn in desc and not any(r["concept"] == sn for r in concept_refs):
|
|
470
|
-
concept_refs.append({"concept": sn, "type": "references"})
|
|
471
|
-
|
|
472
|
-
# Deduplicate
|
|
473
|
-
seen = set()
|
|
474
|
-
unique_refs = []
|
|
475
|
-
for r in concept_refs:
|
|
476
|
-
key = (r["concept"], r["type"])
|
|
477
|
-
if key not in seen:
|
|
478
|
-
seen.add(key)
|
|
479
|
-
unique_refs.append(r)
|
|
480
|
-
|
|
481
|
-
# Build YAML references block
|
|
482
|
-
if unique_refs:
|
|
483
|
-
ref_lines = []
|
|
484
|
-
for r in unique_refs:
|
|
485
|
-
ref_lines.append(f' - concept: {r["concept"]}')
|
|
486
|
-
ref_lines.append(f' type: {r["type"]}')
|
|
487
|
-
ref_block = "\n".join(ref_lines)
|
|
488
|
-
content = re.sub(r'references:\s*\[\]', f'references:\n{ref_block}', content)
|
|
489
|
-
count_updated += 1
|
|
490
|
-
else:
|
|
491
|
-
# No edges and no description mentions → self-reference as fallback
|
|
492
|
-
content = re.sub(
|
|
493
|
-
r'references:\s*\[\]',
|
|
494
|
-
f'references:\n - concept: skills-wiki\n type: belongs_to',
|
|
495
|
-
content
|
|
496
|
-
)
|
|
497
|
-
count_updated += 1
|
|
498
|
-
|
|
499
|
-
f.write_text(content)
|
|
500
|
-
|
|
501
|
-
print(f" concepts with cross-references: {count_updated}", file=sys.stderr)
|
|
502
|
-
PYEOF
|
|
503
|
-
else
|
|
504
|
-
echo "sync-skills: WARNING — $GRAPH_JSON not found; adding fallback references" >&2
|
|
505
|
-
# Add fallback references to all concepts
|
|
506
|
-
for f in "$OKF_WIKI_SKILLS"/*.md; do
|
|
507
|
-
[[ -f "$f" ]] || continue
|
|
508
|
-
if command -v $PYTHON &>/dev/null; then
|
|
509
|
-
$PYTHON -c "
|
|
510
|
-
import re, sys
|
|
511
|
-
content = open('$f').read()
|
|
512
|
-
content = re.sub(r'references:\s*\[\]', 'references:\n - concept: skills-wiki\n type: belongs_to', content)
|
|
513
|
-
open('$f', 'w').write(content)
|
|
514
|
-
"
|
|
515
|
-
fi
|
|
516
|
-
done
|
|
517
|
-
fi
|
|
518
|
-
|
|
519
|
-
# Step 3: Generate specs/skills-wiki/index.md with progressive disclosure
|
|
520
|
-
echo "Generating index.md..."
|
|
521
|
-
OKF_INDEX="$OKF_WIKI_DIR/index.md"
|
|
522
|
-
$PYTHON - "$OKF_WIKI_SKILLS" "$OKF_WIKI_DIR" "$OKF_INDEX" <<'PYEOF'
|
|
523
|
-
import json, sys, re
|
|
524
|
-
from pathlib import Path
|
|
525
|
-
from collections import defaultdict
|
|
526
|
-
|
|
527
|
-
wiki_skills = Path(sys.argv[1])
|
|
528
|
-
wiki_dir = Path(sys.argv[2])
|
|
529
|
-
index_path = Path(sys.argv[3])
|
|
530
|
-
|
|
531
|
-
# Collect all concepts
|
|
532
|
-
concepts = []
|
|
533
|
-
for f in sorted(wiki_skills.glob("*.md")):
|
|
534
|
-
content = f.read_text()
|
|
535
|
-
fm_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
|
|
536
|
-
if not fm_match:
|
|
537
|
-
continue
|
|
538
|
-
fm_text = fm_match.group(1)
|
|
539
|
-
# Simple YAML extraction
|
|
540
|
-
name = ""
|
|
541
|
-
title = ""
|
|
542
|
-
phase = "utility"
|
|
543
|
-
model = ""
|
|
544
|
-
effort = ""
|
|
545
|
-
description = ""
|
|
546
|
-
for line in fm_text.split('\n'):
|
|
547
|
-
line = line.strip()
|
|
548
|
-
if line.startswith('name:'):
|
|
549
|
-
name = line.split(':', 1)[1].strip()
|
|
550
|
-
elif line.startswith('title:'):
|
|
551
|
-
title = line.split(':', 1)[1].strip().strip('"')
|
|
552
|
-
elif line.startswith('phase:'):
|
|
553
|
-
phase = line.split(':', 1)[1].strip()
|
|
554
|
-
elif line.startswith('model:'):
|
|
555
|
-
model = line.split(':', 1)[1].strip()
|
|
556
|
-
elif line.startswith('effort:'):
|
|
557
|
-
effort = line.split(':', 1)[1].strip()
|
|
558
|
-
elif line.startswith('description:'):
|
|
559
|
-
description = line.split(':', 1)[1].strip().strip('"')
|
|
560
|
-
if not name:
|
|
561
|
-
continue
|
|
562
|
-
concepts.append({
|
|
563
|
-
"name": name,
|
|
564
|
-
"title": title or name.replace('-', ' ').title(),
|
|
565
|
-
"phase": phase,
|
|
566
|
-
"model": model,
|
|
567
|
-
"effort": effort,
|
|
568
|
-
"description": description[:120] + "..." if len(description) > 120 else description,
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
# Group by phase
|
|
572
|
-
phases = defaultdict(list)
|
|
573
|
-
for c in concepts:
|
|
574
|
-
phases[c["phase"]].append(c)
|
|
575
|
-
|
|
576
|
-
# Count
|
|
577
|
-
total = len(concepts)
|
|
578
|
-
cp_count = len(phases.get("critical-path", []))
|
|
579
|
-
util_count = len(phases.get("utility", []))
|
|
580
|
-
|
|
581
|
-
# Generate index.md
|
|
582
|
-
with open(index_path, "w") as out:
|
|
583
|
-
out.write(f"""---
|
|
584
|
-
okf_kind: concept
|
|
585
|
-
type: Index
|
|
586
|
-
id: skills-wiki
|
|
587
|
-
title: "BigPowers Skills Wiki"
|
|
588
|
-
category: skills
|
|
589
|
-
description: "Progressive-disclosure index of {total} bigpowers skills ({cp_count} critical-path, {util_count} utility) — auto-generated by sync-skills.sh --okf (e39s04)."
|
|
590
|
-
references:
|
|
591
|
-
- concept: bigpowers
|
|
592
|
-
type: belongs_to
|
|
593
|
-
---
|
|
594
|
-
|
|
595
|
-
# BigPowers Skills Wiki
|
|
596
|
-
|
|
597
|
-
**{total} skills** ({cp_count} critical-path, {util_count} utility)
|
|
598
|
-
|
|
599
|
-
> Auto-generated by sync-skills.sh --okf (e39s04).
|
|
600
|
-
> Source of truth: skills/*/SKILL.md files.
|
|
601
|
-
|
|
602
|
-
## Critical-Path Skills ({cp_count})
|
|
603
|
-
|
|
604
|
-
These skills form the bigpowers core development loop — used on every feature.
|
|
605
|
-
|
|
606
|
-
| Skill | Model | Effort | Description |
|
|
607
|
-
|-------|-------|--------|-------------|
|
|
608
|
-
""")
|
|
609
|
-
|
|
610
|
-
for c in phases.get("critical-path", []):
|
|
611
|
-
out.write(f"| [{c['title']}](skills/{c['name']}.md) | {c['model']} | {c['effort']} | {c['description']} |\n")
|
|
612
|
-
|
|
613
|
-
out.write(f"""
|
|
614
|
-
## Utility Skills ({util_count})
|
|
615
|
-
|
|
616
|
-
Supporting skills for specialised workflows.
|
|
617
|
-
|
|
618
|
-
| Skill | Model | Effort | Description |
|
|
619
|
-
|-------|-------|--------|-------------|
|
|
620
|
-
""")
|
|
621
|
-
|
|
622
|
-
for c in phases.get("utility", []):
|
|
623
|
-
out.write(f"| [{c['title']}](skills/{c['name']}.md) | {c['model']} | {c['effort']} | {c['description']} |\n")
|
|
624
|
-
|
|
625
|
-
out.write(f"""
|
|
626
|
-
## Quick Reference
|
|
627
|
-
|
|
628
|
-
- **Total skills:** {total}
|
|
629
|
-
- **Critical-path:** {cp_count}
|
|
630
|
-
- **Utility:** {util_count}
|
|
631
|
-
- **Format:** OKF concept bundles (okf_kind: concept)
|
|
632
|
-
- **Validation:** `bash scripts/validate-okf.sh --dir specs/skills-wiki/skills`
|
|
633
|
-
""")
|
|
634
|
-
|
|
635
|
-
print(f" index.md written: {total} skills indexed", file=sys.stderr)
|
|
636
|
-
PYEOF
|
|
637
|
-
|
|
638
|
-
# Step 4: Validate OKF conformance via validate-okf.sh
|
|
639
|
-
echo ""
|
|
640
|
-
echo "Validating OKF conformance..."
|
|
641
|
-
VALIDATE_SCRIPT="$REPO_ROOT/scripts/validate-okf.sh"
|
|
642
|
-
if [[ -x "$VALIDATE_SCRIPT" ]]; then
|
|
643
|
-
if bash "$VALIDATE_SCRIPT" --dir "$OKF_WIKI_SKILLS" 2>&1; then
|
|
644
|
-
echo "sync-skills: OKF validation PASSED"
|
|
645
|
-
else
|
|
646
|
-
OKF_EXIT=$?
|
|
647
|
-
echo "sync-skills: OKF validation returned exit code $OKF_EXIT (non-zero — check output above)" >&2
|
|
648
|
-
# Don't hard-fail on validation warnings; the bundle is still generated
|
|
649
|
-
fi
|
|
650
|
-
else
|
|
651
|
-
echo "sync-skills: WARNING — validate-okf.sh not found; skipping validation" >&2
|
|
652
|
-
fi
|
|
653
|
-
|
|
654
|
-
OKF_CONCEPT_COUNT=$(find "$OKF_WIKI_SKILLS" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
655
|
-
echo ""
|
|
656
|
-
echo "sync-skills (--okf): $OKF_CONCEPT_COUNT OKF concepts generated"
|
|
657
|
-
echo " → specs/skills-wiki/skills/ ($OKF_CONCEPT_COUNT .md files)"
|
|
658
|
-
echo " → specs/skills-wiki/index.md (progressive-disclosure index)"
|
|
659
|
-
fi
|
|
660
|
-
|
|
661
|
-
# Regression guard (BUG-2026-06-02T164500)
|
|
662
|
-
trace_mdc="$REPO_ROOT/.cursor/rules/trace-requirement.mdc"
|
|
663
|
-
if [[ -f "$trace_mdc" ]] && ! grep -q 'release-plan.yaml + epic' "$trace_mdc"; then
|
|
664
|
-
echo "sync-skills: FAIL — '+' missing from trace-requirement; use sed -E for whitespace collapse" >&2
|
|
665
|
-
exit 1
|
|
666
|
-
fi
|
|
667
|
-
manifest="$REPO_ROOT/.gemini/extensions/bigpowers/gemini-extension.json"
|
|
668
|
-
if [[ -f "$manifest" ]]; then
|
|
669
|
-
ext_ver=$(jq -r '.version // empty' "$manifest")
|
|
670
|
-
pkg_ver=$(jq -r '.version // empty' "$REPO_ROOT/package.json")
|
|
671
|
-
if [[ -z "$ext_ver" || -z "$pkg_ver" ]]; then
|
|
672
|
-
: # skip version compare when either field is missing
|
|
673
|
-
elif [[ "$ext_ver" != "$pkg_ver" ]]; then
|
|
674
|
-
echo "sync-skills: FAIL — gemini-extension.json version ($ext_ver) != package.json ($pkg_ver)" >&2
|
|
675
|
-
exit 1
|
|
676
|
-
fi
|
|
677
|
-
fi
|
|
678
|
-
|
|
679
|
-
# Regression guard (BUG-2026-06-18T100000): validate generated YAML frontmatter
|
|
680
|
-
validate_script="$REPO_ROOT/scripts/validate-skill-yaml.py"
|
|
681
|
-
if [[ -f "$validate_script" ]] && command -v $PYTHON &>/dev/null; then
|
|
682
|
-
if ! $PYTHON "$validate_script" > /dev/null 2>&1; then
|
|
683
|
-
echo "sync-skills: FAIL — YAML frontmatter validation failed" >&2
|
|
684
|
-
$PYTHON "$validate_script" >&2
|
|
685
|
-
exit 1
|
|
686
|
-
fi
|
|
687
|
-
fi
|
|
688
|
-
|
|
689
|
-
# Regression guard (BUG-2026-07-02T103911): no bash 4+ features
|
|
690
|
-
if grep -rn '^declare -A\|^[[:space:]]*declare -A' scripts/sync-skills.sh scripts/generate-skill-index.sh scripts/regenerate-lockfile.sh scripts/build-skill-index.sh 2>/dev/null; then
|
|
691
|
-
echo "sync-skills: FAIL — bash 4+ features detected in install-chain scripts (not macOS-compatible)" >&2
|
|
692
|
-
exit 1
|
|
103
|
+
bash "$REPO_ROOT/scripts/okf-post-sync.sh"
|
|
693
104
|
fi
|
|
694
105
|
|
|
695
|
-
|
|
696
|
-
if [[ -d "$REPO_ROOT/.git" ]]; then
|
|
697
|
-
bash "$REPO_ROOT/scripts/generate-reference-tables.sh"
|
|
698
|
-
fi
|
|
699
|
-
|
|
700
|
-
# ── OKF wiki generation (e45s01) ──────────────────────────────────────
|
|
701
|
-
echo ""
|
|
702
|
-
echo "Generating OKF wikis..."
|
|
703
|
-
bash "$REPO_ROOT/scripts/generate-epics-wiki.sh"
|
|
704
|
-
bash "$REPO_ROOT/scripts/generate-adr-wiki.sh"
|
|
705
|
-
echo "sync-skills: OKF wikis regenerated"
|
|
706
|
-
|
|
106
|
+
sync_post_run "$skill_count" "$opencode_count"
|
|
707
107
|
exit 0
|