bigpowers 2.68.4 → 2.68.6
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 +14 -0
- package/package.json +1 -1
- package/scripts/bp-opensrc-check.sh +2 -1
- package/scripts/bp-timing.sh +4 -3
- package/scripts/bp-yaml-set.sh +2 -1
- package/scripts/build-receipts.sh +2 -1
- package/scripts/build-skill-graph.sh +5 -4
- package/scripts/check-blind-spots.sh +2 -1
- package/scripts/check-spec-drift.sh +2 -1
- package/scripts/check-spec-version-gap.sh +9 -8
- package/scripts/check-stale-locks.sh +2 -1
- package/scripts/convert-legado.sh +3 -2
- package/scripts/decompose-conventions.sh +2 -1
- package/scripts/enrich-epics-from-archive.sh +2 -1
- package/scripts/generate-adr-wiki.sh +5 -4
- package/scripts/generate-agent-guide.sh +2 -1
- package/scripts/generate-reference-tables.sh +2 -1
- package/scripts/generate-viz-graph.sh +4 -3
- package/scripts/golden-g09-yaml-roundtrip.sh +3 -2
- package/scripts/golden-g10-trace-anti-vacuity.sh +2 -1
- package/scripts/migrate-version.sh +46 -45
- package/scripts/okf-post-sync.sh +5 -4
- package/scripts/run-golden-suite.sh +7 -6
- package/scripts/run-smoke.sh +5 -4
- package/scripts/sync-bugs-registry.sh +2 -1
- package/scripts/sync-skills.sh +8 -7
- package/scripts/sync-status-from-epics.sh +2 -1
- package/scripts/sync-version-mirrors.sh +3 -2
- package/scripts/trace-stories.sh +2 -1
- package/scripts/validate-okf.sh +22 -21
- package/scripts/validate-specs-yaml.sh +4 -3
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.68.6](https://github.com/danielvm-git/bigpowers/compare/v2.68.5...v2.68.6) (2026-07-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ci:** retry GitHub Pages deploy on transient API failure ([fe15c20](https://github.com/danielvm-git/bigpowers/commit/fe15c209f4184ea3473e7f2504ec3466321c4245))
|
|
7
|
+
|
|
8
|
+
## [2.68.5](https://github.com/danielvm-git/bigpowers/compare/v2.68.4...v2.68.5) (2026-07-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **bug:** python-interpreter-fragility — wire python-env.sh into 29 scripts ([1088995](https://github.com/danielvm-git/bigpowers/commit/10889957c5b4fb07fe2be5f4624a3608461802c7))
|
|
14
|
+
|
|
1
15
|
## [2.68.4](https://github.com/danielvm-git/bigpowers/compare/v2.68.3...v2.68.4) (2026-07-06)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Usage: bash scripts/bp-opensrc-check.sh [package.json|requirements.txt]
|
|
4
4
|
# Output: one line per dependency — FOUND or NOT CACHED, with local path when found.
|
|
5
5
|
set -euo pipefail
|
|
6
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
7
|
|
|
7
8
|
INPUT="${1:-package.json}"
|
|
8
9
|
|
|
@@ -20,7 +21,7 @@ extract_deps() {
|
|
|
20
21
|
local file="$1"
|
|
21
22
|
case "$file" in
|
|
22
23
|
*.json)
|
|
23
|
-
|
|
24
|
+
$PYTHON -c "
|
|
24
25
|
import json, sys
|
|
25
26
|
data = json.load(open('$file'))
|
|
26
27
|
deps = list(data.get('dependencies', {}).keys()) + list(data.get('devDependencies', {}).keys())
|
package/scripts/bp-timing.sh
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# bp-timing.sh — Record skill invocation timings for stocktake effectiveness analysis.
|
|
3
3
|
# Usage: bash scripts/bp-timing.sh start <skill-name>
|
|
4
4
|
# bash scripts/bp-timing.sh end <skill-name>
|
|
5
|
-
# Requires:
|
|
5
|
+
# Requires: $PYTHON (for YAML-safe state updates)
|
|
6
6
|
set -euo pipefail
|
|
7
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
7
8
|
|
|
8
9
|
STATE_YAML="specs/state.yaml"
|
|
9
10
|
TIMINGS_KEY="metrics.skill_timings"
|
|
@@ -21,7 +22,7 @@ SKILL="$2"
|
|
|
21
22
|
case "$ACTION" in
|
|
22
23
|
start)
|
|
23
24
|
STAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
24
|
-
|
|
25
|
+
$PYTHON -c "
|
|
25
26
|
import yaml, sys
|
|
26
27
|
try:
|
|
27
28
|
with open('$STATE_YAML') as f:
|
|
@@ -42,7 +43,7 @@ with open('$STATE_YAML', 'w') as f:
|
|
|
42
43
|
|
|
43
44
|
end)
|
|
44
45
|
STAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
45
|
-
|
|
46
|
+
$PYTHON -c "
|
|
46
47
|
import yaml
|
|
47
48
|
from datetime import datetime
|
|
48
49
|
|
package/scripts/bp-yaml-set.sh
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
# bp-yaml-set.sh — patch a dotted key in a specs YAML file
|
|
3
3
|
# Usage: bp-yaml-set.sh <file> <dotted.key> <value>
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
FILE="${1:?file}"
|
|
6
7
|
KEY="${2:?dotted.key}"
|
|
7
8
|
VAL="${3:?value}"
|
|
8
|
-
|
|
9
|
+
$PYTHON "$REPO_ROOT/scripts/yaml-tools.py" set "$FILE" "$KEY" "$VAL"
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
# story: e41s01
|
|
3
3
|
# build-receipts.sh — aggregate all quality evidence into specs/receipts.json.
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "build-receipts: not inside a git repo" >&2; exit 1; }
|
|
6
7
|
OUTFILE="$ROOT/specs/receipts.json"
|
|
7
8
|
TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
8
9
|
emit_json() {
|
|
9
|
-
|
|
10
|
+
$PYTHON -c "
|
|
10
11
|
import json, sys, os, subprocess, glob as gb
|
|
11
12
|
sections = {}
|
|
12
13
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# - specs/skill-graph.json — machine-readable dependency graph
|
|
5
5
|
# - specs/skills-wiki/ — OKF wiki with per-skill pages
|
|
6
6
|
set -euo pipefail
|
|
7
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
7
8
|
|
|
8
9
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
10
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -41,7 +42,7 @@ TMP_NODES=$(mktemp)
|
|
|
41
42
|
TMP_EDGES=$(mktemp)
|
|
42
43
|
TMP_ALL=$(mktemp)
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
$PYTHON -c "
|
|
45
46
|
import json, sys
|
|
46
47
|
|
|
47
48
|
nodes = {}
|
|
@@ -68,7 +69,7 @@ with open('$GRAPH_JSONL') as f:
|
|
|
68
69
|
print(json.dumps(list(nodes.values()), indent=2))
|
|
69
70
|
" > "$TMP_NODES"
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
$PYTHON -c "
|
|
72
73
|
import json
|
|
73
74
|
# re-read edges into a separate file
|
|
74
75
|
edges = []
|
|
@@ -86,7 +87,7 @@ print(json.dumps(edges, indent=2))
|
|
|
86
87
|
" > "$TMP_EDGES"
|
|
87
88
|
|
|
88
89
|
# --- Write skill-graph.json ---
|
|
89
|
-
|
|
90
|
+
$PYTHON -c "
|
|
90
91
|
import json
|
|
91
92
|
|
|
92
93
|
with open('$TMP_NODES') as f:
|
|
@@ -133,7 +134,7 @@ context: benchmark
|
|
|
133
134
|
WEOF
|
|
134
135
|
|
|
135
136
|
# Generate one .md per skill
|
|
136
|
-
|
|
137
|
+
$PYTHON -c "
|
|
137
138
|
import json, os
|
|
138
139
|
|
|
139
140
|
# Re-read nodes
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
# Severities: HIGH / MEDIUM / LOW. Exit 1 on HIGH findings.
|
|
11
11
|
|
|
12
12
|
set -euo pipefail
|
|
13
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
13
14
|
|
|
14
15
|
BLIND_SPOTS_JSON="$REPO_ROOT/specs/blind-spots.json"
|
|
15
16
|
EXEC_STATUS="$REPO_ROOT/specs/execution-status.yaml"
|
|
@@ -47,5 +48,5 @@ if [[ ! -f "$EXEC_STATUS" ]]; then
|
|
|
47
48
|
exit 1
|
|
48
49
|
fi
|
|
49
50
|
|
|
50
|
-
exec
|
|
51
|
+
exec $PYTHON "$(dirname "${BASH_SOURCE[0]}")/lib/blind-spots.py" \
|
|
51
52
|
"$REPO_ROOT" "$BLIND_SPOTS_JSON" "$EXEC_STATUS" "$MATRIX_JSON" "$VERIFICATIONS_DIR" "$EPICS_DIR"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Compare spec-file mtimes against implementing source-file mtimes,
|
|
4
4
|
# mark suspect links, write to specs/drift-report.json
|
|
5
5
|
set -euo pipefail
|
|
6
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
7
|
|
|
7
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
9
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -32,7 +33,7 @@ if [ ! -f "$TRACE_MATRIX" ]; then
|
|
|
32
33
|
exit 1
|
|
33
34
|
fi
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
$PYTHON -c "
|
|
36
37
|
import json, os, sys
|
|
37
38
|
|
|
38
39
|
with open('$TRACE_MATRIX') as f:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# check-spec-version-gap.sh — detect spec format gap (e44s02)
|
|
3
3
|
set -euo pipefail
|
|
4
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
4
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
6
|
BP_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
6
7
|
|
|
@@ -33,12 +34,12 @@ emit_json() {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
get_installed_version() {
|
|
36
|
-
|
|
37
|
+
$PYTHON -c "import json; d=json.load(open('$BP_ROOT/package.json')); print(d.get('version','unknown'))" 2>/dev/null || echo "unknown"
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
yaml_get() {
|
|
40
41
|
[[ ! -f "$1" ]] && { echo ""; return; }
|
|
41
|
-
|
|
42
|
+
$PYTHON -c "
|
|
42
43
|
import sys, yaml
|
|
43
44
|
d = yaml.safe_load(open('$1')) or {}
|
|
44
45
|
parts = '$2'.split('.')
|
|
@@ -64,7 +65,7 @@ version_lt() {
|
|
|
64
65
|
find_migrations() {
|
|
65
66
|
local ver="$1" reg="$BP_ROOT/specs/migrations/registry.okf.md"
|
|
66
67
|
[[ ! -f "$reg" ]] && { echo "[]"; return; }
|
|
67
|
-
|
|
68
|
+
$PYTHON -c "
|
|
68
69
|
import json, yaml
|
|
69
70
|
def vt(v): return tuple(int(p) if p.isdigit() else 0 for p in v.lstrip('v').split('.')[:3])
|
|
70
71
|
dt = vt('$ver')
|
|
@@ -108,8 +109,8 @@ if [[ -n "$SV" && "$SV" != "null" && "$SV" != "None" ]]; then
|
|
|
108
109
|
exit 0
|
|
109
110
|
fi
|
|
110
111
|
APP=$(find_migrations "$SV")
|
|
111
|
-
CNT=$(echo "$APP" |
|
|
112
|
-
emit_json "$(
|
|
112
|
+
CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
|
|
113
|
+
emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); print(json.dumps({'gap':True,'stamp':True,'detected_version':'$SV','detection_method':'stamp','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[m['id'] for m in a],'confidence':'high','active_work_blocked':False}))")"
|
|
113
114
|
exit 1
|
|
114
115
|
fi
|
|
115
116
|
|
|
@@ -136,8 +137,8 @@ if [[ "$DV" != "unknown" ]] && ! version_lt "$DV" "$IV"; then
|
|
|
136
137
|
fi
|
|
137
138
|
|
|
138
139
|
APP=$(find_migrations "$DV")
|
|
139
|
-
CNT=$(echo "$APP" |
|
|
140
|
-
MJ=$(printf '%s\n' "${MARKERS[@]}" |
|
|
140
|
+
CNT=$(echo "$APP" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
|
|
141
|
+
MJ=$(printf '%s\n' "${MARKERS[@]}" | $PYTHON -c "import json,sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))" 2>/dev/null || echo "[]")
|
|
141
142
|
|
|
142
|
-
emit_json "$(
|
|
143
|
+
emit_json "$($PYTHON -c "import json; a=json.loads('''$APP'''); m=json.loads('''$MJ'''); print(json.dumps({'gap':True,'stamp':False,'detected_version':'$DV','detection_method':'fingerprint','installed_version':'$IV','applicable_migrations':len(a),'migration_ids':[x['id'] for x in a],'confidence':'$CONF','active_work_blocked':False,'matched_markers':m}))")"
|
|
143
144
|
exit 1
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# story: e39s02
|
|
3
3
|
# Validate that no agent lock in specs/agent-locks.yaml is >24h old.
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
|
|
6
7
|
LOCK_FILE="specs/agent-locks.yaml"
|
|
7
8
|
if [ ! -f "$LOCK_FILE" ]; then
|
|
@@ -9,7 +10,7 @@ if [ ! -f "$LOCK_FILE" ]; then
|
|
|
9
10
|
exit 0
|
|
10
11
|
fi
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
$PYTHON -c "
|
|
13
14
|
import yaml, datetime, sys
|
|
14
15
|
|
|
15
16
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# convert-legado.sh — RELEASE-PLAN.md + SCOPE.md → YAML layout (one-time migration helper)
|
|
3
3
|
set -euo pipefail
|
|
4
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
4
5
|
SPECS="$REPO_ROOT/specs"
|
|
5
6
|
RP_MD="$SPECS/RELEASE-PLAN.md"
|
|
6
7
|
SCOPE_MD="$SPECS/SCOPE.md"
|
|
@@ -26,7 +27,7 @@ archive_if_needed() {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
# Parse ### WSn — Title · WSJF X.X from RELEASE-PLAN.md
|
|
29
|
-
|
|
30
|
+
$PYTHON - "$RP_MD" "$SPECS" <<'PY'
|
|
30
31
|
import json
|
|
31
32
|
import re
|
|
32
33
|
import sys
|
|
@@ -112,7 +113,7 @@ fi
|
|
|
112
113
|
# requirements/SCOPE stub from SCOPE.md if missing
|
|
113
114
|
if [[ -f "$SCOPE_MD" && ! -f "$SPECS/requirements/SCOPE_LATEST.yaml" ]]; then
|
|
114
115
|
mkdir -p "$SPECS/requirements"
|
|
115
|
-
|
|
116
|
+
$PYTHON - "$SCOPE_MD" "$SPECS/requirements/SCOPE_LATEST.yaml" <<'PY'
|
|
116
117
|
import sys
|
|
117
118
|
from pathlib import Path
|
|
118
119
|
src = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# story: e39s05
|
|
3
3
|
# Decompose CONVENTIONS.md into specs/conventions-wiki/ OKF concept bundle
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
|
|
6
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
8
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -51,7 +52,7 @@ context: conventions
|
|
|
51
52
|
IDXEOF
|
|
52
53
|
|
|
53
54
|
# Parse CONVENTIONS.md headings into sections
|
|
54
|
-
|
|
55
|
+
$PYTHON -c "
|
|
55
56
|
import re, os, json
|
|
56
57
|
|
|
57
58
|
with open('$CONVENTIONS') as f:
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# enrich-epics-from-archive.sh — populate specs/epics from archive RELEASE-PLAN.md
|
|
3
3
|
set -euo pipefail
|
|
4
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
4
5
|
ARCHIVE="${1:-$REPO_ROOT/specs/archive/RELEASE-PLAN.md}"
|
|
5
6
|
SPECS="$REPO_ROOT/specs"
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
$PYTHON - "$ARCHIVE" "$SPECS" <<'PY'
|
|
8
9
|
import json
|
|
9
10
|
import re
|
|
10
11
|
import sys
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# story: e45s01 e45s06
|
|
3
3
|
# generate-adr-wiki.sh — emit OKF concept bundles from specs/adr/*.md
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
|
|
6
7
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
8
|
ADRS="$ROOT/specs/adr"
|
|
@@ -30,7 +31,7 @@ for adr_file in "$ADRS"/*.md; do
|
|
|
30
31
|
# Parse YAML frontmatter if present
|
|
31
32
|
fm="{}"
|
|
32
33
|
if head -1 "$adr_file" 2>/dev/null | grep -q '^---$'; then
|
|
33
|
-
fm=$(
|
|
34
|
+
fm=$($PYTHON -c "
|
|
34
35
|
import sys, yaml, json
|
|
35
36
|
content = open('$adr_file').read()
|
|
36
37
|
parts = content.split('---')
|
|
@@ -43,9 +44,9 @@ if len(parts) >= 3:
|
|
|
43
44
|
" 2>/dev/null) || fm="{}"
|
|
44
45
|
fi
|
|
45
46
|
|
|
46
|
-
title=$(echo "$fm" |
|
|
47
|
-
status=$(echo "$fm" |
|
|
48
|
-
decision=$(echo "$fm" |
|
|
47
|
+
title=$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('title',''))" 2>/dev/null || echo "")
|
|
48
|
+
status=$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('status','accepted'))" 2>/dev/null || echo "accepted")
|
|
49
|
+
decision=$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('decision',''))" 2>/dev/null || echo "")
|
|
49
50
|
|
|
50
51
|
# Fallback title from first heading
|
|
51
52
|
if [ -z "$title" ]; then
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# story: e39s06
|
|
3
3
|
# Decompose CLAUDE.md into specs/agent-guide/ for progressive disclosure
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
|
|
6
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
8
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -36,7 +37,7 @@ fi
|
|
|
36
37
|
|
|
37
38
|
mkdir -p "$AGENT_GUIDE"
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
$PYTHON -c "
|
|
40
41
|
import re, os
|
|
41
42
|
|
|
42
43
|
with open('$CLAUDE_MD') as f:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# from live */SKILL.md frontmatter. Run after any SKILL.md addition/rename.
|
|
4
4
|
# Called by sync-skills.sh and npm version hooks.
|
|
5
5
|
set -euo pipefail
|
|
6
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
7
|
|
|
7
8
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
9
|
TARGET="$REPO_ROOT/docs/references/model-profiles.md"
|
|
@@ -44,7 +45,7 @@ Total: **$TOTAL** skills — verify with \`find . skills -maxdepth 2 -name SKILL
|
|
|
44
45
|
# --- 3. Splice block into target file ---
|
|
45
46
|
# Replace between marker comments if they exist; otherwise append.
|
|
46
47
|
if grep -q "AUTO-GENERATED-CATALOG: begin" "$TARGET"; then
|
|
47
|
-
|
|
48
|
+
$PYTHON - "$TARGET" "$BLOCK" <<'PYEOF'
|
|
48
49
|
import sys, re
|
|
49
50
|
path = sys.argv[1]
|
|
50
51
|
block = sys.argv[2]
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
# story: e45s04
|
|
3
3
|
# generate-viz-graph.sh — build specs/viz.html from bigpowers-mcp/graph.jsonl
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
7
|
GRAPH="$ROOT/bigpowers-mcp/graph.jsonl"
|
|
7
8
|
OUT="$ROOT/specs/viz.html"
|
|
8
9
|
|
|
9
|
-
ENTITIES_JSON=$(
|
|
10
|
+
ENTITIES_JSON=$($PYTHON -c "
|
|
10
11
|
import json, sys
|
|
11
12
|
entities = []
|
|
12
13
|
with open('$GRAPH') as f:
|
|
@@ -17,7 +18,7 @@ with open('$GRAPH') as f:
|
|
|
17
18
|
print(json.dumps(entities))
|
|
18
19
|
")
|
|
19
20
|
|
|
20
|
-
EDGES_JSON=$(
|
|
21
|
+
EDGES_JSON=$($PYTHON -c "
|
|
21
22
|
import json, sys
|
|
22
23
|
edges = []
|
|
23
24
|
with open('$GRAPH') as f:
|
|
@@ -28,7 +29,7 @@ with open('$GRAPH') as f:
|
|
|
28
29
|
print(json.dumps(edges))
|
|
29
30
|
")
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
$PYTHON - "$ENTITIES_JSON" "$EDGES_JSON" "$OUT" << 'PY'
|
|
32
33
|
import sys, json
|
|
33
34
|
|
|
34
35
|
entities_json = sys.argv[1]
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
# Exit 1: round trip destroyed structure (corruption bug is back)
|
|
18
18
|
|
|
19
19
|
set -euo pipefail
|
|
20
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
20
21
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
21
22
|
resolve_repo_root
|
|
22
23
|
|
|
@@ -38,10 +39,10 @@ WORKDIR="$(mktemp -d)"
|
|
|
38
39
|
trap 'rm -rf "$WORKDIR"' EXIT
|
|
39
40
|
cp "$FIXTURE" "$WORKDIR/sample.yaml"
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
$PYTHON "$TOOL" set "$WORKDIR/sample.yaml" release.version "9.9.9-test"
|
|
42
43
|
|
|
43
44
|
set +e
|
|
44
|
-
RESULT="$(
|
|
45
|
+
RESULT="$($PYTHON -c "
|
|
45
46
|
import sys, yaml
|
|
46
47
|
|
|
47
48
|
try:
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# Exit 1: trace-stories.py passed vacuously on the degenerate fixture (fail-open risk)
|
|
14
14
|
|
|
15
15
|
set -euo pipefail
|
|
16
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
16
17
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
17
18
|
resolve_repo_root
|
|
18
19
|
|
|
@@ -38,7 +39,7 @@ OKF_DIR=$(mktemp -d)
|
|
|
38
39
|
trap 'rm -f "$MATRIX_JSON" "$TRACE_MD"; rm -rf "$OKF_DIR"' EXIT
|
|
39
40
|
|
|
40
41
|
set +e
|
|
41
|
-
actual_output=$(
|
|
42
|
+
actual_output=$($PYTHON "$TRACE_ENGINE" "$FIXTURE_DIR" "$MATRIX_JSON" "$TRACE_MD" "$OKF_DIR" 1 "" 2>&1)
|
|
42
43
|
actual_exit=$?
|
|
43
44
|
set -e
|
|
44
45
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# migrate-version.sh — one-shot ordered migration engine with triple safety net.
|
|
4
4
|
# Safety: (1) backup, (2) dry-run diff, (3) auto-commit.
|
|
5
5
|
set -euo pipefail
|
|
6
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
7
|
|
|
7
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
9
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -40,7 +41,7 @@ installed_version() {
|
|
|
40
41
|
|
|
41
42
|
yaml_get() {
|
|
42
43
|
local file="$1" key="$2"
|
|
43
|
-
|
|
44
|
+
$PYTHON -c "
|
|
44
45
|
import yaml, sys
|
|
45
46
|
try:
|
|
46
47
|
d = yaml.safe_load(open('$file'))
|
|
@@ -157,7 +158,7 @@ fi
|
|
|
157
158
|
# ── build migration plan ──────────────────────────────────────────────────
|
|
158
159
|
echo "migrate-version: building migration plan from $REGISTRY …"
|
|
159
160
|
|
|
160
|
-
MIGRATION_PLAN="$(
|
|
161
|
+
MIGRATION_PLAN="$($PYTHON -c "
|
|
161
162
|
import yaml, json, sys
|
|
162
163
|
|
|
163
164
|
with open('$REGISTRY') as f:
|
|
@@ -181,7 +182,7 @@ applicable = [m for m in plan if m['since_version'] > detected and m['since_vers
|
|
|
181
182
|
print(json.dumps(applicable))
|
|
182
183
|
" 2>/dev/null)"
|
|
183
184
|
|
|
184
|
-
MIGRATION_COUNT="$(echo "$MIGRATION_PLAN" |
|
|
185
|
+
MIGRATION_COUNT="$(echo "$MIGRATION_PLAN" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null)"
|
|
185
186
|
|
|
186
187
|
if [ "${MIGRATION_COUNT:-0}" -eq 0 ]; then
|
|
187
188
|
echo "migrate-version: no applicable migrations for $DETECTED → $INSTALLED — stamping version only"
|
|
@@ -190,7 +191,7 @@ fi
|
|
|
190
191
|
echo "migrate-version: $MIGRATION_COUNT migration(s) to apply"
|
|
191
192
|
|
|
192
193
|
if [ "$VERBOSE" = "true" ]; then
|
|
193
|
-
echo "$MIGRATION_PLAN" |
|
|
194
|
+
echo "$MIGRATION_PLAN" | $PYTHON -c "
|
|
194
195
|
import json,sys
|
|
195
196
|
for m in json.load(sys.stdin):
|
|
196
197
|
print(f\" {m['order']}. {m['id']}: {m['title']} (since {m['since_version']})\")
|
|
@@ -216,7 +217,7 @@ if [ "$DRY_RUN" = "true" ]; then
|
|
|
216
217
|
echo "Detected version: $DETECTED"
|
|
217
218
|
echo "Target version: $INSTALLED"
|
|
218
219
|
echo "Migrations:"
|
|
219
|
-
echo "$MIGRATION_PLAN" |
|
|
220
|
+
echo "$MIGRATION_PLAN" | $PYTHON -c "
|
|
220
221
|
import json,sys
|
|
221
222
|
for m in json.load(sys.stdin):
|
|
222
223
|
deps = m.get('depends_on', [])
|
|
@@ -226,7 +227,7 @@ for m in json.load(sys.stdin):
|
|
|
226
227
|
|
|
227
228
|
echo ""
|
|
228
229
|
echo "Transforms preview:"
|
|
229
|
-
echo "$MIGRATION_PLAN" |
|
|
230
|
+
echo "$MIGRATION_PLAN" | $PYTHON -c "
|
|
230
231
|
import json, sys, os
|
|
231
232
|
for m in json.load(sys.stdin):
|
|
232
233
|
bundle_path = os.path.join('$MIGRATIONS_DIR', m['file'])
|
|
@@ -287,15 +288,15 @@ apply_transform() {
|
|
|
287
288
|
case "$action" in
|
|
288
289
|
convert_md_to_yaml)
|
|
289
290
|
local src tgt hm unc_count arch
|
|
290
|
-
src="$(echo "$tjson" |
|
|
291
|
-
tgt="$(echo "$tjson" |
|
|
292
|
-
arch="$(echo "$tjson" |
|
|
291
|
+
src="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
|
|
292
|
+
tgt="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
|
|
293
|
+
arch="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('archive_source','false'))" 2>/dev/null)" || arch="false"
|
|
293
294
|
if [ ! -f "$REPO_ROOT/$src" ]; then
|
|
294
295
|
log_skip "$action: source '$src' not found"
|
|
295
296
|
SKIPPED+=("$action:$src"); return
|
|
296
297
|
fi
|
|
297
|
-
hm="$(echo "$tjson" |
|
|
298
|
-
|
|
298
|
+
hm="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('heuristic_map',{})))")"
|
|
299
|
+
$PYTHON -c "
|
|
299
300
|
import json, re, yaml, sys, os
|
|
300
301
|
src = '$REPO_ROOT/$src'
|
|
301
302
|
tgt = '$REPO_ROOT/$tgt'
|
|
@@ -320,7 +321,7 @@ os.makedirs(os.path.dirname(tgt), exist_ok=True)
|
|
|
320
321
|
with open(tgt, 'w') as f:
|
|
321
322
|
yaml.safe_dump(result, f, sort_keys=False, allow_unicode=True, width=100)
|
|
322
323
|
"
|
|
323
|
-
unc_count="$(echo "$tjson" |
|
|
324
|
+
unc_count="$(echo "$tjson" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin).get('uncertainty',[])))")"
|
|
324
325
|
UNCERTAINTY_COUNT=$((UNCERTAINTY_COUNT + unc_count))
|
|
325
326
|
# Archive source file if requested
|
|
326
327
|
if [ "$arch" = "true" ] || [ "$arch" = "True" ]; then
|
|
@@ -335,8 +336,8 @@ with open(tgt, 'w') as f:
|
|
|
335
336
|
|
|
336
337
|
rename_file)
|
|
337
338
|
local src tgt
|
|
338
|
-
src="$(echo "$tjson" |
|
|
339
|
-
tgt="$(echo "$tjson" |
|
|
339
|
+
src="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
|
|
340
|
+
tgt="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
|
|
340
341
|
if [ ! -e "$REPO_ROOT/$src" ]; then
|
|
341
342
|
log_skip "$action: source '$src' not found — idempotent"
|
|
342
343
|
SKIPPED+=("$action:$src"); return
|
|
@@ -352,8 +353,8 @@ with open(tgt, 'w') as f:
|
|
|
352
353
|
|
|
353
354
|
move_file)
|
|
354
355
|
local src tgt
|
|
355
|
-
src="$(echo "$tjson" |
|
|
356
|
-
tgt="$(echo "$tjson" |
|
|
356
|
+
src="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
|
|
357
|
+
tgt="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
|
|
357
358
|
if [ ! -e "$REPO_ROOT/$src" ]; then
|
|
358
359
|
log_skip "$action: source '$src' not found"
|
|
359
360
|
SKIPPED+=("$action:$src"); return
|
|
@@ -369,7 +370,7 @@ with open(tgt, 'w') as f:
|
|
|
369
370
|
|
|
370
371
|
delete_file)
|
|
371
372
|
local path
|
|
372
|
-
path="$(echo "$tjson" |
|
|
373
|
+
path="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('path',''))")"
|
|
373
374
|
if [ ! -e "$REPO_ROOT/$path" ]; then
|
|
374
375
|
log_skip "$action: '$path' not found — idempotent"
|
|
375
376
|
SKIPPED+=("$action:$path"); return
|
|
@@ -380,10 +381,10 @@ with open(tgt, 'w') as f:
|
|
|
380
381
|
|
|
381
382
|
set_yaml_key)
|
|
382
383
|
local file key val if_missing
|
|
383
|
-
file="$(echo "$tjson" |
|
|
384
|
-
key="$(echo "$tjson" |
|
|
385
|
-
val="$(echo "$tjson" |
|
|
386
|
-
if_missing="$(echo "$tjson" |
|
|
384
|
+
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
385
|
+
key="$(echo "$tjson" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
|
|
386
|
+
val="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('value',''))")"
|
|
387
|
+
if_missing="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('if_missing','false'))" 2>/dev/null)" || if_missing="false"
|
|
387
388
|
if [ "$if_missing" = "true" ] || [ "$if_missing" = "True" ]; then
|
|
388
389
|
if yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
389
390
|
log_skip "$action: $file $key already set (if_missing=true) — idempotent"
|
|
@@ -391,22 +392,22 @@ with open(tgt, 'w') as f:
|
|
|
391
392
|
return
|
|
392
393
|
fi
|
|
393
394
|
fi
|
|
394
|
-
|
|
395
|
+
$PYTHON "$YAML_TOOLS" set "$REPO_ROOT/$file" "$key" "$val"
|
|
395
396
|
log_done "$action: $file $key = $val"
|
|
396
397
|
;;
|
|
397
398
|
|
|
398
399
|
rename_yaml_key)
|
|
399
400
|
local file old_key new_key old_val
|
|
400
|
-
file="$(echo "$tjson" |
|
|
401
|
-
old_key="$(echo "$tjson" |
|
|
402
|
-
new_key="$(echo "$tjson" |
|
|
401
|
+
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
402
|
+
old_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('old_key',''))")"
|
|
403
|
+
new_key="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('new_key',''))")"
|
|
403
404
|
old_val="$(yaml_get "$REPO_ROOT/$file" "$old_key" 2>/dev/null)" || true
|
|
404
405
|
if [ -z "${old_val:-}" ]; then
|
|
405
406
|
log_skip "$action: key '$old_key' not found in $file"
|
|
406
407
|
SKIPPED+=("$action:$old_key"); return
|
|
407
408
|
fi
|
|
408
|
-
|
|
409
|
-
|
|
409
|
+
$PYTHON "$YAML_TOOLS" set "$REPO_ROOT/$file" "$new_key" "$old_val"
|
|
410
|
+
$PYTHON -c "
|
|
410
411
|
import yaml
|
|
411
412
|
data = yaml.safe_load(open('$REPO_ROOT/$file'))
|
|
412
413
|
parts = '$old_key'.split('.')
|
|
@@ -424,13 +425,13 @@ with open('$REPO_ROOT/$file', 'w') as f:
|
|
|
424
425
|
|
|
425
426
|
delete_yaml_key)
|
|
426
427
|
local file key
|
|
427
|
-
file="$(echo "$tjson" |
|
|
428
|
-
key="$(echo "$tjson" |
|
|
428
|
+
file="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
|
|
429
|
+
key="$(echo "$tjson" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
|
|
429
430
|
if ! yaml_get "$REPO_ROOT/$file" "$key" >/dev/null 2>&1; then
|
|
430
431
|
log_skip "$action: key '$key' not found in $file — idempotent"
|
|
431
432
|
SKIPPED+=("$action:$key"); return
|
|
432
433
|
fi
|
|
433
|
-
|
|
434
|
+
$PYTHON -c "
|
|
434
435
|
import yaml
|
|
435
436
|
data = yaml.safe_load(open('$REPO_ROOT/$file'))
|
|
436
437
|
parts = '$key'.split('.')
|
|
@@ -448,10 +449,10 @@ with open('$REPO_ROOT/$file', 'w') as f:
|
|
|
448
449
|
|
|
449
450
|
create_file_from_template)
|
|
450
451
|
local src tgt subs inline_tmpl
|
|
451
|
-
src="$(echo "$tjson" |
|
|
452
|
-
tgt="$(echo "$tjson" |
|
|
453
|
-
subs="$(echo "$tjson" |
|
|
454
|
-
inline_tmpl="$(echo "$tjson" |
|
|
452
|
+
src="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
|
|
453
|
+
tgt="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
|
|
454
|
+
subs="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('substitutions',{})))")"
|
|
455
|
+
inline_tmpl="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('template',''))")"
|
|
455
456
|
if [ -e "$REPO_ROOT/$tgt" ]; then
|
|
456
457
|
log_skip "$action: target '$tgt' already exists — idempotent"
|
|
457
458
|
SKIPPED+=("$action:$tgt"); return
|
|
@@ -461,7 +462,7 @@ with open('$REPO_ROOT/$file', 'w') as f:
|
|
|
461
462
|
if [ -n "$inline_tmpl" ]; then
|
|
462
463
|
echo "$inline_tmpl" > "$REPO_ROOT/$tgt"
|
|
463
464
|
if [ "$subs" != "{}" ]; then
|
|
464
|
-
|
|
465
|
+
$PYTHON -c "
|
|
465
466
|
import json
|
|
466
467
|
subs = json.loads('''$subs''')
|
|
467
468
|
content = open('$REPO_ROOT/$tgt').read()
|
|
@@ -489,7 +490,7 @@ with open('$REPO_ROOT/$tgt', 'w') as f:
|
|
|
489
490
|
SKIPPED+=("$action:$src"); return
|
|
490
491
|
fi
|
|
491
492
|
cp "$template_path" "$REPO_ROOT/$tgt"
|
|
492
|
-
|
|
493
|
+
$PYTHON -c "
|
|
493
494
|
import json
|
|
494
495
|
subs = json.loads('''$subs''')
|
|
495
496
|
content = open('$REPO_ROOT/$tgt').read()
|
|
@@ -509,12 +510,12 @@ with open('$REPO_ROOT/$tgt', 'w') as f:
|
|
|
509
510
|
}
|
|
510
511
|
|
|
511
512
|
# Execute each migration in order
|
|
512
|
-
MIG_LIST="$(echo "$MIGRATION_PLAN" |
|
|
513
|
+
MIG_LIST="$(echo "$MIGRATION_PLAN" | $PYTHON -c "import json,sys; [print(json.dumps(m)) for m in json.load(sys.stdin)]")"
|
|
513
514
|
while IFS= read -r mig_json; do
|
|
514
515
|
[ -z "$mig_json" ] && continue
|
|
515
|
-
MIG_ID="$(echo "$mig_json" |
|
|
516
|
-
MIG_FILE="$(echo "$mig_json" |
|
|
517
|
-
MIG_TITLE="$(echo "$mig_json" |
|
|
516
|
+
MIG_ID="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['id'])")"
|
|
517
|
+
MIG_FILE="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['file'])")"
|
|
518
|
+
MIG_TITLE="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['title'])")"
|
|
518
519
|
|
|
519
520
|
BUNDLE_PATH="$MIGRATIONS_DIR/$MIG_FILE"
|
|
520
521
|
if [ ! -f "$BUNDLE_PATH" ]; then
|
|
@@ -526,7 +527,7 @@ while IFS= read -r mig_json; do
|
|
|
526
527
|
echo ""
|
|
527
528
|
echo "── $MIG_ID: $MIG_TITLE ──"
|
|
528
529
|
|
|
529
|
-
TRANSFORMS="$(
|
|
530
|
+
TRANSFORMS="$($PYTHON -c "
|
|
530
531
|
import yaml, json
|
|
531
532
|
with open('$BUNDLE_PATH') as f:
|
|
532
533
|
parts = f.read().split('---')
|
|
@@ -534,15 +535,15 @@ with open('$BUNDLE_PATH') as f:
|
|
|
534
535
|
print(json.dumps(fm.get('transforms', []), default=str))
|
|
535
536
|
" 2>/dev/null)"
|
|
536
537
|
|
|
537
|
-
T_LIST="$(echo "$TRANSFORMS" |
|
|
538
|
+
T_LIST="$(echo "$TRANSFORMS" | $PYTHON -c "import json,sys; [print(json.dumps(t, default=str)) for t in json.load(sys.stdin)]")"
|
|
538
539
|
while IFS= read -r tjson; do
|
|
539
540
|
[ -z "$tjson" ] && continue
|
|
540
|
-
action="$(echo "$tjson" |
|
|
541
|
+
action="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('action',''))")"
|
|
541
542
|
apply_transform "$MIG_ID" "$action" "$tjson"
|
|
542
543
|
done <<< "$T_LIST"
|
|
543
544
|
|
|
544
545
|
# Run verify commands
|
|
545
|
-
VERIFY_CMDS="$(
|
|
546
|
+
VERIFY_CMDS="$($PYTHON -c "
|
|
546
547
|
import yaml, json
|
|
547
548
|
with open('$BUNDLE_PATH') as f:
|
|
548
549
|
parts = f.read().split('---')
|
|
@@ -574,7 +575,7 @@ done <<< "$MIG_LIST"
|
|
|
574
575
|
# ── stamp ─────────────────────────────────────────────────────────────────
|
|
575
576
|
if [ -f "$SPECS_DIR/state.yaml" ]; then
|
|
576
577
|
log_info "Stamping bigpowers_version=$INSTALLED in state.yaml"
|
|
577
|
-
|
|
578
|
+
$PYTHON "$YAML_TOOLS" set "$SPECS_DIR/state.yaml" "bigpowers_version" "$INSTALLED"
|
|
578
579
|
log_done "stamp applied"
|
|
579
580
|
fi
|
|
580
581
|
|
package/scripts/okf-post-sync.sh
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Called by sync-skills.sh --okf after concept files are rendered.
|
|
5
5
|
# Steps: cross-references from skill-graph.json → index.md → validate-okf.sh
|
|
6
6
|
set -euo pipefail
|
|
7
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
7
8
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
8
9
|
resolve_repo_root
|
|
9
10
|
|
|
@@ -28,13 +29,13 @@ fi
|
|
|
28
29
|
# ── Step 2: Add cross-references from skill-graph.json ─────────────
|
|
29
30
|
echo "Step 2: Adding cross-references to OKF concepts..."
|
|
30
31
|
|
|
31
|
-
if [[ -f "$GRAPH_JSON" ]] && command -v
|
|
32
|
-
|
|
32
|
+
if [[ -f "$GRAPH_JSON" ]] && command -v $PYTHON &>/dev/null; then
|
|
33
|
+
$PYTHON "$REPO_ROOT/scripts/okf-add-references.py" "$GRAPH_JSON" "$OKF_WIKI_SKILLS"
|
|
33
34
|
else
|
|
34
35
|
echo " Adding fallback references..."
|
|
35
36
|
for f in "$OKF_WIKI_SKILLS"/*.md; do
|
|
36
37
|
[[ -f "$f" ]] || continue
|
|
37
|
-
|
|
38
|
+
$PYTHON -c "
|
|
38
39
|
import re
|
|
39
40
|
c = open('$f').read()
|
|
40
41
|
c = re.sub(r'references:\s*\[\]', 'references:\n - concept: skills-wiki\n type: belongs_to', c)
|
|
@@ -46,7 +47,7 @@ fi
|
|
|
46
47
|
# ── Step 3: Generate index.md with progressive disclosure ──────────
|
|
47
48
|
echo "Step 3: Generating index.md..."
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
$PYTHON - "$OKF_WIKI_SKILLS" "$OKF_INDEX" <<'PYEOF'
|
|
50
51
|
import re, sys
|
|
51
52
|
from pathlib import Path
|
|
52
53
|
from collections import defaultdict
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# bash scripts/run-golden-suite.sh --baseline # run + pin baseline
|
|
14
14
|
|
|
15
15
|
set -euo pipefail
|
|
16
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
16
17
|
|
|
17
18
|
REPORT_DIR="specs/benchmarks/reports"
|
|
18
19
|
BASELINE_FILE="$REPORT_DIR/BASELINE-GOLDEN.yaml"
|
|
@@ -80,9 +81,9 @@ agent_dry_run() {
|
|
|
80
81
|
(( yaml_count += 1 ))
|
|
81
82
|
story_id=$(basename "$yaml_file" .yaml)
|
|
82
83
|
|
|
83
|
-
if
|
|
84
|
-
local grader_type=$(
|
|
85
|
-
local pass_k=$(
|
|
84
|
+
if $PYTHON -c "import yaml; yaml.safe_load(open('$yaml_file'))" 2>/dev/null; then
|
|
85
|
+
local grader_type=$($PYTHON -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('grader',{}).get('type',''))" 2>/dev/null)
|
|
86
|
+
local pass_k=$($PYTHON -c "import yaml; d=yaml.safe_load(open('$yaml_file')); pk=d.get('pass_at_k',{}); print(f\"{pk.get('k',0)}-of-{pk.get('threshold',0)}\")" 2>/dev/null)
|
|
86
87
|
echo -e " ${GREEN}OK${NC} $story_id — grader=$grader_type, pass@k=$pass_k"
|
|
87
88
|
(( yaml_ok += 1 ))
|
|
88
89
|
else
|
|
@@ -235,8 +236,8 @@ if $AGENT_MODE && $DETERMINISTIC_PASSED; then
|
|
|
235
236
|
echo -n "[$story_id] "
|
|
236
237
|
|
|
237
238
|
# Read pass@k policy
|
|
238
|
-
PASS_K=$(
|
|
239
|
-
PASS_THRESHOLD=$(
|
|
239
|
+
PASS_K=$($PYTHON -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('pass_at_k',{}).get('k',3))" 2>/dev/null || echo 3)
|
|
240
|
+
PASS_THRESHOLD=$($PYTHON -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('pass_at_k',{}).get('threshold',2))" 2>/dev/null || echo 2)
|
|
240
241
|
|
|
241
242
|
story_pass=0
|
|
242
243
|
story_fail=0
|
|
@@ -254,7 +255,7 @@ if $AGENT_MODE && $DETERMINISTIC_PASSED; then
|
|
|
254
255
|
# gh-aw run would go here. For now, check if workflow exists and is valid.
|
|
255
256
|
# The actual agent execution is triggered via gh workflow run in CI;
|
|
256
257
|
# local runs validate YAML definitions only.
|
|
257
|
-
if
|
|
258
|
+
if $PYTHON -c "
|
|
258
259
|
import yaml
|
|
259
260
|
d = yaml.safe_load(open('$yaml_file'))
|
|
260
261
|
assert d.get('grader',{}).get('type') == 'code', 'grader must be code'
|
package/scripts/run-smoke.sh
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# Reads from smoke-checks.yaml by default. Falls back to single-URL check.
|
|
6
6
|
# Env vars: DEPLOY_URL, SMOKE_CHECKS_FILE, SMOKE_TIMEOUT, SMOKE_RETRIES
|
|
7
7
|
set -euo pipefail
|
|
8
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
8
9
|
|
|
9
10
|
SMOKE_CHECKS_FILE="${2:-${SMOKE_CHECKS_FILE:-smoke-checks.yaml}}"
|
|
10
11
|
BASE_URL="${1:-${DEPLOY_URL:-}}"
|
|
@@ -27,9 +28,9 @@ run_single_url_check() {
|
|
|
27
28
|
local content_signal="${4:-}"
|
|
28
29
|
|
|
29
30
|
echo "[$name]"
|
|
30
|
-
start_time=$(
|
|
31
|
+
start_time=$($PYTHON -c 'import time; print(int(time.time() * 1000))')
|
|
31
32
|
response=$(curl -s -o "$TMPDIR/body.txt" -w "%{http_code}" --max-time "$SMOKE_TIMEOUT" "$url")
|
|
32
|
-
response_time=$(( $(
|
|
33
|
+
response_time=$(( $($PYTHON -c 'import time; print(int(time.time() * 1000))') - start_time ))
|
|
33
34
|
status="$response"
|
|
34
35
|
body=$(cat "$TMPDIR/body.txt" 2>/dev/null || echo "")
|
|
35
36
|
|
|
@@ -147,9 +148,9 @@ run_parsed_check() {
|
|
|
147
148
|
|
|
148
149
|
echo "[$name]"
|
|
149
150
|
echo " URL: $url"
|
|
150
|
-
start_time=$(
|
|
151
|
+
start_time=$($PYTHON -c 'import time; print(int(time.time() * 1000))')
|
|
151
152
|
response=$(curl -s -o "$TMPDIR/body.txt" -w "%{http_code}" --max-time "$SMOKE_TIMEOUT" "$url" 2>/dev/null || echo "000")
|
|
152
|
-
response_time=$(( $(
|
|
153
|
+
response_time=$(( $($PYTHON -c 'import time; print(int(time.time() * 1000))') - start_time ))
|
|
153
154
|
status="$response"
|
|
154
155
|
body=$(cat "$TMPDIR/body.txt" 2>/dev/null || echo "")
|
|
155
156
|
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
# story: e45s03
|
|
3
3
|
# sync-bugs-registry.sh — rebuild specs/bugs/registry.yaml + OKF concept bundles
|
|
4
4
|
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
5
6
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
6
7
|
BUGS="$REPO_ROOT/specs/bugs"
|
|
7
8
|
mkdir -p "$BUGS"
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
$PYTHON - "$BUGS" "$REPO_ROOT" <<'PY'
|
|
10
11
|
import re
|
|
11
12
|
import sys
|
|
12
13
|
from pathlib import Path
|
package/scripts/sync-skills.sh
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# --okf Generate specs/skills-wiki/ OKF concept bundle from SKILL.md frontmatter
|
|
9
9
|
# --opencode <p> Sync to bigpowers-opencode repo
|
|
10
10
|
set -euo pipefail
|
|
11
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
11
12
|
|
|
12
13
|
# ── Parse CLI flags (must happen before source to allow early --help) ─
|
|
13
14
|
OKF_MODE=0
|
|
@@ -409,7 +410,7 @@ if [[ "$OKF_MODE" -eq 1 ]]; then
|
|
|
409
410
|
# Step 2b: Add cross-references from skill-graph.json edges into OKF concepts
|
|
410
411
|
if [[ -f "$GRAPH_JSON" ]]; then
|
|
411
412
|
echo "Adding cross-references from skill-graph.json..."
|
|
412
|
-
|
|
413
|
+
$PYTHON - "$GRAPH_JSON" "$OKF_WIKI_SKILLS" <<'PYEOF'
|
|
413
414
|
import json, sys, re
|
|
414
415
|
from pathlib import Path
|
|
415
416
|
|
|
@@ -489,8 +490,8 @@ PYEOF
|
|
|
489
490
|
# Add fallback references to all concepts
|
|
490
491
|
for f in "$OKF_WIKI_SKILLS"/*.md; do
|
|
491
492
|
[[ -f "$f" ]] || continue
|
|
492
|
-
if command -v
|
|
493
|
-
|
|
493
|
+
if command -v $PYTHON &>/dev/null; then
|
|
494
|
+
$PYTHON -c "
|
|
494
495
|
import re, sys
|
|
495
496
|
content = open('$f').read()
|
|
496
497
|
content = re.sub(r'references:\s*\[\]', 'references:\n - concept: skills-wiki\n type: belongs_to', content)
|
|
@@ -503,7 +504,7 @@ open('$f', 'w').write(content)
|
|
|
503
504
|
# Step 3: Generate specs/skills-wiki/index.md with progressive disclosure
|
|
504
505
|
echo "Generating index.md..."
|
|
505
506
|
OKF_INDEX="$OKF_WIKI_DIR/index.md"
|
|
506
|
-
|
|
507
|
+
$PYTHON - "$OKF_WIKI_SKILLS" "$OKF_WIKI_DIR" "$OKF_INDEX" <<'PYEOF'
|
|
507
508
|
import json, sys, re
|
|
508
509
|
from pathlib import Path
|
|
509
510
|
from collections import defaultdict
|
|
@@ -662,10 +663,10 @@ fi
|
|
|
662
663
|
|
|
663
664
|
# Regression guard (BUG-2026-06-18T100000): validate generated YAML frontmatter
|
|
664
665
|
validate_script="$REPO_ROOT/scripts/validate-skill-yaml.py"
|
|
665
|
-
if [[ -f "$validate_script" ]] && command -v
|
|
666
|
-
if !
|
|
666
|
+
if [[ -f "$validate_script" ]] && command -v $PYTHON &>/dev/null; then
|
|
667
|
+
if ! $PYTHON "$validate_script" > /dev/null 2>&1; then
|
|
667
668
|
echo "sync-skills: FAIL — YAML frontmatter validation failed" >&2
|
|
668
|
-
|
|
669
|
+
$PYTHON "$validate_script" >&2
|
|
669
670
|
exit 1
|
|
670
671
|
fi
|
|
671
672
|
fi
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# sync-status-from-epics.sh — seed execution-status.yaml keys from epic shards
|
|
3
3
|
set -euo pipefail
|
|
4
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
4
5
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
5
6
|
resolve_repo_root
|
|
6
7
|
SPECS="$REPO_ROOT/specs"
|
|
7
8
|
OUT="$SPECS/execution-status.yaml"
|
|
8
9
|
EPICS="$SPECS/epics"
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
$PYTHON - "$EPICS" "$OUT" <<'PY'
|
|
11
12
|
import re
|
|
12
13
|
import sys
|
|
13
14
|
from pathlib import Path
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
# Runs BEFORE @semantic-release/git commits everything.
|
|
10
10
|
|
|
11
11
|
set -euo pipefail
|
|
12
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
12
13
|
|
|
13
14
|
VERSION="${1:?usage: sync-version-mirrors.sh <version>}"
|
|
14
15
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
@@ -16,10 +17,10 @@ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
16
17
|
echo "sync-version-mirrors.sh: bumping mirrors to $VERSION"
|
|
17
18
|
|
|
18
19
|
# ── 1. YAML mirror fields ──────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
+
$PYTHON "$REPO_ROOT/scripts/yaml-tools.py" set \
|
|
20
21
|
"$REPO_ROOT/specs/state.yaml" bigpowers_version "$VERSION"
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
$PYTHON "$REPO_ROOT/scripts/yaml-tools.py" set \
|
|
23
24
|
"$REPO_ROOT/specs/release-plan.yaml" release.version "$VERSION"
|
|
24
25
|
|
|
25
26
|
# ── 2. Regenerate derived artifacts (reads already-bumped package.json) ─
|
package/scripts/trace-stories.sh
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
# 2 — --strict mode: P0 story has 0% coverage
|
|
17
17
|
|
|
18
18
|
set -euo pipefail
|
|
19
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
19
20
|
|
|
20
21
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
21
22
|
|
|
@@ -68,5 +69,5 @@ if [[ ! -f "$EXEC_STATUS" ]]; then
|
|
|
68
69
|
exit 1
|
|
69
70
|
fi
|
|
70
71
|
|
|
71
|
-
exec
|
|
72
|
+
exec $PYTHON "$(dirname "${BASH_SOURCE[0]}")/lib/trace-stories.py" \
|
|
72
73
|
"$REPO_ROOT" "$MATRIX_JSON" "$TRACE_MD" "$OKF_DIR" "$STRICT" "$MODE"
|
package/scripts/validate-okf.sh
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# and verification-report bundles.
|
|
6
6
|
# Multi-dir default: specs/metrics/ + specs/migrations/.
|
|
7
7
|
set -euo pipefail
|
|
8
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
8
9
|
|
|
9
10
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "validate-okf: not inside a git repo"; exit 1; }
|
|
10
11
|
EXIT_CODE=0
|
|
@@ -42,7 +43,7 @@ val_nullish_or_empty() { val_nullish "$1" || [ "$1" = "[]" ]; }
|
|
|
42
43
|
|
|
43
44
|
parse_frontmatter() {
|
|
44
45
|
local file="$1"
|
|
45
|
-
|
|
46
|
+
$PYTHON -c "
|
|
46
47
|
import yaml, sys, json
|
|
47
48
|
|
|
48
49
|
class SafeEncoder(json.JSONEncoder):
|
|
@@ -74,7 +75,7 @@ validate_story_metrics() {
|
|
|
74
75
|
local errs=0
|
|
75
76
|
for key in id epic bcps commit_range source generated_at generator; do
|
|
76
77
|
local val
|
|
77
|
-
val="$(echo "$fm" |
|
|
78
|
+
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
78
79
|
if val_nullish "$val"; then
|
|
79
80
|
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
80
81
|
errs=$((errs + 1))
|
|
@@ -83,28 +84,28 @@ validate_story_metrics() {
|
|
|
83
84
|
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
84
85
|
|
|
85
86
|
local source
|
|
86
|
-
source="$(echo "$fm" |
|
|
87
|
+
source="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('source',''))" 2>/dev/null)"
|
|
87
88
|
if ! echo "measured estimated backfilled" | grep -qw "$source"; then
|
|
88
89
|
printf "${RED}FAIL${NC} %s: invalid source '%s' — must be measured|estimated|backfilled\n" "$name" "$source"
|
|
89
90
|
EXIT_CODE=1; return
|
|
90
91
|
fi
|
|
91
92
|
|
|
92
93
|
local generator
|
|
93
|
-
generator="$(echo "$fm" |
|
|
94
|
+
generator="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('generator',''))" 2>/dev/null)"
|
|
94
95
|
if [ "$generator" != "scripts/record-cycle-time.sh" ]; then
|
|
95
96
|
printf "${RED}FAIL${NC} %s: wrong generator '%s' — expected scripts/record-cycle-time.sh\n" "$name" "$generator"
|
|
96
97
|
EXIT_CODE=1; return
|
|
97
98
|
fi
|
|
98
99
|
|
|
99
100
|
local commit_range
|
|
100
|
-
commit_range="$(echo "$fm" |
|
|
101
|
+
commit_range="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
|
|
101
102
|
if ! git -C "$ROOT" log --oneline -1 "$commit_range" >/dev/null 2>&1; then
|
|
102
103
|
printf "${RED}FAIL${NC} %s: commit_range '%s' does not resolve\n" "$name" "$commit_range"
|
|
103
104
|
EXIT_CODE=1; return
|
|
104
105
|
fi
|
|
105
106
|
|
|
106
107
|
local eff
|
|
107
|
-
eff="$(echo "$fm" |
|
|
108
|
+
eff="$(echo "$fm" | $PYTHON -c "
|
|
108
109
|
import json,sys
|
|
109
110
|
d=json.load(sys.stdin)
|
|
110
111
|
e=d.get('effort',{}).get('effort_hours')
|
|
@@ -128,7 +129,7 @@ validate_spec_migration() {
|
|
|
128
129
|
local errs=0
|
|
129
130
|
for key in id title since_version order actions_needed; do
|
|
130
131
|
local val
|
|
131
|
-
val="$(echo "$fm" |
|
|
132
|
+
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
132
133
|
if val_nullish_or_empty "$val"; then
|
|
133
134
|
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
134
135
|
errs=$((errs + 1))
|
|
@@ -137,7 +138,7 @@ validate_spec_migration() {
|
|
|
137
138
|
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
138
139
|
|
|
139
140
|
local fp_count
|
|
140
|
-
fp_count="$(echo "$fm" |
|
|
141
|
+
fp_count="$(echo "$fm" | $PYTHON -c "
|
|
141
142
|
import json,sys
|
|
142
143
|
d=json.load(sys.stdin)
|
|
143
144
|
fp=d.get('fingerprint',{}).get('any',[])
|
|
@@ -150,14 +151,14 @@ print(len(fp))
|
|
|
150
151
|
|
|
151
152
|
printf "${GREEN}PASS${NC} %s (actions=%s, fingerprint has %s check(s))\n" \
|
|
152
153
|
"$name" \
|
|
153
|
-
"$(echo "$fm" |
|
|
154
|
+
"$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('actions_needed',[])))" 2>/dev/null)" \
|
|
154
155
|
"$fp_count"
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
validate_migration_registry() {
|
|
158
159
|
local file="$1" fm="$2" name="$3"
|
|
159
160
|
local count
|
|
160
|
-
count="$(echo "$fm" |
|
|
161
|
+
count="$(echo "$fm" | $PYTHON -c "
|
|
161
162
|
import json,sys
|
|
162
163
|
d=json.load(sys.stdin)
|
|
163
164
|
migrations=d.get('migrations',[])
|
|
@@ -169,7 +170,7 @@ print(len(migrations))
|
|
|
169
170
|
fi
|
|
170
171
|
|
|
171
172
|
local bad
|
|
172
|
-
bad="$(echo "$fm" |
|
|
173
|
+
bad="$(echo "$fm" | $PYTHON -c "
|
|
173
174
|
import json,sys
|
|
174
175
|
d=json.load(sys.stdin)
|
|
175
176
|
for m in d.get('migrations',[]):
|
|
@@ -188,14 +189,14 @@ validate_concept() {
|
|
|
188
189
|
local errs=0
|
|
189
190
|
for key in id title category; do
|
|
190
191
|
local val
|
|
191
|
-
val="$(echo "$fm" |
|
|
192
|
+
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
192
193
|
if val_nullish "$val"; then
|
|
193
194
|
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
194
195
|
errs=$((errs + 1))
|
|
195
196
|
fi
|
|
196
197
|
done
|
|
197
198
|
local ref_count
|
|
198
|
-
ref_count="$(echo "$fm" |
|
|
199
|
+
ref_count="$(echo "$fm" | $PYTHON -c "
|
|
199
200
|
import json,sys
|
|
200
201
|
d=json.load(sys.stdin)
|
|
201
202
|
r=d.get('references',[])
|
|
@@ -207,7 +208,7 @@ print(len(r) if isinstance(r,list) else 0)
|
|
|
207
208
|
fi
|
|
208
209
|
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
209
210
|
local cat
|
|
210
|
-
cat="$(echo "$fm" |
|
|
211
|
+
cat="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('category',''))" 2>/dev/null)"
|
|
211
212
|
printf "${GREEN}PASS${NC} %s (category=%s, %s reference(s))\n" "$name" "$cat" "$ref_count"
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -217,7 +218,7 @@ validate_verification_report() {
|
|
|
217
218
|
local errs=0
|
|
218
219
|
for key in score gate_status threshold total_pass total_fail generated_by; do
|
|
219
220
|
local val
|
|
220
|
-
val="$(echo "$fm" |
|
|
221
|
+
val="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
|
|
221
222
|
if val_nullish "$val"; then
|
|
222
223
|
printf "${RED}FAIL${NC} %s: missing required key '%s'\n" "$name" "$key"
|
|
223
224
|
errs=$((errs + 1))
|
|
@@ -226,14 +227,14 @@ validate_verification_report() {
|
|
|
226
227
|
[ "$errs" -gt 0 ] && { EXIT_CODE=1; return; }
|
|
227
228
|
|
|
228
229
|
local gs
|
|
229
|
-
gs="$(echo "$fm" |
|
|
230
|
+
gs="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('gate_status',''))" 2>/dev/null)"
|
|
230
231
|
if ! echo "pass fail concerns waived" | grep -qw "$gs"; then
|
|
231
232
|
printf "${RED}FAIL${NC} %s: invalid gate_status '%s' — must be pass|fail|concerns|waived\n" "$name" "$gs"
|
|
232
233
|
EXIT_CODE=1; return
|
|
233
234
|
fi
|
|
234
235
|
|
|
235
236
|
local score
|
|
236
|
-
score="$(echo "$fm" |
|
|
237
|
+
score="$(echo "$fm" | $PYTHON -c "
|
|
237
238
|
import json,sys
|
|
238
239
|
d=json.load(sys.stdin)
|
|
239
240
|
s=d.get('score')
|
|
@@ -254,14 +255,14 @@ print(s)
|
|
|
254
255
|
|
|
255
256
|
printf "${GREEN}PASS${NC} %s (score=%.1f, gate=%s, threshold=%.1f)\n" \
|
|
256
257
|
"$name" "$score" "$gs" \
|
|
257
|
-
"$(echo "$fm" |
|
|
258
|
+
"$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('threshold',0))" 2>/dev/null)"
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
# ---- Receipts validation (e41s04) ----
|
|
261
262
|
validate_receipts() {
|
|
262
263
|
local f="$ROOT/specs/receipts.json"
|
|
263
264
|
if [ ! -f "$f" ]; then printf "${YELLOW}SKIP${NC} receipts.json not found\n"; return; fi
|
|
264
|
-
|
|
265
|
+
$PYTHON -c "
|
|
265
266
|
import json, sys
|
|
266
267
|
d=json.load(open('$f'))
|
|
267
268
|
vs={'measured','estimated','backfilled','absent'}
|
|
@@ -296,7 +297,7 @@ validate_bundle() {
|
|
|
296
297
|
# ── OKF v0.1 Generic Frontmatter Conformance (e39s10) ──
|
|
297
298
|
# Check required 'type' field
|
|
298
299
|
local fm_type
|
|
299
|
-
fm_type="$(echo "$fm" |
|
|
300
|
+
fm_type="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('type',''))" 2>/dev/null)"
|
|
300
301
|
if [ -z "$fm_type" ]; then
|
|
301
302
|
printf "${RED}FAIL${NC} %s: OKF v0.1 — missing required 'type' field\n" "$name"
|
|
302
303
|
EXIT_CODE=1
|
|
@@ -311,7 +312,7 @@ validate_bundle() {
|
|
|
311
312
|
fi
|
|
312
313
|
|
|
313
314
|
local kind
|
|
314
|
-
kind="$(echo "$fm" |
|
|
315
|
+
kind="$(echo "$fm" | $PYTHON -c "import json,sys; d=json.load(sys.stdin); print(d.get('okf_kind',''))" 2>/dev/null)"
|
|
315
316
|
case "$kind" in
|
|
316
317
|
story-metrics) validate_story_metrics "$file" "$fm" "$name" ;;
|
|
317
318
|
spec-migration) validate_spec_migration "$file" "$fm" "$name" ;;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# specs/**/*.yaml with PyYAML first, and runs the required-key checks
|
|
9
9
|
# against the *parsed* objects, not against grep.
|
|
10
10
|
set -euo pipefail
|
|
11
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
11
12
|
|
|
12
13
|
# Resolve REPO_ROOT from script location
|
|
13
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -18,7 +19,7 @@ SPECS="${1:-$REPO_ROOT/specs}"
|
|
|
18
19
|
err=0
|
|
19
20
|
|
|
20
21
|
# ── Phase 1: real parse pass — every specs/**/*.yaml must be valid YAML ──────
|
|
21
|
-
parse_report="$(
|
|
22
|
+
parse_report="$($PYTHON -c "
|
|
22
23
|
import sys, glob, os
|
|
23
24
|
import yaml
|
|
24
25
|
|
|
@@ -61,7 +62,7 @@ need_key() {
|
|
|
61
62
|
err=1
|
|
62
63
|
return
|
|
63
64
|
fi
|
|
64
|
-
if !
|
|
65
|
+
if ! $PYTHON -c "
|
|
65
66
|
import sys, yaml
|
|
66
67
|
d = yaml.safe_load(open(sys.argv[1])) or {}
|
|
67
68
|
sys.exit(0 if ($py_check) else 1)
|
|
@@ -79,7 +80,7 @@ need_key "$SPECS/execution-status.yaml" "isinstance(d.get('development_status'),
|
|
|
79
80
|
|
|
80
81
|
# ── Phase 3: every epic's `file` reference must exist, read from the parsed object ──
|
|
81
82
|
if [[ -f "$SPECS/release-plan.yaml" ]]; then
|
|
82
|
-
missing_files="$(
|
|
83
|
+
missing_files="$($PYTHON -c "
|
|
83
84
|
import sys, os, yaml
|
|
84
85
|
specs = sys.argv[1]
|
|
85
86
|
d = yaml.safe_load(open(os.path.join(specs, 'release-plan.yaml'))) or {}
|