bigpowers 2.59.0 → 2.59.2
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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.59.2](https://github.com/danielvm-git/bigpowers/compare/v2.59.1...v2.59.2) (2026-07-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **trace:** exclude .venv, site-packages, __pycache__ from trace-stories walker ([aff4b4f](https://github.com/danielvm-git/bigpowers/commit/aff4b4fc1b9b0acfd2940c5fd137c04285a834d9))
|
|
7
|
+
|
|
8
|
+
## [2.59.1](https://github.com/danielvm-git/bigpowers/compare/v2.59.0...v2.59.1) (2026-07-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **trace:** replace hand-rolled YAML parser, add floor assertion, G-10 ([67f7c71](https://github.com/danielvm-git/bigpowers/commit/67f7c71a74deefac2fea461f161c9656b751b387))
|
|
14
|
+
|
|
1
15
|
# [2.59.0](https://github.com/danielvm-git/bigpowers/compare/v2.58.9...v2.59.0) (2026-07-04)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -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
|
|
30
|
+
# 1. Parse YAML files → story inventory
|
|
30
31
|
# -----------------------------------------------------------------------
|
|
31
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
except
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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 =
|
|
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 =
|
|
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", "")
|
|
@@ -187,7 +106,7 @@ for line in result.stdout.splitlines():
|
|
|
187
106
|
# -----------------------------------------------------------------------
|
|
188
107
|
# 4. Oracle tiers
|
|
189
108
|
# -----------------------------------------------------------------------
|
|
190
|
-
EXCLUDE_DIRS = {".git", "node_modules", ".cursor", ".gemini", ".pi"}
|
|
109
|
+
EXCLUDE_DIRS = {".git", "node_modules", ".cursor", ".gemini", ".pi", ".venv", "venv", ".tox", "__pycache__", "site-packages", ".mypy_cache", ".pytest_cache", ".ruff_cache"}
|
|
191
110
|
EXCLUDE_PREFIXES = ("specs/archive/", "specs/codebase-wiki/")
|
|
192
111
|
|
|
193
112
|
def _is_excluded(rel_path: str) -> bool:
|
|
@@ -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
|
|