bigpowers 2.59.0 → 2.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.pi/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.59.0",
3
+ "version": "2.59.1",
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"
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.59.1](https://github.com/danielvm-git/bigpowers/compare/v2.59.0...v2.59.1) (2026-07-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **trace:** replace hand-rolled YAML parser, add floor assertion, G-10 ([67f7c71](https://github.com/danielvm-git/bigpowers/commit/67f7c71a74deefac2fea461f161c9656b751b387))
7
+
1
8
  # [2.59.0](https://github.com/danielvm-git/bigpowers/compare/v2.58.9...v2.59.0) (2026-07-04)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.59.0",
3
+ "version": "2.59.1",
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,59 @@
1
+ #!/usr/bin/env bash
2
+ # golden-g10-trace-anti-vacuity.sh — Trace Engine Anti-Vacuity Self-Test (G-10)
3
+ #
4
+ # Proves that trace-stories.py CAN detect a degenerate matrix (floor assertion).
5
+ # It is fed a fixture where specs/release-plan.yaml has valid YAML but produces
6
+ # 0 stories — the --strict floor assertion MUST catch this and exit 2.
7
+ #
8
+ # If --strict exits 0 or 1 on this fixture, the trace engine is failing-open
9
+ # (same bug class as BUG-2026-07-03-trace-engine-vacuous-gate).
10
+ #
11
+ # Usage: bash scripts/golden-g10-trace-anti-vacuity.sh
12
+ # Exit 0: trace-stories.py correctly detected the degenerate fixture (exit 2)
13
+ # Exit 1: trace-stories.py passed vacuously on the degenerate fixture (fail-open risk)
14
+
15
+ set -euo pipefail
16
+
17
+ RED='\033[0;31m'
18
+ GREEN='\033[0;32m'
19
+ NC='\033[0m'
20
+
21
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
22
+ FIXTURE_DIR="$REPO_ROOT/specs/verifications/fixtures/trace-vacuous"
23
+ TRACE_ENGINE="$REPO_ROOT/scripts/lib/trace-stories.py"
24
+
25
+ echo "=== G-10: Trace Engine Anti-Vacuity Self-Test ==="
26
+ echo "Fixture: $FIXTURE_DIR"
27
+ echo ""
28
+
29
+ if [[ ! -f "$FIXTURE_DIR/specs/release-plan.yaml" ]]; then
30
+ echo "ERROR: fixture not found: $FIXTURE_DIR/specs/release-plan.yaml"
31
+ exit 1
32
+ fi
33
+
34
+ MATRIX_JSON=$(mktemp)
35
+ TRACE_MD=$(mktemp)
36
+ OKF_DIR=$(mktemp -d)
37
+ trap 'rm -f "$MATRIX_JSON" "$TRACE_MD"; rm -rf "$OKF_DIR"' EXIT
38
+
39
+ set +e
40
+ actual_output=$(python3 "$TRACE_ENGINE" "$FIXTURE_DIR" "$MATRIX_JSON" "$TRACE_MD" "$OKF_DIR" 1 "" 2>&1)
41
+ actual_exit=$?
42
+ set -e
43
+
44
+ echo "$actual_output"
45
+ echo ""
46
+
47
+ if [[ $actual_exit -eq 2 ]]; then
48
+ echo -e "${GREEN}PASS${NC} trace-stories.py --strict correctly detected the degenerate fixture (exit 2)"
49
+ echo "G-10: PASS"
50
+ exit 0
51
+ elif [[ $actual_exit -eq 0 ]]; then
52
+ echo -e "${RED}FAIL${NC} trace-stories.py --strict passed vacuously on a 0-story fixture (exit 0)"
53
+ echo "G-10: FAIL — fail-open risk (BUG-2026-07-03-trace-engine-vacuous-gate class)"
54
+ exit 1
55
+ else
56
+ echo -e "${RED}FAIL${NC} trace-stories.py --strict exited $actual_exit (expected 2)"
57
+ echo "G-10: FAIL — unexpected exit code"
58
+ exit 1
59
+ fi
@@ -17,6 +17,7 @@ Oracle tiers (TEA-inspired):
17
17
  import json, os, re, subprocess, sys
18
18
  from pathlib import Path
19
19
  from datetime import datetime, timezone
20
+ import yaml
20
21
 
21
22
  ROOT = Path(sys.argv[1])
22
23
  MATRIX_JSON = Path(sys.argv[2])
@@ -26,103 +27,21 @@ STRICT = int(sys.argv[5])
26
27
  MODE = sys.argv[6]
27
28
 
28
29
  # -----------------------------------------------------------------------
29
- # 1. Parse release-plan.yaml → story inventory
30
+ # 1. Parse YAML files → story inventory
30
31
  # -----------------------------------------------------------------------
31
- def parse_simple_yaml(text: str) -> dict:
32
- """Parse flat and one-level-nested YAML including lists of objects."""
33
- root: dict = {}
34
- stack: list = [(0, root, None, None)]
35
- skip_until_indent = None
36
- for i, raw in enumerate(text.splitlines()):
37
- stripped = raw.strip()
38
- if not stripped or stripped.startswith("#"):
39
- continue
40
- indent = len(raw) - len(raw.lstrip())
41
- if skip_until_indent is not None:
42
- if indent > skip_until_indent:
43
- continue
44
- else:
45
- skip_until_indent = None
46
- while len(stack) > 1:
47
- if indent < stack[-1][0]:
48
- stack.pop()
49
- elif indent == stack[-1][0]:
50
- if isinstance(stack[-1][1], list) and stripped.startswith("- "):
51
- break
52
- elif isinstance(stack[-1][1], dict) and stripped.startswith("- "):
53
- entry_container = stack[-1][1]
54
- entry_parent = stack[-1][2]
55
- entry_key = stack[-1][3]
56
- if entry_parent is not None and entry_key is not None:
57
- if not isinstance(entry_parent.get(entry_key), list):
58
- entry_parent[entry_key] = []
59
- stack[-1] = (stack[-1][0], entry_parent[entry_key], entry_parent, entry_key)
60
- break
61
- stack.pop()
62
- else:
63
- stack.pop()
64
- else:
65
- break
66
- container = stack[-1][1]
67
- parent_dict = stack[-1][2]
68
- key_in_parent = stack[-1][3]
69
- if stripped.startswith("- "):
70
- inner = stripped[2:]
71
- if isinstance(container, dict) and parent_dict is not None and key_in_parent is not None:
72
- if not isinstance(parent_dict.get(key_in_parent), list):
73
- orig_indent = stack[-1][0]
74
- parent_dict[key_in_parent] = []
75
- container = parent_dict[key_in_parent]
76
- stack[-1] = (orig_indent, container, parent_dict, key_in_parent)
77
- if ":" in inner:
78
- k, _, v = inner.partition(":")
79
- k = k.strip(); v = v.strip()
80
- if v in ("|", ">", "|-", ">-", "|+", ">+"):
81
- item = {k: v}; container.append(item)
82
- skip_until_indent = indent; continue
83
- item = {k: _yaml_scalar(v) if v else None}
84
- container.append(item)
85
- if v == "":
86
- stack.append((indent, item, container, k))
87
- else:
88
- container.append(_yaml_scalar(inner))
89
- continue
90
- if ":" not in stripped:
91
- continue
92
- key, _, val = stripped.partition(":")
93
- key = key.strip(); val = val.strip()
94
- if val in ("|", ">", "|-", ">-", "|+", ">+"):
95
- if isinstance(container, list) and container:
96
- container[-1][key] = val
97
- elif isinstance(container, dict):
98
- container[key] = val
99
- skip_until_indent = indent; continue
100
- if isinstance(container, list) and container:
101
- container[-1][key] = _yaml_scalar(val) if val else None
102
- if val == "":
103
- nxt = {}; container[-1][key] = nxt
104
- stack.append((indent, nxt, container[-1], key))
105
- elif isinstance(container, dict):
106
- if val == "":
107
- nxt = {}; container[key] = nxt
108
- stack.append((indent, nxt, container, key))
109
- else:
110
- container[key] = _yaml_scalar(val)
111
- return root
32
+ _MIN_STORY_BASELINE = 50 # floor assertion: --strict FAILs if story count drops below this
112
33
 
113
- def _yaml_scalar(val: str):
114
- val = val.strip('"').strip("'")
115
- if val in ("true", "false"): return val == "true"
116
- if val in ("null", "~", ""): return None
117
- try: return int(val)
118
- except ValueError:
119
- try: return float(val)
120
- except ValueError: return val
34
+ def _load_yaml(path: Path) -> dict:
35
+ """Load a YAML file with PyYAML. Exits with code 1 if parse fails."""
36
+ try:
37
+ with open(str(path), encoding="utf-8") as f:
38
+ return yaml.safe_load(f) or {}
39
+ except Exception as e:
40
+ print(f"trace-stories.py: ERROR parsing {path}: {e}", file=sys.stderr)
41
+ sys.exit(1)
121
42
 
122
- release_text = (ROOT / "specs" / "release-plan.yaml").read_text(encoding="utf-8")
123
- release = parse_simple_yaml(release_text)
124
- exec_text = (ROOT / "specs" / "execution-status.yaml").read_text(encoding="utf-8")
125
- exec_status = parse_simple_yaml(exec_text)
43
+ release = _load_yaml(ROOT / "specs" / "release-plan.yaml")
44
+ exec_status = _load_yaml(ROOT / "specs" / "execution-status.yaml")
126
45
  dev_status = exec_status.get("development_status", {})
127
46
 
128
47
  # -----------------------------------------------------------------------
@@ -141,7 +60,7 @@ for epic in epics:
141
60
  if capsule_dir:
142
61
  capsule_path = ROOT / "specs" / capsule_dir / "epic.yaml"
143
62
  if capsule_path.exists():
144
- cap = parse_simple_yaml(capsule_path.read_text(encoding="utf-8"))
63
+ cap = _load_yaml(capsule_path)
145
64
  for s in cap.get("stories", []) or []:
146
65
  if isinstance(s, dict):
147
66
  sid = s.get("id", "")
@@ -153,7 +72,7 @@ for epic in epics:
153
72
  if file_key and not capsule_dir:
154
73
  legacy_path = ROOT / "specs" / file_key
155
74
  if legacy_path.exists():
156
- leg = parse_simple_yaml(legacy_path.read_text(encoding="utf-8"))
75
+ leg = _load_yaml(legacy_path)
157
76
  for s in leg.get("stories", []) or []:
158
77
  if isinstance(s, dict):
159
78
  sid = s.get("id", "")
@@ -362,6 +281,11 @@ for s in matrix_stories:
362
281
  # 8. Strict mode
363
282
  # -----------------------------------------------------------------------
364
283
  if STRICT:
284
+ # Floor assertion — anti-vacuity guard: if story count drops below baseline
285
+ # the matrix is degenerate and --strict must fail open.
286
+ if len(stories) < _MIN_STORY_BASELINE:
287
+ print(f"trace-stories.py: STRICT FAIL — story count {len(stories)} below baseline {_MIN_STORY_BASELINE}", file=sys.stderr)
288
+ sys.exit(2)
365
289
  if matrix_stories:
366
290
  wsjf_sorted = sorted(set(s["wsjf"] for s in matrix_stories), reverse=True)
367
291
  cutoff_idx = max(0, len(wsjf_sorted) // 4 - 1)
@@ -34,6 +34,7 @@ GATES=(
34
34
  "g07-negative-path:bash scripts/golden-g07-negative-path.sh:false"
35
35
  "g08-anti-vacuity:bash scripts/golden-g08-anti-vacuity.sh:false"
36
36
  "g09-yaml-roundtrip:bash scripts/golden-g09-yaml-roundtrip.sh:false"
37
+ "g10-trace-anti-vacuity:bash scripts/golden-g10-trace-anti-vacuity.sh:false"
37
38
  "specs-parse:bash scripts/validate-specs-yaml.sh:false"
38
39
  )
39
40