bigpowers 2.58.7 → 2.58.8

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.58.7",
3
+ "version": "2.58.8",
4
4
  "description": "73 skills — 72 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -12,8 +12,9 @@ Welcome to **bigpowers** — a lifecycle of **70** agent skills for production-r
12
12
  ## Install
13
13
 
14
14
  ```bash
15
- npx bigpowers # one-shot setup
16
- npm install -g bigpowers # global install, then run: bigpowers
15
+ npx bigpowers setup # one-shot setup
16
+ npm install -g bigpowers # global install
17
+ bigpowers setup # sync skills + link to your tools
17
18
  ```
18
19
 
19
20
  From source (contributors): `git clone` → `npm install` or `bash scripts/install.sh`.
@@ -14,8 +14,9 @@ Welcome to **bigpowers** — a lifecycle of **70** agent skills for production-r
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- npx bigpowers # one-shot setup
18
- npm install -g bigpowers # global install, then run: bigpowers
17
+ npx bigpowers setup # one-shot setup
18
+ npm install -g bigpowers # global install
19
+ bigpowers setup # sync skills + link to your tools
19
20
  ```
20
21
 
21
22
  From source (contributors): `git clone` → `npm install` or `bash scripts/install.sh`.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.58.8](https://github.com/danielvm-git/bigpowers/compare/v2.58.7...v2.58.8) (2026-07-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **release-pipeline:** stop YAML round-trip corruption, recover specs ([db09bbf](https://github.com/danielvm-git/bigpowers/commit/db09bbf34ed10092bd874cb7ce46af9de3fce563))
7
+
1
8
  ## [2.58.7](https://github.com/danielvm-git/bigpowers/compare/v2.58.6...v2.58.7) (2026-07-03)
2
9
 
3
10
 
package/CLAUDE.md CHANGED
@@ -13,7 +13,7 @@ Stack: Markdown / Bash (documentation-based; skills integrate with Claude Code,
13
13
 
14
14
  | Action | Command |
15
15
  |---------|---------|
16
- | Install | `npx bigpowers` or `npm install -g bigpowers` |
16
+ | Install | `npm install -g bigpowers && bigpowers setup` |
17
17
  | Run | `bash scripts/sync-skills.sh` |
18
18
  | Test | N/A (documentation project) |
19
19
  | Build | `bash scripts/install.sh` (from source) |
package/CONTRIBUTING.md CHANGED
@@ -9,7 +9,7 @@ Be respectful, constructive, and patient. We're building tools for developers
9
9
  ## Before You Start
10
10
 
11
11
  1. **Read the docs.** Start with [`README.md`](README.md), [`CLAUDE.md`](CLAUDE.md), and [`CONVENTIONS.md`](CONVENTIONS.md). These define the project's architecture, conventions, and engineering standards.
12
- 2. **Run bigpowers.** Install with `npx bigpowers` and walk through the experience as a user before contributing as a developer.
12
+ 2. **Run bigpowers.** Install with `npm install -g bigpowers && bigpowers setup` and walk through the experience as a user before contributing as a developer.
13
13
  3. **Understand the lifecycle.** bigpowers follows a 6-phase methodology: Discover → Elaborate → Plan → Build → Verify → Release. Read [`docs/WORKFLOW-SOP-v2.md`](docs/WORKFLOW-SOP-v2.md) for the full SOP.
14
14
 
15
15
  ## How to Contribute
@@ -66,8 +66,10 @@ Documentation is a first-class contribution. You don't need to write code to hel
66
66
  git clone https://github.com/danielvm-git/bigpowers.git
67
67
  cd bigpowers
68
68
 
69
- # Install dependencies + run postinstall sync
69
+ # Install dependencies
70
70
  npm install
71
+ # Sync skills and set up symlinks
72
+ bash scripts/install.sh
71
73
 
72
74
  # Or manually:
73
75
  bash scripts/install.sh
package/GEMINI.md CHANGED
@@ -13,7 +13,7 @@ Stack: Markdown / Bash (documentation-based; skills integrate with Claude Code,
13
13
 
14
14
  | Action | Command |
15
15
  |---------|---------|
16
- | Install | `npx bigpowers` or `npm install -g bigpowers` |
16
+ | Install | `npm install -g bigpowers && bigpowers setup` |
17
17
  | Run | `bash scripts/sync-skills.sh` |
18
18
  | Test | N/A (documentation project) |
19
19
  | Build | `bash scripts/install.sh` (from source) |
package/README.md CHANGED
@@ -36,12 +36,12 @@ After installing, ask your agent to run the `using-bigpowers` skill — it is th
36
36
  ### npm (recommended)
37
37
 
38
38
  ```bash
39
- # One-shot setup — downloads, syncs artifacts, and links skills to your tools
40
- npx bigpowers
41
-
42
- # Or install globally and run the setup command anytime
39
+ # Global install (no lifecycle scripts — npm v10+ safe)
43
40
  npm install -g bigpowers
44
- bigpowers
41
+ bigpowers setup # runs sync + install, links skills to your tools
42
+
43
+ # Or one-shot with npx
44
+ npx bigpowers setup
45
45
  ```
46
46
 
47
47
  Both commands sync skill artifacts and link them to Claude Code, Gemini CLI, and Cursor (see [Prerequisites](#-prerequisites)).
@@ -50,10 +50,8 @@ Both commands sync skill artifacts and link them to Claude Code, Gemini CLI, and
50
50
 
51
51
  ```bash
52
52
  git clone https://github.com/danielvm-git/bigpowers.git && cd bigpowers
53
- npm install # runs postinstall: sync + link
54
- # or manually:
53
+ npm install
55
54
  bash scripts/install.sh
56
- npm run sync
57
55
  ```
58
56
 
59
57
  ---
@@ -253,7 +251,7 @@ Or add manually to `.claude/settings.json`:
253
251
 
254
252
  ```bash
255
253
  npm update -g bigpowers
256
- bigpowers # re-sync and refresh symlinks
254
+ bigpowers update # re-sync and refresh symlinks
257
255
  ```
258
256
 
259
257
  **git clone:**
@@ -261,6 +259,7 @@ bigpowers # re-sync and refresh symlinks
261
259
  ```bash
262
260
  git pull
263
261
  npm run sync
262
+ bash scripts/install.sh
264
263
  ```
265
264
 
266
265
  *Install uses symlinks — re-running setup refreshes links without duplicating files.*
@@ -283,9 +282,9 @@ bash scripts/install.sh --uninstall
283
282
  ### Reinstall
284
283
 
285
284
  ```bash
286
- npx bigpowers
285
+ npx bigpowers setup
287
286
  # or, if installed globally:
288
- bigpowers
287
+ bigpowers setup
289
288
  ```
290
289
 
291
290
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.58.7",
3
+ "version": "2.58.8",
4
4
  "description": "72 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # golden-g08-anti-vacuity.sh — validate-specs-yaml.sh Anti-Vacuity Self-Test (G-08)
3
+ #
4
+ # Proves that validate-specs-yaml.sh CAN detect structurally corrupt YAML
5
+ # (BUG-2026-07-03-validate-specs-no-real-parser bug class). It is fed a
6
+ # fixture where specs/verifications/fixtures/corrupt-yaml/release-plan.yaml
7
+ # is deliberately flattened/corrupt (matching the real yaml-roundtrip
8
+ # corruption pattern) while still containing every string the grep-only
9
+ # checks look for (^epics:, version:, ^release:). A validator that only
10
+ # greps for those strings passes vacuously; a validator that really parses
11
+ # the YAML must FAIL.
12
+ #
13
+ # Mirrors scripts/golden-g07-negative-path.sh's fixture + non-zero-exit
14
+ # pattern.
15
+ #
16
+ # Usage: bash scripts/golden-g08-anti-vacuity.sh
17
+ # Exit 0: validate-specs-yaml.sh correctly detected the corrupt fixture
18
+ # Exit 1: validate-specs-yaml.sh passed vacuously on the corrupt fixture
19
+
20
+ set -euo pipefail
21
+
22
+ RED='\033[0;31m'
23
+ GREEN='\033[0;32m'
24
+ NC='\033[0m'
25
+
26
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
27
+ FIXTURE_DIR="$REPO_ROOT/specs/verifications/fixtures/corrupt-yaml"
28
+ VALIDATOR="$REPO_ROOT/scripts/validate-specs-yaml.sh"
29
+
30
+ echo "=== G-08: Anti-Vacuity Self-Test (validate-specs-yaml.sh) ==="
31
+ echo "Fixture: $FIXTURE_DIR"
32
+ echo ""
33
+
34
+ if [[ ! -f "$FIXTURE_DIR/release-plan.yaml" ]]; then
35
+ echo "ERROR: fixture not found: $FIXTURE_DIR/release-plan.yaml"
36
+ exit 1
37
+ fi
38
+
39
+ set +e
40
+ actual_output=$(bash "$VALIDATOR" "$FIXTURE_DIR" 2>&1)
41
+ actual_exit=$?
42
+ set -e
43
+
44
+ echo "$actual_output"
45
+ echo ""
46
+
47
+ if [[ $actual_exit -ne 0 ]]; then
48
+ echo -e "${GREEN}PASS${NC} validate-specs-yaml.sh correctly detected the corrupt fixture (exit $actual_exit)"
49
+ echo "G-08: PASS"
50
+ exit 0
51
+ else
52
+ echo -e "${RED}FAIL${NC} validate-specs-yaml.sh passed vacuously on a structurally corrupt fixture (exit 0)"
53
+ echo "G-08: FAIL — fail-open risk (same bug class as GAP-1 / BUG-2026-07-03-validate-specs-no-real-parser)"
54
+ exit 1
55
+ fi
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # golden-g09-yaml-roundtrip.sh — yaml-tools.py Lossless Round-Trip Self-Test (G-09)
3
+ #
4
+ # Proves yaml-tools.py's `set` command preserves structure it doesn't touch
5
+ # (BUG-2026-07-03-yaml-roundtrip-corruption bug class). The old hand-rolled
6
+ # parser/dumper had no concept of YAML lists or block scalars and flattened
7
+ # both on every write. This feeds `set` a fixture containing an epics[] list
8
+ # (nested dicts + a flow-style list) and a block scalar (`note: >`), changes
9
+ # one unrelated field, and asserts:
10
+ # 1. the file still parses as valid YAML afterward
11
+ # 2. the epics[] list still has the same length and nested keys
12
+ # 3. fields untouched by the `set` call keep their original values
13
+ # 4. the targeted field actually changed
14
+ #
15
+ # Usage: bash scripts/golden-g09-yaml-roundtrip.sh
16
+ # Exit 0: round trip is lossless
17
+ # Exit 1: round trip destroyed structure (corruption bug is back)
18
+
19
+ set -euo pipefail
20
+
21
+ RED='\033[0;31m'
22
+ GREEN='\033[0;32m'
23
+ NC='\033[0m'
24
+
25
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
26
+ FIXTURE="$REPO_ROOT/specs/verifications/fixtures/yaml-roundtrip/sample.yaml"
27
+ TOOL="$REPO_ROOT/scripts/yaml-tools.py"
28
+
29
+ echo "=== G-09: yaml-tools.py Lossless Round-Trip Self-Test ==="
30
+
31
+ if [[ ! -f "$FIXTURE" ]]; then
32
+ echo "ERROR: fixture not found: $FIXTURE"
33
+ exit 1
34
+ fi
35
+
36
+ WORKDIR="$(mktemp -d)"
37
+ trap 'rm -rf "$WORKDIR"' EXIT
38
+ cp "$FIXTURE" "$WORKDIR/sample.yaml"
39
+
40
+ python3 "$TOOL" set "$WORKDIR/sample.yaml" release.version "9.9.9-test"
41
+
42
+ set +e
43
+ RESULT="$(python3 -c "
44
+ import sys, yaml
45
+
46
+ try:
47
+ with open(sys.argv[1]) as f:
48
+ d = yaml.safe_load(f)
49
+ except yaml.YAMLError as e:
50
+ print('FAIL: file no longer parses as YAML:', str(e).splitlines()[0])
51
+ sys.exit(1)
52
+
53
+ errors = []
54
+
55
+ if d.get('release', {}).get('version') != '9.9.9-test':
56
+ errors.append('targeted field release.version was not updated')
57
+
58
+ epics = d.get('epics')
59
+ if not isinstance(epics, list) or len(epics) != 2:
60
+ errors.append(f'epics[] list destroyed: expected 2 items, got {epics!r}')
61
+ else:
62
+ if epics[0].get('id') != 'e19' or epics[0].get('wsjf') != 4.7:
63
+ errors.append('epics[0] fields not preserved')
64
+ if 'note' not in epics[0] or 'Delivered' not in epics[0].get('note', ''):
65
+ errors.append('epics[0].note (block scalar) not preserved')
66
+ if epics[1].get('id') != 'e40' or epics[1].get('depends_on') != ['e39', 'e28']:
67
+ errors.append('epics[1] fields (incl. flow-style list) not preserved')
68
+
69
+ if d.get('release', {}).get('codename') != 'Deepening':
70
+ errors.append('release.codename (untouched sibling field) not preserved')
71
+
72
+ if d.get('bigpowers_version') != '2.58.7':
73
+ errors.append('bigpowers_version (untouched top-level field) not preserved')
74
+
75
+ if errors:
76
+ for e in errors:
77
+ print('FAIL:', e)
78
+ sys.exit(1)
79
+
80
+ print('OK: round trip is lossless')
81
+ sys.exit(0)
82
+ " "$WORKDIR/sample.yaml")"
83
+ EXIT=$?
84
+ set -e
85
+
86
+ echo "$RESULT"
87
+
88
+ if [[ $EXIT -eq 0 ]]; then
89
+ echo -e "${GREEN}PASS${NC} yaml-tools.py round trip is lossless"
90
+ echo "G-09: PASS"
91
+ exit 0
92
+ else
93
+ echo -e "${RED}FAIL${NC} yaml-tools.py round trip destroyed structure"
94
+ echo "G-09: FAIL — same bug class as BUG-2026-07-03-yaml-roundtrip-corruption"
95
+ exit 1
96
+ fi
@@ -242,6 +242,6 @@ else
242
242
  if [[ -d "$REPO_ROOT/.git" ]]; then
243
243
  echo " git pull && ./scripts/sync-skills.sh"
244
244
  else
245
- echo " npm update -g bigpowers && bigpowers"
245
+ echo " npm update -g bigpowers && bigpowers update"
246
246
  fi
247
247
  fi
@@ -32,6 +32,9 @@ GATES=(
32
32
  "compliance:npm run compliance:false"
33
33
  "g04-selftest:bash scripts/golden-g04-selftest.sh:true"
34
34
  "g07-negative-path:bash scripts/golden-g07-negative-path.sh:false"
35
+ "g08-anti-vacuity:bash scripts/golden-g08-anti-vacuity.sh:false"
36
+ "g09-yaml-roundtrip:bash scripts/golden-g09-yaml-roundtrip.sh:false"
37
+ "specs-parse:bash scripts/validate-specs-yaml.sh:false"
35
38
  )
36
39
 
37
40
  GREEN='\033[0;32m'
@@ -1,38 +1,98 @@
1
1
  #!/usr/bin/env bash
2
- # validate-specs-yaml.sh — required keys for state, release-plan, execution-status
2
+ # validate-specs-yaml.sh — real-parse gate + required keys for state,
3
+ # release-plan, execution-status.
4
+ #
5
+ # BUG-2026-07-03-validate-specs-no-real-parser: this validator used to only
6
+ # grep for key strings, so a structurally flattened/corrupt YAML file that
7
+ # still contained those strings passed vacuously. It now really parses every
8
+ # specs/**/*.yaml with PyYAML first, and runs the required-key checks
9
+ # against the *parsed* objects, not against grep.
3
10
  set -euo pipefail
4
11
  REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
12
  SPECS="${1:-$REPO_ROOT/specs}"
6
13
 
7
14
  err=0
8
- need() {
9
- local file="$1" pattern="$2" msg="$3"
15
+
16
+ # ── Phase 1: real parse pass — every specs/**/*.yaml must be valid YAML ──────
17
+ parse_report="$(python3 -c "
18
+ import sys, glob, os
19
+ import yaml
20
+
21
+ specs = sys.argv[1]
22
+ bad = []
23
+ for f in sorted(glob.glob(os.path.join(specs, '**', '*.yaml'), recursive=True)):
24
+ # Test fixtures (e.g. specs/verifications/fixtures/corrupt-yaml/) hold
25
+ # deliberately-broken YAML for the G-07/G-08 negative-path golden checks.
26
+ # Skip them when scanning a real specs/ tree — but still validate them
27
+ # when a fixture dir is itself the target (G-08 invokes this script
28
+ # with the fixture dir as $SPECS directly).
29
+ rel = os.path.relpath(f, specs)
30
+ if rel.startswith('verifications' + os.sep + 'fixtures' + os.sep):
31
+ continue
32
+ try:
33
+ yaml.safe_load(open(f))
34
+ except yaml.YAMLError as e:
35
+ bad.append((f, str(e).splitlines()[0] if str(e) else type(e).__name__))
36
+
37
+ for f, msg in bad:
38
+ print(f'PARSE ERROR: {f}: {msg}')
39
+
40
+ sys.exit(1 if bad else 0)
41
+ " "$SPECS")" || err=1
42
+
43
+ if [[ -n "$parse_report" ]]; then
44
+ echo "$parse_report"
45
+ fi
46
+
47
+ if [[ "$err" -ne 0 ]]; then
48
+ echo "validate-specs-yaml: FAIL — one or more specs/**/*.yaml files are not valid YAML"
49
+ exit 1
50
+ fi
51
+
52
+ # ── Phase 2: required-key checks against the parsed objects, not grep ────────
53
+ need_key() {
54
+ local file="$1" py_check="$2" msg="$3"
10
55
  if [[ ! -f "$file" ]]; then
11
56
  echo "missing: $file"
12
57
  err=1
13
58
  return
14
59
  fi
15
- if ! grep -qE "$pattern" "$file"; then
60
+ if ! python3 -c "
61
+ import sys, yaml
62
+ d = yaml.safe_load(open(sys.argv[1])) or {}
63
+ sys.exit(0 if ($py_check) else 1)
64
+ " "$file" 2>/dev/null; then
16
65
  echo "$file: $msg"
17
66
  err=1
18
67
  fi
19
68
  }
20
69
 
21
- need "$SPECS/state.yaml" '^active_flow:' 'missing active_flow'
22
- need "$SPECS/release-plan.yaml" '^release:' 'missing release block'
23
- need "$SPECS/release-plan.yaml" 'version:' 'missing release.version'
24
- need "$SPECS/release-plan.yaml" '^epics:' 'missing epics list'
25
- need "$SPECS/execution-status.yaml" '^development_status:' 'missing development_status'
70
+ need_key "$SPECS/state.yaml" "isinstance(d.get('active_flow'), str)" 'missing active_flow'
71
+ need_key "$SPECS/release-plan.yaml" "isinstance(d.get('release'), dict)" 'missing release block'
72
+ need_key "$SPECS/release-plan.yaml" "isinstance(d.get('release'), dict) and 'version' in d['release']" 'missing release.version'
73
+ need_key "$SPECS/release-plan.yaml" "isinstance(d.get('epics'), list) and len(d['epics']) > 0" 'missing epics list'
74
+ need_key "$SPECS/execution-status.yaml" "isinstance(d.get('development_status'), dict)" 'missing development_status'
26
75
 
76
+ # ── Phase 3: every epic's `file` reference must exist, read from the parsed object ──
27
77
  if [[ -f "$SPECS/release-plan.yaml" ]]; then
28
- while IFS= read -r f; do
29
- [[ -z "$f" ]] && continue
30
- path="$SPECS/$f"
31
- if [[ ! -f "$path" ]]; then
78
+ missing_files="$(python3 -c "
79
+ import sys, os, yaml
80
+ specs = sys.argv[1]
81
+ d = yaml.safe_load(open(os.path.join(specs, 'release-plan.yaml'))) or {}
82
+ for epic in d.get('epics') or []:
83
+ if not isinstance(epic, dict):
84
+ continue
85
+ f = epic.get('file')
86
+ if f and not os.path.isfile(os.path.join(specs, f)):
87
+ print(os.path.join(specs, f))
88
+ " "$SPECS")"
89
+ if [[ -n "$missing_files" ]]; then
90
+ while IFS= read -r path; do
91
+ [[ -z "$path" ]] && continue
32
92
  echo "release-plan: missing epic file $path"
33
93
  err=1
34
- fi
35
- done < <(grep -E '^\s+file:' "$SPECS/release-plan.yaml" | sed 's/.*file:[[:space:]]*//; s/^"//; s/"$//')
94
+ done <<< "$missing_files"
95
+ fi
36
96
  fi
37
97
 
38
98
  if [[ "$err" -ne 0 ]]; then
@@ -1,78 +1,29 @@
1
1
  #!/usr/bin/env python3
2
- """Minimal YAML helpers for bigpowers specs (no external deps)."""
2
+ """YAML helpers for bigpowers specs.
3
+
4
+ BUG-2026-07-03-yaml-roundtrip-corruption: this used to hand-roll a
5
+ "_parse_simple_yaml" reader with no concept of YAML lists or block
6
+ scalars, and re-serialize with a matching hand-rolled dumper. Any file
7
+ with a `- item` sequence (every epics[] list, every stories[] list) or
8
+ a block scalar (`note: >`) got silently flattened and destroyed on the
9
+ very first `set` call. It now uses PyYAML's safe_load/safe_dump for a
10
+ real, information-preserving round trip.
11
+ """
3
12
  from __future__ import annotations
4
13
 
5
- import re
6
14
  import sys
7
15
  from pathlib import Path
8
16
  from typing import Any
9
17
 
10
-
11
- def _parse_simple_yaml(text: str) -> dict[str, Any]:
12
- """Parse flat and one-level-nested YAML (no lists of objects)."""
13
- root: dict[str, Any] = {}
14
- stack: list[tuple[int, dict[str, Any]]] = [(0, root)]
15
- for raw in text.splitlines():
16
- if not raw.strip() or raw.strip().startswith("#"):
17
- continue
18
- indent = len(raw) - len(raw.lstrip())
19
- line = raw.strip()
20
- if ":" not in line:
21
- continue
22
- key, _, val = line.partition(":")
23
- key = key.strip()
24
- val = val.strip()
25
- while stack and indent < stack[-1][0]:
26
- stack.pop()
27
- cur = stack[-1][1]
28
- if val == "":
29
- nxt: dict[str, Any] = {}
30
- cur[key] = nxt
31
- stack.append((indent + 2, nxt))
32
- else:
33
- if val in ("true", "false"):
34
- cur[key] = val == "true"
35
- elif val.startswith('"') and val.endswith('"'):
36
- cur[key] = val[1:-1]
37
- elif val.startswith("'") and val.endswith("'"):
38
- cur[key] = val[1:-1]
39
- else:
40
- try:
41
- if "." in val:
42
- cur[key] = float(val)
43
- else:
44
- cur[key] = int(val)
45
- except ValueError:
46
- cur[key] = val
47
- return root
48
-
49
-
50
- def _dump_value(val: Any, indent: int) -> str:
51
- sp = " " * indent
52
- if isinstance(val, dict):
53
- lines = []
54
- for k, v in val.items():
55
- if isinstance(v, dict):
56
- lines.append(f"{sp}{k}:")
57
- lines.append(_dump_value(v, indent + 2).rstrip())
58
- elif isinstance(v, bool):
59
- lines.append(f"{sp}{k}: {'true' if v else 'false'}")
60
- elif isinstance(v, (int, float)):
61
- lines.append(f"{sp}{k}: {v}")
62
- else:
63
- s = str(v).replace('"', '\\"')
64
- lines.append(f'{sp}{k}: "{s}"')
65
- return "\n".join(lines) + "\n"
66
- return f"{sp}{val}\n"
67
-
68
-
69
- def dump_yaml(data: dict[str, Any]) -> str:
70
- return _dump_value(data, 0)
18
+ import yaml
71
19
 
72
20
 
73
21
  def set_path(path: Path, dotted_key: str, value: str) -> None:
22
+ """Surgically set one dotted-path key, preserving everything else in the file."""
74
23
  text = path.read_text(encoding="utf-8") if path.exists() else ""
75
- data = _parse_simple_yaml(text) if text.strip() else {}
24
+ loaded: Any = yaml.safe_load(text) if text.strip() else {}
25
+ data: dict[str, Any] = loaded if isinstance(loaded, dict) else {}
26
+
76
27
  parts = dotted_key.split(".")
77
28
  cur: Any = data
78
29
  for part in parts[:-1]:
@@ -80,6 +31,7 @@ def set_path(path: Path, dotted_key: str, value: str) -> None:
80
31
  cur[part] = {}
81
32
  cur = cur[part]
82
33
  last = parts[-1]
34
+
83
35
  if value.lower() in ("true", "false"):
84
36
  cur[last] = value.lower() == "true"
85
37
  else:
@@ -90,16 +42,24 @@ def set_path(path: Path, dotted_key: str, value: str) -> None:
90
42
  cur[last] = float(value)
91
43
  except ValueError:
92
44
  cur[last] = value.strip('"').strip("'")
45
+
93
46
  path.parent.mkdir(parents=True, exist_ok=True)
94
- path.write_text(dump_yaml(data), encoding="utf-8")
47
+ # sort_keys=False preserves author-intended ordering (e.g. epics[]
48
+ # sequencing by WSJF priority) instead of alphabetizing every key.
49
+ path.write_text(
50
+ yaml.safe_dump(data, sort_keys=False, allow_unicode=True, width=100),
51
+ encoding="utf-8",
52
+ )
95
53
 
96
54
 
97
55
  def validate_file(path: Path, required_keys: list[str]) -> list[str]:
98
- errors: list[str] = []
99
56
  if not path.exists():
100
57
  return [f"missing: {path}"]
101
- text = path.read_text(encoding="utf-8")
102
- data = _parse_simple_yaml(text)
58
+ try:
59
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
60
+ except yaml.YAMLError as e:
61
+ return [f"{path}: PARSE ERROR: {str(e).splitlines()[0]}"]
62
+ errors: list[str] = []
103
63
  for key in required_keys:
104
64
  parts = key.split(".")
105
65
  cur: Any = data
@@ -14,8 +14,9 @@ Welcome to **bigpowers** — a lifecycle of **70** agent skills for production-r
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- npx bigpowers # one-shot setup
18
- npm install -g bigpowers # global install, then run: bigpowers
17
+ npx bigpowers setup # one-shot setup
18
+ npm install -g bigpowers # global install
19
+ bigpowers setup # sync skills + link to your tools
19
20
  ```
20
21
 
21
22
  From source (contributors): `git clone` → `npm install` or `bash scripts/install.sh`.
package/skills-lock.json CHANGED
@@ -328,7 +328,7 @@
328
328
  },
329
329
  "using-bigpowers": {
330
330
  "description": "One-time bootstrap that introduces the bigpowers skills system, the PMBOK lifecycle arc, and tells you which skill to call first for your situation. Use when starting with bigpowers for the first time, when user asks \"where do I start?\", or when the skills system needs to be explained.",
331
- "sha256": "e467775c61f943c8",
331
+ "sha256": "33f6d163b536e87d",
332
332
  "path": "skills/using-bigpowers/SKILL.md"
333
333
  },
334
334
  "validate-contracts": {