bigpowers 2.79.0 → 2.79.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/CHANGELOG.md +7 -0
- package/CONVENTIONS.md +8 -4
- package/package.json +1 -1
- package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
- package/scripts/lib/completeness-critic.sh +18 -5
- package/scripts/test-completeness-critic-scope.sh +99 -0
- package/scripts/test-generate-epics-wiki-zero-stories.sh +64 -0
- package/scripts/test-golden-g11-worktree-venv.sh +63 -0
- package/scripts/tombstone-skill.sh +18 -7
- package/scripts/validate-tombstones.sh +21 -2
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.79.1](https://github.com/danielvm-git/bigpowers/compare/v2.79.0...v2.79.1) (2026-07-23)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **e53:** close migration-readiness gaps — code-injection, expiry semantics, fail-open gate ([4042c62](https://github.com/danielvm-git/bigpowers/commit/4042c62f5ccac9790596f81aac5aaebd1235c763))
|
|
7
|
+
|
|
1
8
|
# [2.79.0](https://github.com/danielvm-git/bigpowers/compare/v2.78.1...v2.79.0) (2026-07-23)
|
|
2
9
|
|
|
3
10
|
|
package/CONVENTIONS.md
CHANGED
|
@@ -364,11 +364,15 @@ referencing the old name don't break immediately.
|
|
|
364
364
|
renaming or merging a skill. It replaces `skills/<old-name>/SKILL.md` with a stub
|
|
365
365
|
pointing at the new name/location, preserves the old skill's `# story: eNNsNN` tags
|
|
366
366
|
for traceability, and registers the mapping in `specs/tombstones.yaml` (old name,
|
|
367
|
-
new name/target, creation timestamp, and the `
|
|
367
|
+
new name/target, creation timestamp, and the `created_at_version` at creation time).
|
|
368
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`
|
|
370
|
-
|
|
371
|
-
and should be removed.
|
|
369
|
+
stub still resolves, and to flag any tombstone whose `created_at_version` is in a
|
|
370
|
+
different minor/major release train (major.minor) than the current version — that
|
|
371
|
+
stub has served its one-release transition window and should be removed. Patch-only
|
|
372
|
+
bumps within the same minor version don't count as a new release: this repo ships
|
|
373
|
+
via semantic-release on every merge to main, so comparing full semver strings would
|
|
374
|
+
expire a tombstone within minutes of creation instead of giving consumers a real
|
|
375
|
+
migration window.
|
|
372
376
|
3. **Required alongside the first real use of `tombstone-skill.sh`:** update
|
|
373
377
|
`docs/references/model-profiles.md`'s skill-count annotations. A tombstone stub is
|
|
374
378
|
still a `skills/*/SKILL.md` file — it increments the live skill count the same as
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -33,12 +33,25 @@ if [[ -f specs/traceability-matrix.json ]]; then
|
|
|
33
33
|
# not-yet-built siblings (which have no code tags by definition until their
|
|
34
34
|
# own turn) and not unrelated epics (which may be plan-worked-to-build-ready
|
|
35
35
|
# but not yet kicked off).
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
# `|| true` guards the whole pipe under `set -o pipefail`: a state.yaml
|
|
37
|
+
# with no active_story line makes grep exit 1, which would otherwise kill
|
|
38
|
+
# this script outright (no WARNING, no summary line) instead of falling
|
|
39
|
+
# through to the empty-ACTIVE_STORY branch below.
|
|
40
|
+
ACTIVE_STORY="$(grep '^active_story:' specs/state.yaml 2>/dev/null | awk '{print $2}' || true)"
|
|
41
|
+
if [[ -z "$ACTIVE_STORY" ]]; then
|
|
42
|
+
classify WARNING "specs/state.yaml has no active_story set — zero-tag check skipped, not a pass"
|
|
43
|
+
else
|
|
44
|
+
STORY_EXISTS="$(jq --arg s "$ACTIVE_STORY" \
|
|
45
|
+
'[.stories[]? | select(.id == $s)] | length' \
|
|
40
46
|
specs/traceability-matrix.json 2>/dev/null || echo 0)"
|
|
41
|
-
|
|
47
|
+
if [[ "$STORY_EXISTS" -eq 0 ]]; then
|
|
48
|
+
classify WARNING "active_story $ACTIVE_STORY not found in specs/traceability-matrix.json — zero-tag check skipped, not a pass"
|
|
49
|
+
else
|
|
50
|
+
UNDONE="$(jq --arg s "$ACTIVE_STORY" \
|
|
51
|
+
'[.stories[]? | select(.id == $s and .status != "done" and ((.links // []) | map(select(.method == "explicit_tag")) | length) == 0)] | length' \
|
|
52
|
+
specs/traceability-matrix.json 2>/dev/null || echo 0)"
|
|
53
|
+
(( UNDONE > 0 )) && classify BLOCKER "active story $ACTIVE_STORY has zero explicit story-tag links"
|
|
54
|
+
fi
|
|
42
55
|
fi
|
|
43
56
|
COVERAGE="$(jq -r '.summary.coverage_percent // .coverage_percent // empty' specs/traceability-matrix.json 2>/dev/null)"
|
|
44
57
|
if [[ -n "$COVERAGE" && "$COVERAGE" != "null" ]]; then
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s01
|
|
3
|
+
# test-completeness-critic-scope.sh — regression test for
|
|
4
|
+
# BUG-2026-07-23-completeness-critic-code-tags-schema-drift: the zero-tag
|
|
5
|
+
# check must scope to the active story only (not siblings/other epics), and
|
|
6
|
+
# must WARN — not silently no-op — when active_story is unset or unresolved.
|
|
7
|
+
#
|
|
8
|
+
# Usage: bash scripts/test-completeness-critic-scope.sh
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12
|
+
cd "$REPO_ROOT"
|
|
13
|
+
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
FAILURES=0
|
|
19
|
+
tt_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
20
|
+
tt_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
21
|
+
|
|
22
|
+
STATE_FILE="specs/state.yaml"
|
|
23
|
+
MATRIX_FILE="specs/traceability-matrix.json"
|
|
24
|
+
STATE_BACKUP=$(mktemp)
|
|
25
|
+
MATRIX_BACKUP=$(mktemp)
|
|
26
|
+
cp "$STATE_FILE" "$STATE_BACKUP"
|
|
27
|
+
cp "$MATRIX_FILE" "$MATRIX_BACKUP"
|
|
28
|
+
|
|
29
|
+
cleanup() {
|
|
30
|
+
cp "$STATE_BACKUP" "$STATE_FILE"
|
|
31
|
+
cp "$MATRIX_BACKUP" "$MATRIX_FILE"
|
|
32
|
+
rm -f "$STATE_BACKUP" "$MATRIX_BACKUP"
|
|
33
|
+
}
|
|
34
|
+
trap cleanup EXIT
|
|
35
|
+
|
|
36
|
+
run_critic() {
|
|
37
|
+
bash scripts/lib/completeness-critic.sh 2>&1 || true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Fixture matrix: one untagged sibling story (e00s02) that must NOT block,
|
|
41
|
+
# and the active story (e00s01) which is under test.
|
|
42
|
+
write_matrix() {
|
|
43
|
+
local active_links="$1"
|
|
44
|
+
cat > "$MATRIX_FILE" <<FIXTUREEOF
|
|
45
|
+
{
|
|
46
|
+
"stories": [
|
|
47
|
+
{"id": "e00s01", "status": "in_progress", "links": ${active_links}},
|
|
48
|
+
{"id": "e00s02", "status": "todo", "links": []}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
FIXTUREEOF
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# --- Scenario: active_story unset → WARNING, not silent pass ---
|
|
55
|
+
sed -i.bak '/^active_story:/d' "$STATE_FILE" && rm -f "${STATE_FILE}.bak"
|
|
56
|
+
write_matrix '[]'
|
|
57
|
+
output=$(run_critic)
|
|
58
|
+
if echo "$output" | grep -q "\[WARNING\].*no active_story set"; then
|
|
59
|
+
tt_pass "unset active_story produces an explicit WARNING"
|
|
60
|
+
else
|
|
61
|
+
tt_fail "unset active_story did not produce the expected WARNING"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# --- Scenario: active_story set but absent from the matrix → WARNING ---
|
|
65
|
+
printf 'active_story: e99s99\n' >> "$STATE_FILE"
|
|
66
|
+
output=$(run_critic)
|
|
67
|
+
if echo "$output" | grep -q "\[WARNING\].*e99s99 not found"; then
|
|
68
|
+
tt_pass "unresolved active_story produces an explicit WARNING"
|
|
69
|
+
else
|
|
70
|
+
tt_fail "unresolved active_story did not produce the expected WARNING"
|
|
71
|
+
fi
|
|
72
|
+
sed -i.bak '/^active_story:/d' "$STATE_FILE" && rm -f "${STATE_FILE}.bak"
|
|
73
|
+
|
|
74
|
+
# --- Scenario: active story has zero explicit_tag links → BLOCKER, scoped to it ---
|
|
75
|
+
printf 'active_story: e00s01\n' >> "$STATE_FILE"
|
|
76
|
+
write_matrix '[]'
|
|
77
|
+
output=$(run_critic)
|
|
78
|
+
if echo "$output" | grep -q "\[BLOCKER\] active story e00s01 has zero explicit story-tag links"; then
|
|
79
|
+
tt_pass "active story with zero tags is correctly flagged as BLOCKER"
|
|
80
|
+
else
|
|
81
|
+
tt_fail "active story with zero tags was not flagged as BLOCKER"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# --- Scenario: active story IS tagged → no BLOCKER, even though sibling e00s02 is untagged ---
|
|
85
|
+
write_matrix '[{"method": "explicit_tag", "file": "scripts/fixture.sh", "line": 1, "confidence": 1.0}]'
|
|
86
|
+
output=$(run_critic)
|
|
87
|
+
if echo "$output" | grep -q "\[BLOCKER\] active story e00s01"; then
|
|
88
|
+
tt_fail "tagged active story incorrectly flagged as BLOCKER"
|
|
89
|
+
else
|
|
90
|
+
tt_pass "tagged active story is not flagged, and untagged sibling e00s02 does not leak into the check"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
echo "---"
|
|
94
|
+
if [[ "$FAILURES" -eq 0 ]]; then
|
|
95
|
+
echo -e "${GREEN}test-completeness-critic-scope: ALL PASS${NC}"
|
|
96
|
+
else
|
|
97
|
+
echo -e "${RED}test-completeness-critic-scope: $FAILURES failure(s)${NC}"
|
|
98
|
+
fi
|
|
99
|
+
exit "$FAILURES"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s01
|
|
3
|
+
# test-generate-epics-wiki-zero-stories.sh — regression test for
|
|
4
|
+
# BUG-2026-07-23-epics-wiki-empty-references: generate-epics-wiki.sh must
|
|
5
|
+
# skip epics with zero sliced stories rather than emit an invalid OKF bundle
|
|
6
|
+
# with an empty references[].
|
|
7
|
+
#
|
|
8
|
+
# Usage: bash scripts/test-generate-epics-wiki-zero-stories.sh
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
12
|
+
resolve_repo_root
|
|
13
|
+
cd "$REPO_ROOT"
|
|
14
|
+
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
NC='\033[0m'
|
|
18
|
+
|
|
19
|
+
FAILURES=0
|
|
20
|
+
tt_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
21
|
+
tt_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
22
|
+
|
|
23
|
+
FIXTURE_ID="e00"
|
|
24
|
+
FIXTURE_DIR="specs/epics/e00-zero-story-fixture"
|
|
25
|
+
FIXTURE_BUNDLE="specs/epics-wiki/${FIXTURE_ID}.okf.md"
|
|
26
|
+
|
|
27
|
+
cleanup() {
|
|
28
|
+
rm -rf "$FIXTURE_DIR"
|
|
29
|
+
rm -f "$FIXTURE_BUNDLE"
|
|
30
|
+
}
|
|
31
|
+
trap cleanup EXIT
|
|
32
|
+
|
|
33
|
+
mkdir -p "$FIXTURE_DIR"
|
|
34
|
+
cat > "${FIXTURE_DIR}/epic.yaml" <<'FIXTUREEOF'
|
|
35
|
+
id: e00
|
|
36
|
+
title: "Zero-story fixture epic"
|
|
37
|
+
wsjf: 0
|
|
38
|
+
bcps: 0
|
|
39
|
+
capsule_dir: epics/e00-zero-story-fixture
|
|
40
|
+
mode: capsule
|
|
41
|
+
status: todo
|
|
42
|
+
depends_on: []
|
|
43
|
+
stories: []
|
|
44
|
+
FIXTUREEOF
|
|
45
|
+
|
|
46
|
+
# A scoped-but-unsliced epic has a `stories:` key with nothing under it —
|
|
47
|
+
# grep -c '^\s*- id:' correctly returns 0 for this shape.
|
|
48
|
+
[[ -f "$FIXTURE_BUNDLE" ]] && rm -f "$FIXTURE_BUNDLE"
|
|
49
|
+
|
|
50
|
+
bash scripts/generate-epics-wiki.sh > /dev/null
|
|
51
|
+
|
|
52
|
+
if [[ ! -f "$FIXTURE_BUNDLE" ]]; then
|
|
53
|
+
tt_pass "generate-epics-wiki.sh skips a zero-story epic (no bundle emitted)"
|
|
54
|
+
else
|
|
55
|
+
tt_fail "generate-epics-wiki.sh emitted a bundle for a zero-story epic: $FIXTURE_BUNDLE"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "---"
|
|
59
|
+
if [[ "$FAILURES" -eq 0 ]]; then
|
|
60
|
+
echo -e "${GREEN}test-generate-epics-wiki-zero-stories: ALL PASS${NC}"
|
|
61
|
+
else
|
|
62
|
+
echo -e "${RED}test-generate-epics-wiki-zero-stories: $FAILURES failure(s)${NC}"
|
|
63
|
+
fi
|
|
64
|
+
exit "$FAILURES"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e53s01
|
|
3
|
+
# test-golden-g11-worktree-venv.sh — regression test for
|
|
4
|
+
# BUG-2026-07-23-golden-g11-worktree-venv: golden-g11-gitignore-venv.sh must
|
|
5
|
+
# PASS in a fresh worktree where .venv does not exist yet on disk (before
|
|
6
|
+
# setup-environment has run), not just when a real .venv is already present.
|
|
7
|
+
#
|
|
8
|
+
# Usage: bash scripts/test-golden-g11-worktree-venv.sh
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
12
|
+
cd "$REPO_ROOT"
|
|
13
|
+
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
FAILURES=0
|
|
19
|
+
tt_pass() { echo -e "${GREEN}PASS${NC} $1"; }
|
|
20
|
+
tt_fail() { echo -e "${RED}FAIL${NC} $1"; FAILURES=$((FAILURES + 1)); }
|
|
21
|
+
strip_ansi() { sed -E 's/\x1b\[[0-9;]*m//g'; }
|
|
22
|
+
|
|
23
|
+
VENV_MOVED=0
|
|
24
|
+
if [[ -e .venv ]]; then
|
|
25
|
+
mv .venv .venv.test-golden-g11-backup
|
|
26
|
+
VENV_MOVED=1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
cleanup() {
|
|
30
|
+
rm -rf .venv
|
|
31
|
+
if [[ "$VENV_MOVED" -eq 1 ]]; then
|
|
32
|
+
mv .venv.test-golden-g11-backup .venv
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
trap cleanup EXIT
|
|
36
|
+
|
|
37
|
+
# --- Scenario: .venv absent (fresh worktree) → gate still PASSes ---
|
|
38
|
+
if [[ -e .venv ]]; then
|
|
39
|
+
tt_fail "test setup failed to remove .venv before running the gate"
|
|
40
|
+
else
|
|
41
|
+
output=$(bash scripts/golden-g11-gitignore-venv.sh 2>&1 | strip_ansi) && exit_code=0 || exit_code=$?
|
|
42
|
+
if [[ "$exit_code" -eq 0 ]] && echo "$output" | grep -q "PASS git check-ignore .venv"; then
|
|
43
|
+
tt_pass "gate PASSes when .venv does not exist on disk"
|
|
44
|
+
else
|
|
45
|
+
tt_fail "gate did not PASS with .venv absent (exit=$exit_code)"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# The gate must clean up the .venv it created for the check — a fresh
|
|
49
|
+
# worktree shouldn't be left with a stray directory after a gate run.
|
|
50
|
+
if [[ ! -e .venv ]]; then
|
|
51
|
+
tt_pass "gate removes the .venv it created for the check"
|
|
52
|
+
else
|
|
53
|
+
tt_fail "gate left a stray .venv behind after the check"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "---"
|
|
58
|
+
if [[ "$FAILURES" -eq 0 ]]; then
|
|
59
|
+
echo -e "${GREEN}test-golden-g11-worktree-venv: ALL PASS${NC}"
|
|
60
|
+
else
|
|
61
|
+
echo -e "${RED}test-golden-g11-worktree-venv: $FAILURES failure(s)${NC}"
|
|
62
|
+
fi
|
|
63
|
+
exit "$FAILURES"
|
|
@@ -51,7 +51,7 @@ if [[ ! -f "$OLD_SKILL_MD" ]]; then
|
|
|
51
51
|
fi
|
|
52
52
|
|
|
53
53
|
STORY_TAGS=$(grep -E '^#\s*story:' "$OLD_SKILL_MD" || true)
|
|
54
|
-
CURRENT_VERSION=$(
|
|
54
|
+
CURRENT_VERSION=$(jq -r '.version' package.json 2>/dev/null)
|
|
55
55
|
CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
56
56
|
|
|
57
57
|
cat > "$OLD_SKILL_MD" <<STUBEOF
|
|
@@ -73,16 +73,27 @@ STUBEOF
|
|
|
73
73
|
TOMBSTONES_FILE="specs/tombstones.yaml"
|
|
74
74
|
[[ -f "$TOMBSTONES_FILE" ]] || echo "tombstones: []" > "$TOMBSTONES_FILE"
|
|
75
75
|
|
|
76
|
+
# Values flow through the environment, never interpolated into the python
|
|
77
|
+
# source string — string-interpolating shell variables into `python3 -c`
|
|
78
|
+
# turns any unvalidated field (e.g. CURRENT_VERSION, sourced from
|
|
79
|
+
# package.json) into a code-injection vector.
|
|
80
|
+
TOMBSTONE_FILE="$TOMBSTONES_FILE" \
|
|
81
|
+
TOMBSTONE_OLD_NAME="$OLD_NAME" \
|
|
82
|
+
TOMBSTONE_NEW_NAME="$NEW_NAME" \
|
|
83
|
+
TOMBSTONE_CREATED_AT="$CREATED_AT" \
|
|
84
|
+
TOMBSTONE_VERSION="$CURRENT_VERSION" \
|
|
76
85
|
python3 -c "
|
|
86
|
+
import os
|
|
77
87
|
import yaml
|
|
78
|
-
|
|
88
|
+
path = os.environ['TOMBSTONE_FILE']
|
|
89
|
+
d = yaml.safe_load(open(path)) or {'tombstones': []}
|
|
79
90
|
d.setdefault('tombstones', []).append({
|
|
80
|
-
'old_name': '
|
|
81
|
-
'new_name_or_merge_target': '
|
|
82
|
-
'created_at': '
|
|
83
|
-
'created_at_version': '
|
|
91
|
+
'old_name': os.environ['TOMBSTONE_OLD_NAME'],
|
|
92
|
+
'new_name_or_merge_target': os.environ['TOMBSTONE_NEW_NAME'],
|
|
93
|
+
'created_at': os.environ['TOMBSTONE_CREATED_AT'],
|
|
94
|
+
'created_at_version': os.environ['TOMBSTONE_VERSION'],
|
|
84
95
|
})
|
|
85
|
-
yaml.dump(d, open(
|
|
96
|
+
yaml.dump(d, open(path, 'w'), default_flow_style=False)
|
|
86
97
|
"
|
|
87
98
|
|
|
88
99
|
echo "Tombstoned: ${OLD_NAME} -> ${NEW_NAME}"
|
|
@@ -16,6 +16,7 @@ YELLOW='\033[1;33m'
|
|
|
16
16
|
NC='\033[0m'
|
|
17
17
|
|
|
18
18
|
TOMBSTONES_FILE="specs/tombstones.yaml"
|
|
19
|
+
NAME_PATTERN='^[a-z][a-z0-9-]*$'
|
|
19
20
|
FAIL=0
|
|
20
21
|
|
|
21
22
|
if [[ ! -f "$TOMBSTONES_FILE" ]]; then
|
|
@@ -23,7 +24,14 @@ if [[ ! -f "$TOMBSTONES_FILE" ]]; then
|
|
|
23
24
|
exit 0
|
|
24
25
|
fi
|
|
25
26
|
|
|
26
|
-
CURRENT_VERSION=$(
|
|
27
|
+
CURRENT_VERSION=$(jq -r '.version' package.json 2>/dev/null)
|
|
28
|
+
|
|
29
|
+
# A "release" for tombstone-expiry purposes is a minor/major version bump.
|
|
30
|
+
# This repo ships via semantic-release on every merge to main (multiple
|
|
31
|
+
# patch releases per day) — comparing full semver strings would expire
|
|
32
|
+
# every tombstone within minutes instead of giving consumers the one-release
|
|
33
|
+
# migration window CONVENTIONS.md promises.
|
|
34
|
+
release_train() { echo "$1" | cut -d. -f1-2; }
|
|
27
35
|
|
|
28
36
|
ROWS=$(python3 -c "
|
|
29
37
|
import yaml
|
|
@@ -39,6 +47,17 @@ fi
|
|
|
39
47
|
|
|
40
48
|
while IFS=$'\t' read -r old_name new_name created_version; do
|
|
41
49
|
[[ -z "$old_name" ]] && continue
|
|
50
|
+
|
|
51
|
+
# Threat-model mitigation (specs/security/epics/e53/THREAT_MODEL.md): apply
|
|
52
|
+
# the same kebab-case validation tombstone-skill.sh applies before writing
|
|
53
|
+
# entries, since these names come back out of specs/tombstones.yaml here
|
|
54
|
+
# and are used to construct a filesystem path.
|
|
55
|
+
if [[ ! "$old_name" =~ $NAME_PATTERN || ! "$new_name" =~ $NAME_PATTERN ]]; then
|
|
56
|
+
echo -e "${RED}FAIL${NC}: '${old_name}' -> '${new_name}' — not a valid skill name (must match ${NAME_PATTERN})"
|
|
57
|
+
FAIL=1
|
|
58
|
+
continue
|
|
59
|
+
fi
|
|
60
|
+
|
|
42
61
|
stub="skills/${old_name}/SKILL.md"
|
|
43
62
|
|
|
44
63
|
if [[ ! -f "$stub" ]]; then
|
|
@@ -52,7 +71,7 @@ while IFS=$'\t' read -r old_name new_name created_version; do
|
|
|
52
71
|
continue
|
|
53
72
|
fi
|
|
54
73
|
|
|
55
|
-
if [[ -n "$created_version" && "$created_version" != "$CURRENT_VERSION" ]]; then
|
|
74
|
+
if [[ -n "$created_version" && "$(release_train "$created_version")" != "$(release_train "$CURRENT_VERSION")" ]]; then
|
|
56
75
|
echo -e "${YELLOW}EXPIRED${NC}: ${old_name} -> ${new_name} — created at ${created_version}, now ${CURRENT_VERSION}; remove the stub"
|
|
57
76
|
continue
|
|
58
77
|
fi
|