bigpowers 2.68.4 → 2.68.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.68.4",
3
+ "version": "2.68.5",
4
4
  "description": "75 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "pi-package"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.68.5](https://github.com/danielvm-git/bigpowers/compare/v2.68.4...v2.68.5) (2026-07-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **bug:** python-interpreter-fragility — wire python-env.sh into 29 scripts ([1088995](https://github.com/danielvm-git/bigpowers/commit/10889957c5b4fb07fe2be5f4624a3608461802c7))
7
+
1
8
  ## [2.68.4](https://github.com/danielvm-git/bigpowers/compare/v2.68.3...v2.68.4) (2026-07-06)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.68.4",
3
+ "version": "2.68.5",
4
4
  "description": "73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- python3 -c "
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())
@@ -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: python3 (for YAML-safe state updates)
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
- python3 -c "
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
- python3 -c "
46
+ $PYTHON -c "
46
47
  import yaml
47
48
  from datetime import datetime
48
49
 
@@ -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
- python3 "$REPO_ROOT/scripts/yaml-tools.py" set "$FILE" "$KEY" "$VAL"
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
- python3 -c "
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
- python3 -c "
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
- python3 -c "
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
- python3 -c "
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
- python3 -c "
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 python3 "$(dirname "${BASH_SOURCE[0]}")/lib/blind-spots.py" \
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
- python3 -c "
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
- python3 -c "import json; d=json.load(open('$BP_ROOT/package.json')); print(d.get('version','unknown'))" 2>/dev/null || echo "unknown"
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
- python3 -c "
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
- python3 -c "
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" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
112
- emit_json "$(python3 -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}))")"
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" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
140
- MJ=$(printf '%s\n' "${MARKERS[@]}" | python3 -c "import json,sys; print(json.dumps([l.strip() for l in sys.stdin if l.strip()]))" 2>/dev/null || echo "[]")
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 "$(python3 -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
+ 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
- python3 -c "
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
- python3 - "$RP_MD" "$SPECS" <<'PY'
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
- python3 - "$SCOPE_MD" "$SPECS/requirements/SCOPE_LATEST.yaml" <<'PY'
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
- python3 -c "
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
- python3 - "$ARCHIVE" "$SPECS" <<'PY'
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=$(python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('title',''))" 2>/dev/null || echo "")
47
- status=$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('status','accepted'))" 2>/dev/null || echo "accepted")
48
- decision=$(echo "$fm" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('decision',''))" 2>/dev/null || echo "")
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
- python3 -c "
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
- python3 - "$TARGET" "$BLOCK" <<'PYEOF'
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=$(python3 -c "
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=$(python3 -c "
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
- python3 - "$ENTITIES_JSON" "$EDGES_JSON" "$OUT" << 'PY'
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
- python3 "$TOOL" set "$WORKDIR/sample.yaml" release.version "9.9.9-test"
42
+ $PYTHON "$TOOL" set "$WORKDIR/sample.yaml" release.version "9.9.9-test"
42
43
 
43
44
  set +e
44
- RESULT="$(python3 -c "
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=$(python3 "$TRACE_ENGINE" "$FIXTURE_DIR" "$MATRIX_JSON" "$TRACE_MD" "$OKF_DIR" 1 "" 2>&1)
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
- python3 -c "
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="$(python3 -c "
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" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null)"
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" | python3 -c "
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" | python3 -c "
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" | python3 -c "
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
291
- tgt="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
292
- arch="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('archive_source','false'))" 2>/dev/null)" || arch="false"
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" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('heuristic_map',{})))")"
298
- python3 -c "
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" | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('uncertainty',[])))")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
339
- tgt="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
356
- tgt="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('path',''))")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
384
- key="$(echo "$tjson" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
385
- val="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('value',''))")"
386
- if_missing="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('if_missing','false'))" 2>/dev/null)" || if_missing="false"
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
- python3 "$YAML_TOOLS" set "$REPO_ROOT/$file" "$key" "$val"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
401
- old_key="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('old_key',''))")"
402
- new_key="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('new_key',''))")"
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
- python3 "$YAML_TOOLS" set "$REPO_ROOT/$file" "$new_key" "$old_val"
409
- python3 -c "
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file',''))")"
428
- key="$(echo "$tjson" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('path') or d.get('key',''))")"
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
- python3 -c "
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('source',''))")"
452
- tgt="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('target',''))")"
453
- subs="$(echo "$tjson" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('substitutions',{})))")"
454
- inline_tmpl="$(echo "$tjson" | python3 -c "import json,sys; print(json.load(sys.stdin).get('template',''))")"
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
- python3 -c "
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
- python3 -c "
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" | python3 -c "import json,sys; [print(json.dumps(m)) for m in json.load(sys.stdin)]")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")"
516
- MIG_FILE="$(echo "$mig_json" | python3 -c "import json,sys; print(json.load(sys.stdin)['file'])")"
517
- MIG_TITLE="$(echo "$mig_json" | python3 -c "import json,sys; print(json.load(sys.stdin)['title'])")"
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="$(python3 -c "
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" | python3 -c "import json,sys; [print(json.dumps(t, default=str)) for t in json.load(sys.stdin)]")"
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" | python3 -c "import json,sys; print(json.load(sys.stdin).get('action',''))")"
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="$(python3 -c "
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
- python3 "$YAML_TOOLS" set "$SPECS_DIR/state.yaml" "bigpowers_version" "$INSTALLED"
578
+ $PYTHON "$YAML_TOOLS" set "$SPECS_DIR/state.yaml" "bigpowers_version" "$INSTALLED"
578
579
  log_done "stamp applied"
579
580
  fi
580
581
 
@@ -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 python3 &>/dev/null; then
32
- python3 "$REPO_ROOT/scripts/okf-add-references.py" "$GRAPH_JSON" "$OKF_WIKI_SKILLS"
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
- python3 -c "
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
- python3 - "$OKF_WIKI_SKILLS" "$OKF_INDEX" <<'PYEOF'
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 python3 -c "import yaml; yaml.safe_load(open('$yaml_file'))" 2>/dev/null; then
84
- local grader_type=$(python3 -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('grader',{}).get('type',''))" 2>/dev/null)
85
- local pass_k=$(python3 -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)
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=$(python3 -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('pass_at_k',{}).get('k',3))" 2>/dev/null || echo 3)
239
- PASS_THRESHOLD=$(python3 -c "import yaml; d=yaml.safe_load(open('$yaml_file')); print(d.get('pass_at_k',{}).get('threshold',2))" 2>/dev/null || echo 2)
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 python3 -c "
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'
@@ -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=$(python3 -c 'import time; print(int(time.time() * 1000))')
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=$(( $(python3 -c 'import time; print(int(time.time() * 1000))') - start_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=$(python3 -c 'import time; print(int(time.time() * 1000))')
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=$(( $(python3 -c 'import time; print(int(time.time() * 1000))') - start_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
- python3 - "$BUGS" "$REPO_ROOT" <<'PY'
10
+ $PYTHON - "$BUGS" "$REPO_ROOT" <<'PY'
10
11
  import re
11
12
  import sys
12
13
  from pathlib import Path
@@ -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
- python3 - "$GRAPH_JSON" "$OKF_WIKI_SKILLS" <<'PYEOF'
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 python3 &>/dev/null; then
493
- python3 -c "
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
- python3 - "$OKF_WIKI_SKILLS" "$OKF_WIKI_DIR" "$OKF_INDEX" <<'PYEOF'
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 python3 &>/dev/null; then
666
- if ! python3 "$validate_script" > /dev/null 2>&1; then
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
- python3 "$validate_script" >&2
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
- python3 - "$EPICS" "$OUT" <<'PY'
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
- python3 "$REPO_ROOT/scripts/yaml-tools.py" set \
20
+ $PYTHON "$REPO_ROOT/scripts/yaml-tools.py" set \
20
21
  "$REPO_ROOT/specs/state.yaml" bigpowers_version "$VERSION"
21
22
 
22
- python3 "$REPO_ROOT/scripts/yaml-tools.py" set \
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) ─
@@ -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 python3 "$(dirname "${BASH_SOURCE[0]}")/lib/trace-stories.py" \
72
+ exec $PYTHON "$(dirname "${BASH_SOURCE[0]}")/lib/trace-stories.py" \
72
73
  "$REPO_ROOT" "$MATRIX_JSON" "$TRACE_MD" "$OKF_DIR" "$STRICT" "$MODE"
@@ -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
- python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('source',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('generator',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('commit_range',''))" 2>/dev/null)"
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" | python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
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" | python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d.get('actions_needed',[])))" 2>/dev/null)" \
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" | python3 -c "
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" | python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
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" | python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('category',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('$key',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('gate_status',''))" 2>/dev/null)"
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" | python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('threshold',0))" 2>/dev/null)"
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
- python3 -c "
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('type',''))" 2>/dev/null)"
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" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('okf_kind',''))" 2>/dev/null)"
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="$(python3 -c "
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 ! python3 -c "
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="$(python3 -c "
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 {}