bigpowers 2.78.1 → 2.79.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/.pi/package.json +1 -1
- package/CHANGELOG.md +7 -0
- package/CONVENTIONS.md +29 -0
- package/package.json +1 -1
- package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
- package/scripts/test-tombstone-mechanism.sh +111 -0
- package/scripts/tombstone-skill.sh +89 -0
- package/scripts/validate-tombstones.sh +63 -0
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.79.0](https://github.com/danielvm-git/bigpowers/compare/v2.78.1...v2.79.0) (2026-07-23)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **scripts:** implement tombstone-skill.sh and validate-tombstones.sh ([c81a451](https://github.com/danielvm-git/bigpowers/commit/c81a451598d8338188fb9e18b4486e20e6c177f8))
|
|
7
|
+
|
|
1
8
|
## [2.78.1](https://github.com/danielvm-git/bigpowers/compare/v2.78.0...v2.78.1) (2026-07-23)
|
|
2
9
|
|
|
3
10
|
|
package/CONVENTIONS.md
CHANGED
|
@@ -353,6 +353,35 @@ name must return < 5 results across the repo.
|
|
|
353
353
|
|
|
354
354
|
Any new exception requires an entry in this table before the skill is published.
|
|
355
355
|
|
|
356
|
+
## Tombstone Aliases (e53s02)
|
|
357
|
+
|
|
358
|
+
Every skill rename or merge ships a tombstone alias for one release, so consumers
|
|
359
|
+
referencing the old name don't break immediately.
|
|
360
|
+
|
|
361
|
+
**Mechanism:**
|
|
362
|
+
|
|
363
|
+
1. Run `bash scripts/tombstone-skill.sh <old-name> <new-name-or-merge-target>` when
|
|
364
|
+
renaming or merging a skill. It replaces `skills/<old-name>/SKILL.md` with a stub
|
|
365
|
+
pointing at the new name/location, preserves the old skill's `# story: eNNsNN` tags
|
|
366
|
+
for traceability, and registers the mapping in `specs/tombstones.yaml` (old name,
|
|
367
|
+
new name/target, creation timestamp, and the `bigpowers_version` at creation time).
|
|
368
|
+
2. Run `bash scripts/validate-tombstones.sh` to confirm every registered tombstone's
|
|
369
|
+
stub still resolves, and to flag any tombstone whose `created_at_version` differs
|
|
370
|
+
from the current release — that stub has served its one-release transition window
|
|
371
|
+
and should be removed.
|
|
372
|
+
3. **Required alongside the first real use of `tombstone-skill.sh`:** update
|
|
373
|
+
`docs/references/model-profiles.md`'s skill-count annotations. A tombstone stub is
|
|
374
|
+
still a `skills/*/SKILL.md` file — it increments the live skill count the same as
|
|
375
|
+
any other skill, and the count doc must reflect that.
|
|
376
|
+
|
|
377
|
+
**Rejected names:** both the old and new name must be a bare kebab-case skill name
|
|
378
|
+
(`^[a-z][a-z0-9-]*$`) — `tombstone-skill.sh` refuses anything else before touching the
|
|
379
|
+
filesystem (path segments, absolute paths, etc.).
|
|
380
|
+
|
|
381
|
+
This story (e53s02) builds the mechanism only; it creates no real tombstone stub
|
|
382
|
+
itself, so the live skill count doesn't move yet. The first real use is expected in
|
|
383
|
+
e56 (skill reclassification/merges).
|
|
384
|
+
|
|
356
385
|
## File-Size Exceptions
|
|
357
386
|
|
|
358
387
|
The 300-line file cap exists to ensure files fit in a single agent context
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s02
|
|
3
|
+
# test-tombstone-mechanism.sh — exercises tombstone-skill.sh and
|
|
4
|
+
# validate-tombstones.sh end-to-end against throwaway fixtures.
|
|
5
|
+
#
|
|
6
|
+
# Usage: bash scripts/test-tombstone-mechanism.sh
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
10
|
+
resolve_repo_root
|
|
11
|
+
cd "$REPO_ROOT"
|
|
12
|
+
|
|
13
|
+
RED='\033[0;31m'
|
|
14
|
+
GREEN='\033[0;32m'
|
|
15
|
+
NC='\033[0m'
|
|
16
|
+
|
|
17
|
+
FAILURES=0
|
|
18
|
+
tt_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
19
|
+
tt_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
20
|
+
strip_ansi() { sed -E 's/\x1b\[[0-9;]*m//g'; }
|
|
21
|
+
|
|
22
|
+
FIXTURE_OLD="test-tombstone-fixture"
|
|
23
|
+
FIXTURE_NEW="test-tombstone-target"
|
|
24
|
+
FIXTURE_DIR="skills/${FIXTURE_OLD}"
|
|
25
|
+
TOMBSTONES_FILE="specs/tombstones.yaml"
|
|
26
|
+
TOMBSTONES_BACKUP=""
|
|
27
|
+
|
|
28
|
+
cleanup() {
|
|
29
|
+
rm -rf "$FIXTURE_DIR" "skills/${FIXTURE_NEW}"
|
|
30
|
+
if [[ -n "$TOMBSTONES_BACKUP" ]]; then
|
|
31
|
+
mv "$TOMBSTONES_BACKUP" "$TOMBSTONES_FILE"
|
|
32
|
+
else
|
|
33
|
+
rm -f "$TOMBSTONES_FILE"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
trap cleanup EXIT
|
|
37
|
+
|
|
38
|
+
if [[ -f "$TOMBSTONES_FILE" ]]; then
|
|
39
|
+
TOMBSTONES_BACKUP=$(mktemp)
|
|
40
|
+
cp "$TOMBSTONES_FILE" "$TOMBSTONES_BACKUP"
|
|
41
|
+
rm -f "$TOMBSTONES_FILE"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# --- Scenario: zero tombstones registered (6a) ---
|
|
45
|
+
if bash scripts/validate-tombstones.sh 2>&1 | grep -qi "no tombstones"; then
|
|
46
|
+
tt_pass "validate-tombstones.sh reports clean when zero tombstones registered"
|
|
47
|
+
else
|
|
48
|
+
tt_fail "validate-tombstones.sh did not report 'no tombstones' with an empty ledger"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# --- Scenario: tombstone-skill.sh generates a stub ---
|
|
52
|
+
mkdir -p "$FIXTURE_DIR"
|
|
53
|
+
cat > "${FIXTURE_DIR}/SKILL.md" <<'FIXTUREEOF'
|
|
54
|
+
# story: e00s00
|
|
55
|
+
---
|
|
56
|
+
name: test-tombstone-fixture
|
|
57
|
+
description: throwaway fixture for test-tombstone-mechanism.sh
|
|
58
|
+
---
|
|
59
|
+
Fixture body.
|
|
60
|
+
FIXTUREEOF
|
|
61
|
+
|
|
62
|
+
bash scripts/tombstone-skill.sh "$FIXTURE_OLD" "$FIXTURE_NEW"
|
|
63
|
+
|
|
64
|
+
if grep -q "TOMBSTONE" "${FIXTURE_DIR}/SKILL.md"; then
|
|
65
|
+
tt_pass "tombstone-skill.sh replaced SKILL.md with a stub"
|
|
66
|
+
else
|
|
67
|
+
tt_fail "SKILL.md was not replaced with a tombstone stub"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if grep -q "story: e00s00" "${FIXTURE_DIR}/SKILL.md"; then
|
|
71
|
+
tt_pass "tombstone-skill.sh preserved the old story tag"
|
|
72
|
+
else
|
|
73
|
+
tt_fail "story tag was not preserved in the stub"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if [[ -f "$TOMBSTONES_FILE" ]] && grep -q "$FIXTURE_OLD" "$TOMBSTONES_FILE"; then
|
|
77
|
+
tt_pass "tombstone-skill.sh registered the mapping in ${TOMBSTONES_FILE}"
|
|
78
|
+
else
|
|
79
|
+
tt_fail "mapping not registered in ${TOMBSTONES_FILE}"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# --- Scenario: fresh tombstone resolves cleanly ---
|
|
83
|
+
if bash scripts/validate-tombstones.sh 2>&1 | strip_ansi | grep -q "OK: ${FIXTURE_OLD}"; then
|
|
84
|
+
tt_pass "validate-tombstones.sh confirms the fresh stub resolves"
|
|
85
|
+
else
|
|
86
|
+
tt_fail "validate-tombstones.sh did not confirm the fresh stub"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# --- Scenario: expired tombstone flagged (6b) ---
|
|
90
|
+
python3 -c "
|
|
91
|
+
import yaml
|
|
92
|
+
d = yaml.safe_load(open('$TOMBSTONES_FILE')) or {'tombstones': []}
|
|
93
|
+
for t in d['tombstones']:
|
|
94
|
+
if t['old_name'] == '$FIXTURE_OLD':
|
|
95
|
+
t['created_at_version'] = '0.0.0'
|
|
96
|
+
yaml.dump(d, open('$TOMBSTONES_FILE', 'w'), default_flow_style=False)
|
|
97
|
+
"
|
|
98
|
+
|
|
99
|
+
if bash scripts/validate-tombstones.sh 2>&1 | strip_ansi | grep -qi "EXPIRED: ${FIXTURE_OLD}"; then
|
|
100
|
+
tt_pass "validate-tombstones.sh flags a tombstone whose expiry window has passed"
|
|
101
|
+
else
|
|
102
|
+
tt_fail "expired tombstone was not flagged"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo "---"
|
|
106
|
+
if [[ "$FAILURES" -eq 0 ]]; then
|
|
107
|
+
echo -e "${GREEN}test-tombstone-mechanism: ALL PASS${NC}"
|
|
108
|
+
else
|
|
109
|
+
echo -e "${RED}test-tombstone-mechanism: $FAILURES failure(s)${NC}"
|
|
110
|
+
fi
|
|
111
|
+
exit "$FAILURES"
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s02
|
|
3
|
+
# tombstone-skill.sh — replace a renamed/merged skill's SKILL.md with a stub,
|
|
4
|
+
# preserving its story tags and registering the mapping in specs/tombstones.yaml
|
|
5
|
+
# for a one-release expiry window.
|
|
6
|
+
#
|
|
7
|
+
# Usage: bash scripts/tombstone-skill.sh <old-name> <new-name-or-merge-target>
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
11
|
+
resolve_repo_root
|
|
12
|
+
cd "$REPO_ROOT"
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'EOF'
|
|
16
|
+
Usage: tombstone-skill.sh <old-name> <new-name-or-merge-target>
|
|
17
|
+
|
|
18
|
+
Replaces skills/<old-name>/SKILL.md with a stub pointing at <new-name>,
|
|
19
|
+
preserves the old name's story tags, and registers the mapping in
|
|
20
|
+
specs/tombstones.yaml with a one-release expiry window.
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
|
25
|
+
usage
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
OLD_NAME="${1:-}"
|
|
30
|
+
NEW_NAME="${2:-}"
|
|
31
|
+
NAME_PATTERN='^[a-z][a-z0-9-]*$'
|
|
32
|
+
|
|
33
|
+
if [[ -z "$OLD_NAME" || -z "$NEW_NAME" ]]; then
|
|
34
|
+
usage >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Threat-model mitigation (specs/security/epics/e53/THREAT_MODEL.md): reject
|
|
39
|
+
# anything that isn't a bare kebab-case skill name before any path use.
|
|
40
|
+
for name in "$OLD_NAME" "$NEW_NAME"; do
|
|
41
|
+
if [[ ! "$name" =~ $NAME_PATTERN ]]; then
|
|
42
|
+
echo "ERROR: '$name' is not a valid skill name (must match $NAME_PATTERN)" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
OLD_SKILL_MD="skills/${OLD_NAME}/SKILL.md"
|
|
48
|
+
if [[ ! -f "$OLD_SKILL_MD" ]]; then
|
|
49
|
+
echo "ERROR: $OLD_SKILL_MD does not exist" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
STORY_TAGS=$(grep -E '^#\s*story:' "$OLD_SKILL_MD" || true)
|
|
54
|
+
CURRENT_VERSION=$(grep '"version"' package.json 2>/dev/null | head -1 | sed 's/.*: *"\(.*\)".*/\1/')
|
|
55
|
+
CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
56
|
+
|
|
57
|
+
cat > "$OLD_SKILL_MD" <<STUBEOF
|
|
58
|
+
---
|
|
59
|
+
name: ${OLD_NAME}
|
|
60
|
+
description: "TOMBSTONE — renamed/merged to ${NEW_NAME}. This stub resolves for one release then is removed."
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
# ${OLD_NAME} (tombstoned)
|
|
64
|
+
|
|
65
|
+
This skill has been renamed or merged into \`${NEW_NAME}\`. Use \`${NEW_NAME}\` instead.
|
|
66
|
+
|
|
67
|
+
This stub exists for one release as a transition aid, then is removed once
|
|
68
|
+
\`scripts/validate-tombstones.sh\` flags it as expired.
|
|
69
|
+
|
|
70
|
+
${STORY_TAGS}
|
|
71
|
+
STUBEOF
|
|
72
|
+
|
|
73
|
+
TOMBSTONES_FILE="specs/tombstones.yaml"
|
|
74
|
+
[[ -f "$TOMBSTONES_FILE" ]] || echo "tombstones: []" > "$TOMBSTONES_FILE"
|
|
75
|
+
|
|
76
|
+
python3 -c "
|
|
77
|
+
import yaml
|
|
78
|
+
d = yaml.safe_load(open('$TOMBSTONES_FILE')) or {'tombstones': []}
|
|
79
|
+
d.setdefault('tombstones', []).append({
|
|
80
|
+
'old_name': '$OLD_NAME',
|
|
81
|
+
'new_name_or_merge_target': '$NEW_NAME',
|
|
82
|
+
'created_at': '$CREATED_AT',
|
|
83
|
+
'created_at_version': '$CURRENT_VERSION',
|
|
84
|
+
})
|
|
85
|
+
yaml.dump(d, open('$TOMBSTONES_FILE', 'w'), default_flow_style=False)
|
|
86
|
+
"
|
|
87
|
+
|
|
88
|
+
echo "Tombstoned: ${OLD_NAME} -> ${NEW_NAME}"
|
|
89
|
+
echo "Registered in ${TOMBSTONES_FILE}"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s02
|
|
3
|
+
# validate-tombstones.sh — confirm each registered tombstone's stub still
|
|
4
|
+
# resolves, and flag any whose one-release expiry window has passed.
|
|
5
|
+
#
|
|
6
|
+
# Usage: bash scripts/validate-tombstones.sh
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
10
|
+
resolve_repo_root
|
|
11
|
+
cd "$REPO_ROOT"
|
|
12
|
+
|
|
13
|
+
RED='\033[0;31m'
|
|
14
|
+
GREEN='\033[0;32m'
|
|
15
|
+
YELLOW='\033[1;33m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
TOMBSTONES_FILE="specs/tombstones.yaml"
|
|
19
|
+
FAIL=0
|
|
20
|
+
|
|
21
|
+
if [[ ! -f "$TOMBSTONES_FILE" ]]; then
|
|
22
|
+
echo -e "${GREEN}OK${NC}: no tombstones registered"
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
CURRENT_VERSION=$(grep '"version"' package.json 2>/dev/null | head -1 | sed 's/.*: *"\(.*\)".*/\1/')
|
|
27
|
+
|
|
28
|
+
ROWS=$(python3 -c "
|
|
29
|
+
import yaml
|
|
30
|
+
d = yaml.safe_load(open('$TOMBSTONES_FILE')) or {'tombstones': []}
|
|
31
|
+
for t in d.get('tombstones', []) or []:
|
|
32
|
+
print(f\"{t['old_name']}\t{t['new_name_or_merge_target']}\t{t.get('created_at_version', '')}\")
|
|
33
|
+
")
|
|
34
|
+
|
|
35
|
+
if [[ -z "$ROWS" ]]; then
|
|
36
|
+
echo -e "${GREEN}OK${NC}: no tombstones registered"
|
|
37
|
+
exit 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
while IFS=$'\t' read -r old_name new_name created_version; do
|
|
41
|
+
[[ -z "$old_name" ]] && continue
|
|
42
|
+
stub="skills/${old_name}/SKILL.md"
|
|
43
|
+
|
|
44
|
+
if [[ ! -f "$stub" ]]; then
|
|
45
|
+
echo -e "${RED}FAIL${NC}: ${old_name} -> ${new_name} — stub missing at ${stub}"
|
|
46
|
+
FAIL=1
|
|
47
|
+
continue
|
|
48
|
+
fi
|
|
49
|
+
if ! grep -q "TOMBSTONE" "$stub"; then
|
|
50
|
+
echo -e "${RED}FAIL${NC}: ${old_name} — stub exists but is not a tombstone"
|
|
51
|
+
FAIL=1
|
|
52
|
+
continue
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [[ -n "$created_version" && "$created_version" != "$CURRENT_VERSION" ]]; then
|
|
56
|
+
echo -e "${YELLOW}EXPIRED${NC}: ${old_name} -> ${new_name} — created at ${created_version}, now ${CURRENT_VERSION}; remove the stub"
|
|
57
|
+
continue
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo -e "${GREEN}OK${NC}: ${old_name} -> ${new_name} resolves"
|
|
61
|
+
done <<< "$ROWS"
|
|
62
|
+
|
|
63
|
+
exit "$FAIL"
|