bigpowers 2.73.5 → 2.73.7

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.73.5",
3
+ "version": "2.73.7",
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,17 @@
1
+ ## [2.73.7](https://github.com/danielvm-git/bigpowers/compare/v2.73.6...v2.73.7) (2026-07-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **migrate-spec:** split migrate-version.sh into lib modules under 300-line cap ([#62](https://github.com/danielvm-git/bigpowers/issues/62)) ([11842f2](https://github.com/danielvm-git/bigpowers/commit/11842f297c58d88df24f451316153242e3f4c63a))
7
+
8
+ ## [2.73.6](https://github.com/danielvm-git/bigpowers/compare/v2.73.5...v2.73.6) (2026-07-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **plan-release:** encapsulate G28 boolean logic in check-spec-version-gap ([#61](https://github.com/danielvm-git/bigpowers/issues/61)) ([613e45c](https://github.com/danielvm-git/bigpowers/commit/613e45c00a636f66783a220f35ec4e26916649ee))
14
+
1
15
  ## [2.73.5](https://github.com/danielvm-git/bigpowers/compare/v2.73.4...v2.73.5) (2026-07-06)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.73.5",
3
+ "version": "2.73.7",
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": {
@@ -62,6 +62,35 @@ version_lt() {
62
62
  return 1
63
63
  }
64
64
 
65
+ is_protected_branch() {
66
+ [[ "$1" == "main" || "$1" == "master" ]]
67
+ }
68
+
69
+ on_feature_branch() {
70
+ local branch="$1"
71
+ [[ -n "$branch" ]] || return 1
72
+ is_protected_branch "$branch" && return 1
73
+ return 0
74
+ }
75
+
76
+ yaml_value_set() {
77
+ local value="$1"
78
+ [[ -n "$value" ]] || return 1
79
+ [[ "$value" == "null" || "$value" == "None" ]] && return 1
80
+ return 0
81
+ }
82
+
83
+ has_dirty_specs() {
84
+ [[ -d "$SPECS" ]] && git -C "$PROJECT_DIR" status --porcelain -- specs/ 2>/dev/null | grep -q .
85
+ }
86
+
87
+ is_blocking_active_flow() {
88
+ local flow="$1"
89
+ yaml_value_set "$flow" || return 1
90
+ case "$flow" in build_epic|develop_tdd|fix_bug) return 0 ;; esac
91
+ return 1
92
+ }
93
+
65
94
  find_migrations() {
66
95
  local ver="$1" reg="$BP_ROOT/specs/migrations/registry.okf.md"
67
96
  [[ ! -f "$reg" ]] && { echo "[]"; return; }
@@ -83,13 +112,12 @@ print(json.dumps(out))
83
112
  }
84
113
 
85
114
  check_active_work() {
86
- local branch; branch=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
87
- if [[ -n "$branch" && "$branch" != "main" && "$branch" != "master" ]]; then echo "true"; return; fi
88
- if [[ -d "$SPECS" ]] && git -C "$PROJECT_DIR" status --porcelain -- specs/ 2>/dev/null | grep -q .; then echo "true"; return; fi
89
- local af; af=$(yaml_get "$SPECS/state.yaml" "active_flow")
90
- if [[ -n "$af" && "$af" != "null" && "$af" != "None" ]]; then
91
- case "$af" in build_epic|develop_tdd|fix_bug) echo "true"; return ;; esac
92
- fi
115
+ local branch af
116
+ branch=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
117
+ if on_feature_branch "$branch"; then echo "true"; return; fi
118
+ if has_dirty_specs; then echo "true"; return; fi
119
+ af=$(yaml_get "$SPECS/state.yaml" "active_flow")
120
+ if is_blocking_active_flow "$af"; then echo "true"; return; fi
93
121
  echo "false"
94
122
  }
95
123
 
@@ -103,7 +131,7 @@ fi
103
131
 
104
132
  SV=$(yaml_get "$SPECS/state.yaml" "bigpowers_version")
105
133
 
106
- if [[ -n "$SV" && "$SV" != "null" && "$SV" != "None" ]]; then
134
+ if yaml_value_set "$SV"; then
107
135
  if [[ "$SV" == "$IV" ]]; then
108
136
  emit_json '{"gap":false,"stamp":true,"detected_version":"'"$SV"'","installed_version":"'"$IV"'","detection_method":"stamp","confidence":"high"}'
109
137
  exit 0
@@ -123,7 +151,7 @@ for f in release-plan.yaml execution-status.yaml product/SCOPE_LATEST.yaml metri
123
151
  done
124
152
  if [[ -f "$SPECS/state.yaml" ]]; then
125
153
  ec=$(yaml_get "$SPECS/state.yaml" "epic_cycle.current_step")
126
- if [[ -n "$ec" && "$ec" != "null" && "$ec" != "None" ]]; then
154
+ if yaml_value_set "$ec"; then
127
155
  MARKERS+=("epic_cycle"); [[ "$DV" == "2.0.0" ]] && DV="2.20.0"
128
156
  fi
129
157
  fi
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env bash
2
+ # story: e44s03 e44s04
3
+ # Shared helpers for migrate-version.sh
4
+
5
+ if [ -n "${MIGRATE_VERSION_COMMON_LOADED:-}" ]; then return 0; fi
6
+ MIGRATE_VERSION_COMMON_LOADED=1
7
+
8
+ migrate_usage() {
9
+ cat <<'EOF'
10
+ Usage: scripts/migrate-version.sh [flags]
11
+
12
+ Flags:
13
+ --help Show this message
14
+ --dry-run Compose migration plan and show diff, but make no changes
15
+ --force Skip active-work block detection
16
+ --no-commit Skip auto-commit (still applies transforms and stamps)
17
+ --verbose Show detailed progress for each transform step
18
+ --project <dir> Target project directory (migrations from bigpowers)
19
+ EOF
20
+ exit 0
21
+ }
22
+
23
+ migrate_installed_version() {
24
+ node -e "console.log(require('$BIGPOWERS_ROOT/package.json').version)" 2>/dev/null || echo "unknown"
25
+ }
26
+
27
+ migrate_yaml_get() {
28
+ local file="$1" key="$2"
29
+ $PYTHON -c "
30
+ import yaml, sys
31
+ try:
32
+ d = yaml.safe_load(open('$file'))
33
+ val = d
34
+ for k in '$key'.split('.'):
35
+ if not isinstance(val, dict): sys.exit(1)
36
+ val = val.get(k)
37
+ if val is None: sys.exit(1)
38
+ print(val)
39
+ except: sys.exit(1)
40
+ " 2>/dev/null
41
+ }
42
+
43
+ log_info() { echo " [INFO] $*"; }
44
+ log_warn() { echo " [WARN] $*"; }
45
+ log_done() { echo " [OK] $*"; }
46
+ log_fail() { echo " [FAIL] $*"; }
47
+ log_skip() { echo " [SKIP] $*"; }
48
+
49
+ migrate_yaml_value_set() {
50
+ local value="$1"
51
+ [[ -n "$value" ]] || return 1
52
+ [[ "$value" == "null" ]] && return 1
53
+ return 0
54
+ }
55
+
56
+ migrate_on_feature_branch() {
57
+ local branch="$1"
58
+ [[ -n "$branch" ]] || return 1
59
+ [[ "$branch" == "main" || "$branch" == "master" ]] && return 1
60
+ return 0
61
+ }
62
+
63
+ migrate_specs_clean() {
64
+ git -C "$REPO_ROOT" diff --quiet -- specs/ 2>/dev/null \
65
+ && git -C "$REPO_ROOT" diff --cached --quiet -- specs/ 2>/dev/null
66
+ }
67
+
68
+ migrate_preflight_block() {
69
+ local branch af
70
+ branch="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" || true
71
+ if migrate_on_feature_branch "${branch:-}"; then
72
+ echo "migrate-version: BLOCKED — on non-main branch '$branch'. Use --force to override." >&2
73
+ return 3
74
+ fi
75
+ if ! migrate_specs_clean; then
76
+ echo "migrate-version: BLOCKED — uncommitted changes in specs/. Commit or stash first." >&2
77
+ return 3
78
+ fi
79
+ if [[ -f "$SPECS_DIR/state.yaml" ]]; then
80
+ af="$(migrate_yaml_get "$SPECS_DIR/state.yaml" "active_flow" 2>/dev/null)" || true
81
+ if migrate_yaml_value_set "${af:-}"; then
82
+ echo "migrate-version: BLOCKED — active_flow '$af' is set (mid-epic work). Use --force." >&2
83
+ return 3
84
+ fi
85
+ fi
86
+ return 0
87
+ }
88
+
89
+ migrate_detect_version() {
90
+ local stamped
91
+ DETECTED=""
92
+ if [[ -f "$SPECS_DIR/state.yaml" ]]; then
93
+ stamped="$(migrate_yaml_get "$SPECS_DIR/state.yaml" "bigpowers_version" 2>/dev/null)" || true
94
+ if migrate_yaml_value_set "${stamped:-}"; then
95
+ DETECTED="$stamped"
96
+ fi
97
+ fi
98
+ if [[ -z "$DETECTED" ]]; then
99
+ if [[ -f "$SPECS_DIR/STATE.md" && ! -f "$SPECS_DIR/state.yaml" ]]; then
100
+ DETECTED="1.0.0"
101
+ elif [[ -f "$SPECS_DIR/state.yaml" ]]; then
102
+ if migrate_yaml_get "$SPECS_DIR/state.yaml" "epic_cycle" >/dev/null 2>&1; then
103
+ DETECTED="2.20.0"
104
+ else
105
+ DETECTED="2.0.0"
106
+ fi
107
+ else
108
+ echo "migrate-version: cannot determine spec version" >&2
109
+ return 4
110
+ fi
111
+ fi
112
+ return 0
113
+ }
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ # story: e44s03
3
+ # Migration execution loop for migrate-version.sh
4
+
5
+ if [ -n "${MIGRATE_VERSION_EXECUTE_LOADED:-}" ]; then return 0; fi
6
+ MIGRATE_VERSION_EXECUTE_LOADED=1
7
+
8
+ migrate_execute_migrations() {
9
+ local MIG_LIST mig_json MIG_ID MIG_FILE MIG_TITLE BUNDLE_PATH TRANSFORMS T_LIST tjson action VERIFY_CMDS VERIFY_OK vcmd
10
+
11
+ echo ""
12
+ echo "Applying migrations …"
13
+ SUCCEEDED=()
14
+ FAILED=()
15
+ SKIPPED=()
16
+ SUCCEEDED_COUNT=0
17
+ FAILED_COUNT=0
18
+ SKIPPED_COUNT=0
19
+ UNCERTAINTY_COUNT=0
20
+
21
+ MIG_LIST="$(echo "$MIGRATION_PLAN" | $PYTHON -c "import json,sys; [print(json.dumps(m)) for m in json.load(sys.stdin)]")"
22
+ while IFS= read -r mig_json; do
23
+ [ -z "$mig_json" ] && continue
24
+ MIG_ID="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['id'])")"
25
+ MIG_FILE="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['file'])")"
26
+ MIG_TITLE="$(echo "$mig_json" | $PYTHON -c "import json,sys; print(json.load(sys.stdin)['title'])")"
27
+
28
+ BUNDLE_PATH="$MIGRATIONS_DIR/$MIG_FILE"
29
+ if [ ! -f "$BUNDLE_PATH" ]; then
30
+ log_warn "$MIG_ID: bundle file not found — skipping"
31
+ SKIPPED+=("$MIG_ID:bundle_missing")
32
+ continue
33
+ fi
34
+
35
+ echo ""
36
+ echo "── $MIG_ID: $MIG_TITLE ──"
37
+
38
+ TRANSFORMS="$($PYTHON -c "
39
+ import yaml, json
40
+ with open('$BUNDLE_PATH') as f:
41
+ parts = f.read().split('---')
42
+ fm = yaml.safe_load(parts[1])
43
+ print(json.dumps(fm.get('transforms', []), default=str))
44
+ " 2>/dev/null)"
45
+
46
+ T_LIST="$(echo "$TRANSFORMS" | $PYTHON -c "import json,sys; [print(json.dumps(t, default=str)) for t in json.load(sys.stdin)]")"
47
+ while IFS= read -r tjson; do
48
+ [ -z "$tjson" ] && continue
49
+ action="$(echo "$tjson" | $PYTHON -c "import json,sys; print(json.load(sys.stdin).get('action',''))")"
50
+ apply_transform "$MIG_ID" "$action" "$tjson"
51
+ done <<< "$T_LIST"
52
+
53
+ VERIFY_CMDS="$($PYTHON -c "
54
+ import yaml, json
55
+ with open('$BUNDLE_PATH') as f:
56
+ parts = f.read().split('---')
57
+ fm = yaml.safe_load(parts[1])
58
+ for a in fm.get('actions_needed', []):
59
+ v = a.get('verify', '')
60
+ if v: print(v)
61
+ " 2>/dev/null)"
62
+
63
+ VERIFY_OK=true
64
+ while IFS= read -r vcmd; do
65
+ [ -z "$vcmd" ] && continue
66
+ log_info "verify: $vcmd"
67
+ if eval "$vcmd" 2>&1; then
68
+ log_done "verify passed"
69
+ else
70
+ log_fail "verify failed"
71
+ VERIFY_OK=false
72
+ fi
73
+ done <<< "$VERIFY_CMDS"
74
+
75
+ if [ "$VERIFY_OK" = "true" ]; then
76
+ SUCCEEDED+=("$MIG_ID")
77
+ else
78
+ FAILED+=("$MIG_ID")
79
+ fi
80
+ done <<< "$MIG_LIST"
81
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # story: e44s03
3
+ # Migration plan builder and dry-run preview for migrate-version.sh
4
+
5
+ if [ -n "${MIGRATE_VERSION_PLAN_LOADED:-}" ]; then return 0; fi
6
+ MIGRATE_VERSION_PLAN_LOADED=1
7
+
8
+ migrate_build_plan() {
9
+ echo "migrate-version: building migration plan from $REGISTRY …"
10
+
11
+ MIGRATION_PLAN="$($PYTHON -c "
12
+ import yaml, json, sys
13
+
14
+ with open('$REGISTRY') as f:
15
+ parts = f.read().split('---')
16
+ fm = yaml.safe_load(parts[1])
17
+
18
+ detected = '$DETECTED'
19
+ installed = '$INSTALLED'
20
+
21
+ plan = []
22
+ for m in sorted(fm.get('migrations', []), key=lambda x: x.get('order', 99)):
23
+ sid = m['id']
24
+ since = m.get('since_version', '0')
25
+ order = m.get('order', 99)
26
+ deps = m.get('depends_on', [])
27
+ plan.append({'id': sid, 'file': m.get('file', sid + '.okf.md'),
28
+ 'since_version': since, 'order': order,
29
+ 'depends_on': deps, 'title': m.get('title', sid)})
30
+
31
+ applicable = [m for m in plan if m['since_version'] > detected and m['since_version'] <= installed]
32
+ print(json.dumps(applicable))
33
+ " 2>/dev/null)"
34
+
35
+ MIGRATION_COUNT="$(echo "$MIGRATION_PLAN" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null)"
36
+
37
+ if [ "${MIGRATION_COUNT:-0}" -eq 0 ]; then
38
+ echo "migrate-version: no applicable migrations for $DETECTED → $INSTALLED — stamping version only"
39
+ fi
40
+
41
+ echo "migrate-version: $MIGRATION_COUNT migration(s) to apply"
42
+
43
+ if [ "$VERBOSE" = "true" ]; then
44
+ echo "$MIGRATION_PLAN" | $PYTHON -c "
45
+ import json,sys
46
+ for m in json.load(sys.stdin):
47
+ print(f\" {m['order']}. {m['id']}: {m['title']} (since {m['since_version']})\")
48
+ "
49
+ fi
50
+ }
51
+
52
+ migrate_backup_specs() {
53
+ log_info "Creating backup: $BACKUP_DIR"
54
+ rm -rf "$BACKUP_DIR"
55
+ mkdir -p "$BACKUP_DIR"
56
+ cp -r "$SPECS_DIR"/* "$BACKUP_DIR"/ 2>/dev/null || true
57
+ rm -rf "$BACKUP_DIR/.pre-upgrade-backup" 2>/dev/null || true
58
+ }
59
+
60
+ migrate_print_dry_run() {
61
+ echo ""
62
+ echo "=========================================="
63
+ echo " DRY RUN — no changes will be made"
64
+ echo "=========================================="
65
+ echo ""
66
+ echo "Detected version: $DETECTED"
67
+ echo "Target version: $INSTALLED"
68
+ echo "Migrations:"
69
+ echo "$MIGRATION_PLAN" | $PYTHON -c "
70
+ import json,sys
71
+ for m in json.load(sys.stdin):
72
+ deps = m.get('depends_on', [])
73
+ dep_str = ' (depends on: ' + ', '.join(deps) + ')' if deps else ''
74
+ print(f\" [{m['order']}] {m['id']}: {m['title']}{dep_str}\")
75
+ "
76
+
77
+ echo ""
78
+ echo "Transforms preview:"
79
+ echo "$MIGRATION_PLAN" | $PYTHON -c "
80
+ import json, sys, os
81
+ for m in json.load(sys.stdin):
82
+ bundle_path = os.path.join('$MIGRATIONS_DIR', m['file'])
83
+ if not os.path.exists(bundle_path):
84
+ print(f\" WARN {m['id']}: bundle file not found ({m['file']})\")
85
+ continue
86
+ with open(bundle_path) as f:
87
+ parts = f.read().split('---')
88
+ fm = __import__('yaml').safe_load(parts[1])
89
+ transforms = fm.get('transforms', [])
90
+ print(f\"\n -- {m['id']}: {m['title']} --\")
91
+ for t in transforms:
92
+ action = t.get('action', '?')
93
+ note = t.get('note', '')
94
+ note_str = f' ({note})' if note else ''
95
+ if action == 'convert_md_to_yaml':
96
+ src = t.get('source', '?')
97
+ tgt = t.get('target', '?')
98
+ unc = t.get('uncertainty', [])
99
+ print(f\" {action}: {src} -> {tgt}{note_str}\")
100
+ for u in unc:
101
+ print(f\" GUESS: {u.get('field','?')} - {u.get('reason','?')}\")
102
+ elif action in ('rename_file', 'move_file'):
103
+ print(f\" {action}: {t.get('source','?')} -> {t.get('target','?')}{note_str}\")
104
+ elif action == 'delete_file':
105
+ print(f\" {action}: {t.get('path','?')}{note_str}\")
106
+ elif action in ('set_yaml_key', 'rename_yaml_key', 'delete_yaml_key'):
107
+ print(f\" {action}: {t.get('file','?')} {t.get('key','?') or t.get('old_key','?')} = {t.get('value','?')}{note_str}\")
108
+ elif action == 'create_file_from_template':
109
+ print(f\" {action}: {t.get('source','?')} -> {t.get('target','?')}{note_str}\")
110
+ else:
111
+ print(f\" {action}{note_str}\")
112
+ for a in fm.get('actions_needed', []):
113
+ v = a.get('verify', '')
114
+ if v: print(f\" verify: {v}\")
115
+ " 2>/dev/null
116
+
117
+ echo ""
118
+ echo "Run without --dry-run to apply these migrations."
119
+ }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env bash
2
+ # story: e44s03 e44s04
3
+ # Post-migration stamp, gates, report, and commit for migrate-version.sh
4
+
5
+ if [ -n "${MIGRATE_VERSION_POST_LOADED:-}" ]; then return 0; fi
6
+ MIGRATE_VERSION_POST_LOADED=1
7
+
8
+ migrate_stamp_version() {
9
+ if [ -f "$SPECS_DIR/state.yaml" ]; then
10
+ log_info "Stamping bigpowers_version=$INSTALLED in state.yaml"
11
+ $PYTHON "$YAML_TOOLS" set "$SPECS_DIR/state.yaml" "bigpowers_version" "$INSTALLED"
12
+ log_done "stamp applied"
13
+ fi
14
+ }
15
+
16
+ migrate_run_soft_gates() {
17
+ if [ -f "$SCRIPT_DIR/validate-okf.sh" ]; then
18
+ log_info "Running validate-okf.sh (soft gate) …"
19
+ if bash "$SCRIPT_DIR/validate-okf.sh" --dir "$MIGRATIONS_DIR" 2>&1; then
20
+ log_done "validate-okf: PASS"
21
+ else
22
+ log_warn "validate-okf: some checks failed (soft gate — non-blocking)"
23
+ fi
24
+ else
25
+ log_info "validate-okf.sh not found — skipping soft gate"
26
+ fi
27
+
28
+ if [ -f "$SCRIPT_DIR/validate-specs-yaml.sh" ]; then
29
+ log_info "Running global validate-specs-yaml.sh …"
30
+ if bash "$SCRIPT_DIR/validate-specs-yaml.sh" 2>&1; then
31
+ log_done "validate-specs-yaml: PASS"
32
+ else
33
+ log_fail "validate-specs-yaml: FAIL"
34
+ FAILED+=("validate-specs-yaml")
35
+ fi
36
+ fi
37
+ }
38
+
39
+ migrate_check_staleness() {
40
+ local proj_lines bp_lines delta
41
+ STALE_FILES=()
42
+ BIGPOWERS_CLAUDE="$SCRIPT_DIR/../CLAUDE.md"
43
+ if [ -f "$REPO_ROOT/CLAUDE.md" ] && [ -f "$BIGPOWERS_CLAUDE" ]; then
44
+ proj_lines="$(wc -l < "$REPO_ROOT/CLAUDE.md")"
45
+ bp_lines="$(wc -l < "$BIGPOWERS_CLAUDE")"
46
+ delta=$((bp_lines - proj_lines))
47
+ if [ $delta -gt 20 ]; then
48
+ STALE_FILES+=("CLAUDE.md")
49
+ log_warn "CLAUDE.md: $delta lines behind bigpowers template — consider updating"
50
+ fi
51
+ fi
52
+ if [ -f "$REPO_ROOT/CONVENTIONS.md" ] && [ -f "$SCRIPT_DIR/../CONVENTIONS.md" ]; then
53
+ proj_lines="$(wc -l < "$REPO_ROOT/CONVENTIONS.md")"
54
+ bp_lines="$(wc -l < "$SCRIPT_DIR/../CONVENTIONS.md")"
55
+ delta=$((bp_lines - proj_lines))
56
+ if [ $delta -gt 20 ]; then
57
+ STALE_FILES+=("CONVENTIONS.md")
58
+ log_warn "CONVENTIONS.md: $delta lines behind bigpowers template — consider updating"
59
+ fi
60
+ fi
61
+ }
62
+
63
+ migrate_write_report() {
64
+ local m ISO_DATE FAILED_COUNT
65
+ ISO_DATE="$(date -u +%Y-%m-%dT%H%M%SZ)"
66
+ REPORT_FILE="$SPECS_DIR/migration-report-$ISO_DATE.md"
67
+
68
+ cat > "$REPORT_FILE" <<REPORT
69
+ # Migration Report — $ISO_DATE
70
+
71
+ ## Gap Summary
72
+
73
+ | Field | Value |
74
+ |-------|-------|
75
+ | Detected version | $DETECTED |
76
+ | Installed version | $INSTALLED |
77
+ | Migration count | ${MIGRATION_COUNT:-0} |
78
+
79
+ ## Migrations Applied
80
+
81
+ REPORT
82
+
83
+ for m in "${SUCCEEDED[@]:-}"; do
84
+ [ -z "$m" ] && continue
85
+ echo "- $m PASS" >> "$REPORT_FILE"
86
+ done
87
+ FAILED_COUNT="${#FAILED[@]}"
88
+ if [ "${FAILED_COUNT}" -gt 0 ]; then
89
+ echo "" >> "$REPORT_FILE"
90
+ echo "## Migrations Failed" >> "$REPORT_FILE"
91
+ for m in "${FAILED[@]}"; do
92
+ echo "- $m FAIL" >> "$REPORT_FILE"
93
+ done
94
+ fi
95
+ if [ ${#SKIPPED[@]} -gt 0 ]; then
96
+ echo "" >> "$REPORT_FILE"
97
+ echo "## Skipped" >> "$REPORT_FILE"
98
+ for m in "${SKIPPED[@]}"; do
99
+ echo "- $m" >> "$REPORT_FILE"
100
+ done
101
+ fi
102
+ echo "" >> "$REPORT_FILE"
103
+ echo "## Uncertainties" >> "$REPORT_FILE"
104
+ echo "WARN count: $UNCERTAINTY_COUNT — review specs/ for GUESS markers and adjust if needed." >> "$REPORT_FILE"
105
+ echo "" >> "$REPORT_FILE"
106
+ echo "## Verification" >> "$REPORT_FILE"
107
+ echo "- validate-specs-yaml: ran as final gate" >> "$REPORT_FILE"
108
+
109
+ if [ ${#STALE_FILES[@]} -gt 0 ]; then
110
+ echo "" >> "$REPORT_FILE"
111
+ echo "## Staleness Notice" >> "$REPORT_FILE"
112
+ for f in "${STALE_FILES[@]:-}"; do
113
+ [ -z "$f" ] && continue
114
+ echo "- $f is behind the current bigpowers template" >> "$REPORT_FILE"
115
+ done
116
+ fi
117
+
118
+ echo "" >> "$REPORT_FILE"
119
+ echo "## Next Steps" >> "$REPORT_FILE"
120
+ echo "1. Review the migration report and verify spec correctness" >> "$REPORT_FILE"
121
+ echo "2. Run \`bash scripts/sync-skills.sh\` to regenerate agent artifacts" >> "$REPORT_FILE"
122
+ echo "3. Commit changes if not auto-committed" >> "$REPORT_FILE"
123
+ echo "" >> "$REPORT_FILE"
124
+ echo "## Rollback" >> "$REPORT_FILE"
125
+ echo "To rollback, restore from backup:" >> "$REPORT_FILE"
126
+ echo "\`\`\`bash" >> "$REPORT_FILE"
127
+ echo "rm -rf specs/ && cp -r $BACKUP_DIR/ specs/" >> "$REPORT_FILE"
128
+ echo "\`\`\`" >> "$REPORT_FILE"
129
+
130
+ log_done "Migration report: $REPORT_FILE"
131
+ }
132
+
133
+ migrate_print_summary() {
134
+ echo ""
135
+ echo "=========================================="
136
+ echo " Migration complete: $DETECTED -> $INSTALLED"
137
+ echo "=========================================="
138
+ echo " Applied: ${#SUCCEEDED[@]}"
139
+ echo " Failed: ${#FAILED[@]}"
140
+ echo " Skipped: ${#SKIPPED[@]}"
141
+ echo " WARN: $UNCERTAINTY_COUNT"
142
+ [ ${#STALE_FILES[@]} -gt 0 ] && echo " Stale: ${STALE_FILES[*]:-}"
143
+ echo " Report: $REPORT_FILE"
144
+ echo "=========================================="
145
+ echo " Remember: run 'bash scripts/sync-skills.sh'"
146
+ echo "=========================================="
147
+ }
148
+
149
+ migrate_auto_commit() {
150
+ local COMMIT_MSG
151
+ if [ "$SKIP_COMMIT" != "true" ] && [ "${MIGRATION_COUNT:-0}" -gt 0 ]; then
152
+ log_info "Auto-committing migration …"
153
+ git -C "$REPO_ROOT" add specs/ 2>/dev/null || true
154
+ COMMIT_MSG="migrate-version: $DETECTED -> $INSTALLED (${MIGRATION_COUNT:-0} migration(s))"
155
+ if git -C "$REPO_ROOT" commit -m "$COMMIT_MSG" 2>/dev/null; then
156
+ log_done "committed: $COMMIT_MSG"
157
+ else
158
+ log_info "nothing to commit (already clean)"
159
+ fi
160
+ fi
161
+ }
162
+
163
+ migrate_finalize() {
164
+ migrate_stamp_version
165
+ migrate_run_soft_gates
166
+ migrate_check_staleness
167
+ migrate_write_report
168
+ migrate_print_summary
169
+ migrate_auto_commit
170
+ }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ # story: e44s03 e44s04
3
+ # migrate-version orchestrator — sources lib modules and runs the migration pipeline
4
+
5
+ if [ -n "${MIGRATE_VERSION_RUN_LOADED:-}" ]; then return 0; fi
6
+ MIGRATE_VERSION_RUN_LOADED=1
7
+
8
+ _LIB="$(dirname "${BASH_SOURCE[0]}")"
9
+ source "$_LIB/migrate-version-common.sh"
10
+ source "$_LIB/migrate-version-plan.sh"
11
+ source "$_LIB/migrate-version-transforms.sh"
12
+ source "$_LIB/migrate-version-execute.sh"
13
+ source "$_LIB/migrate-version-post.sh"
14
+
15
+ migrate_version_main() {
16
+ if [ "$FORCE" != "true" ]; then
17
+ migrate_preflight_block || exit $?
18
+ fi
19
+
20
+ if [ ! -d "$SPECS_DIR" ]; then
21
+ echo "migrate-version: no specs/ directory found" >&2
22
+ exit 2
23
+ fi
24
+ if [ ! -f "$REGISTRY" ]; then
25
+ echo "migrate-version: migration registry not found at $REGISTRY" >&2
26
+ exit 4
27
+ fi
28
+
29
+ INSTALLED="$(migrate_installed_version)"
30
+ migrate_detect_version || exit $?
31
+
32
+ echo "migrate-version: detected=$DETECTED installed=$INSTALLED"
33
+
34
+ if [ "$DETECTED" = "$INSTALLED" ]; then
35
+ echo "migrate-version: no version gap — specs are current"
36
+ exit 0
37
+ fi
38
+
39
+ migrate_build_plan
40
+
41
+ if [ "$DRY_RUN" != "true" ]; then
42
+ migrate_backup_specs
43
+ fi
44
+
45
+ if [ "$DRY_RUN" = "true" ]; then
46
+ migrate_print_dry_run
47
+ exit 0
48
+ fi
49
+
50
+ migrate_execute_migrations
51
+ migrate_finalize
52
+ exit 0
53
+ }