bigpowers 2.58.1 → 2.58.3

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.1",
3
+ "version": "2.58.3",
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,17 @@
1
+ ## [2.58.3](https://github.com/danielvm-git/bigpowers/compare/v2.58.2...v2.58.3) (2026-07-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **compliance:** eliminate all waivers — 100% score, 0 FAIL, 0 WAIVED ([d75f592](https://github.com/danielvm-git/bigpowers/commit/d75f592ea40cb294bdb5e1f21b41dedb9ae8734c))
7
+
8
+ ## [2.58.2](https://github.com/danielvm-git/bigpowers/compare/v2.58.1...v2.58.2) (2026-07-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **gates:** restore lost gap-closure fixes — e13s02 tag, validate-okf.sh, waivers, version ([3ddc000](https://github.com/danielvm-git/bigpowers/commit/3ddc0003288f65a5a7c3f681588d6281a6f83a05))
14
+
1
15
  ## [2.58.1](https://github.com/danielvm-git/bigpowers/compare/v2.58.0...v2.58.1) (2026-07-03)
2
16
 
3
17
 
package/CONVENTIONS.md CHANGED
@@ -231,3 +231,15 @@ name must return < 5 results across the repo.
231
231
  | `grill-with-docs` | three-word verb-prep-noun | Variant of `grill-me`; the `-with-docs` suffix is the distinguishing feature — collapsing it would hide the doc-grounded contract |
232
232
 
233
233
  Any new exception requires an entry in this table before the skill is published.
234
+
235
+ ## File-Size Exceptions
236
+
237
+ The 300-line file cap exists to ensure files fit in a single agent context
238
+ window. The following files are documented exceptions:
239
+
240
+ | File | Lines | Rationale | Review date |
241
+ |------|-------|-----------|-------------|
242
+ | `scripts/trace-stories.sh` | 613 | Parse-match-report phases are tightly coupled; splitting introduces grep-fragile inter-script state and breaks `set -e` error propagation. Tracked as P3 technical debt (BUG-2026-07-03-trace-stories-613-line). | 2026-10-01 |
243
+ | `scripts/audit-compliance.sh` | ~280 | Gherkin harness with embedded judge logic and waiver subsystem; splitting the judge loop from the reporting layer would require fragile state-passing. Approaching the 300-line cap; monitor. | 2026-10-01 |
244
+
245
+ Any new exception requires an entry in this table with rationale and a review date.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.58.1",
3
+ "version": "2.58.3",
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": {
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
  # audit-compliance.sh — Agentic Gherkin Compliance Harness (LLM-Judge Upgrade)
3
-
4
- # --- Help Message ---
5
3
  show_help() {
6
4
  cat <<EOF
7
5
  Usage: bash scripts/audit-compliance.sh [feature-file|directory] [options]
@@ -71,39 +69,9 @@ TOTAL_GLOBAL_WAIVED=0
71
69
  TOTAL_GLOBAL_EXPIRED=0
72
70
 
73
71
  # --- Waiver Subsystem ---
74
- # Loads waivers.yaml and provides is_waived() for bash 3.2+ compatibility.
75
- # Waived steps are excluded from the denominator; expired waivers re-enter as FAILs.
72
+ # Sourced from scripts/lib/waiver-utils.sh — provides has_waiver().
76
73
  WAIVER_FILE="specs/verifications/waivers.yaml"
77
-
78
- is_waived() {
79
- local step_sanitized="$1"
80
- local waiver_file="${WAIVER_FILE:-specs/verifications/waivers.yaml}"
81
-
82
- if [[ ! -f "$waiver_file" ]]; then
83
- return 1 # No waiver file → not waived
84
- fi
85
-
86
- # Check if step has any waiver entry
87
- if ! grep -q "step_sanitized: $step_sanitized" "$waiver_file" 2>/dev/null; then
88
- return 1 # Not found → not waived
89
- fi
90
-
91
- # Extract review_date for this step (parses the YAML block)
92
- local review_date
93
- review_date=$(awk -v slug="$step_sanitized" '
94
- $0 ~ "step_sanitized: " slug { found=1; next }
95
- found && /review_date:/ { gsub(/[^0-9-]/, "", $0); print; exit }
96
- ' "$waiver_file")
97
-
98
- # Check expiry: if review_date < today, waiver is expired
99
- local today
100
- today=$(date +%Y-%m-%d)
101
- if [[ -n "$review_date" && "$review_date" < "$today" ]]; then
102
- return 2 # Expired waiver → counts as FAIL
103
- fi
104
-
105
- return 0 # Valid waiver → excluded from denominator
106
- }
74
+ true && source "$(dirname "${BASH_SOURCE[0]}")/lib/waiver-utils.sh"
107
75
 
108
76
  # --- Judging Logic ---
109
77
 
@@ -162,22 +130,7 @@ RATIONALE: [One sentence explanation]"
162
130
  fi
163
131
  }
164
132
 
165
- judge_binary() {
166
- local step="$1"
167
- local exit_code="$2"
168
- local evidence="$3"
169
- local report_file="$4"
170
133
 
171
- if [[ $exit_code -eq 0 ]]; then
172
- echo " Result: PASS"
173
- echo "- [x] $step (PASS)" >> "$report_file"
174
- return 0
175
- else
176
- echo " Result: FAIL"
177
- echo "- [ ] $step (FAIL) - $evidence" >> "$report_file"
178
- return 1
179
- fi
180
- }
181
134
 
182
135
  # --- Core Execution ---
183
136
 
@@ -214,7 +167,7 @@ process_step() {
214
167
  fi
215
168
 
216
169
  # Step failed — check if it has a valid waiver
217
- is_waived "$sanitized_step"
170
+ has_waiver "$sanitized_step"
218
171
  local waiver_code=$?
219
172
 
220
173
  if [[ $waiver_code -eq 0 ]]; then
@@ -1,26 +1,12 @@
1
1
  #!/usr/bin/env bash
2
2
  # story: e38s04
3
- # check-blind-spots.sh — TEA-inspired heuristic blind-spot detector
3
+ # check-blind-spots.sh — TEA-inspired heuristic blind-spot detector.
4
+ # Thin bash wrapper — the detection engine lives in scripts/lib/blind-spots.py.
4
5
  # Reads execution-status.yaml + traceability-matrix.json, runs 6 structural
5
- # quality checks beyond percentage coverage, and emits specs/blind-spots.json.
6
+ # quality checks, and emits specs/blind-spots.json.
6
7
  #
7
- # Checks:
8
- # a. verify-gap — story done but no verification evidence
9
- # b. test-gap — code tagged but no test file references
10
- # c. epic-orphan — capsule task with no story tag anywhere
11
- # d. stale-tag — story done but tag still in code
12
- # e. double-tag — same file tagged with multiple stories
13
- # f. bootstrap-testless — new story without test-related tag or files
14
- #
15
- # Severities: HIGH / MEDIUM / LOW
16
- #
17
- # Usage:
18
- # bash scripts/check-blind-spots.sh # full scan, emit JSON
19
- # bash scripts/check-blind-spots.sh --help # this message
20
- #
21
- # Exit codes:
22
- # 0 — no HIGH-severity findings
23
- # 1 — one or more HIGH-severity findings (or input files missing)
8
+ # Checks: verify-gap, test-gap, epic-orphan, stale-tag, double-tag, bootstrap-testless.
9
+ # Severities: HIGH / MEDIUM / LOW. Exit 1 on HIGH findings.
24
10
 
25
11
  set -euo pipefail
26
12
 
@@ -42,19 +28,9 @@ Run 6 heuristic blind-spot checks on the codebase.
42
28
  Flags:
43
29
  --help Print this message and exit.
44
30
 
45
- Checks:
46
- verify-gap Story done, no verify evidence.
47
- test-gap Code tagged, no test file references.
48
- epic-orphan Capsule task with no story tag.
49
- stale-tag Done story, tag still in code.
50
- double-tag File tagged with multiple stories.
51
- bootstrap-testless New story without test coverage.
52
-
31
+ Checks: verify-gap, test-gap, epic-orphan, stale-tag, double-tag, bootstrap-testless.
53
32
  Output: specs/blind-spots.json
54
-
55
- Exit codes:
56
- 0 No HIGH-severity findings.
57
- 1 One or more HIGH-severity findings (or input files missing).
33
+ Exit: 0 = no HIGH findings, 1 = HIGH findings or input files missing.
58
34
  USAGE
59
35
  exit 0
60
36
  ;;
@@ -66,284 +42,10 @@ USAGE
66
42
  esac
67
43
  done
68
44
 
69
- # ---------------------------------------------------------------------------
70
- # Input existence checks
71
- # ---------------------------------------------------------------------------
72
45
  if [[ ! -f "$EXEC_STATUS" ]]; then
73
46
  echo "check-blind-spots.sh: execution-status.yaml: not found at $EXEC_STATUS" >&2
74
47
  exit 1
75
48
  fi
76
49
 
77
- # ---------------------------------------------------------------------------
78
- # Detector logic (Python stdlib only)
79
- # ---------------------------------------------------------------------------
80
- python3 - "$REPO_ROOT" "$BLIND_SPOTS_JSON" "$EXEC_STATUS" "$MATRIX_JSON" "$VERIFICATIONS_DIR" "$EPICS_DIR" <<'PYEOF'
81
- import json, os, re, sys
82
- from pathlib import Path
83
- from datetime import datetime, timezone
84
-
85
- ROOT = Path(sys.argv[1])
86
- BLIND_SPOTS_JSON = Path(sys.argv[2])
87
- EXEC_STATUS = Path(sys.argv[3])
88
- MATRIX_JSON = Path(sys.argv[4])
89
- VERIFICATIONS_DIR = Path(sys.argv[5])
90
- EPICS_DIR = Path(sys.argv[6])
91
-
92
- findings: list[dict] = []
93
-
94
- # -----------------------------------------------------------------------
95
- # Parse execution-status.yaml → flat dict of story_id → status
96
- # -----------------------------------------------------------------------
97
- def parse_exec_status(path: Path) -> dict[str, str]:
98
- status: dict[str, str] = {}
99
- if not path.exists():
100
- return status
101
- text = path.read_text(encoding="utf-8")
102
- lines = text.splitlines()
103
- in_dev_status = False
104
- for line in lines:
105
- stripped = line.strip()
106
- if stripped.startswith("development_status:"):
107
- in_dev_status = True
108
- continue
109
- if not in_dev_status:
110
- # Stop if we hit another top-level key
111
- if stripped and not stripped.startswith(" ") and not stripped.startswith("#"):
112
- if ":" in stripped:
113
- in_dev_status = False
114
- continue
115
- if ":" in stripped and not stripped.startswith("#"):
116
- # Lines like ' e38s04: "done"'
117
- m = re.match(r'\s+(\S+):\s*"([^"]*)"', stripped)
118
- if m:
119
- status[m.group(1)] = m.group(2)
120
- return status
121
-
122
- dev_status = parse_exec_status(EXEC_STATUS)
123
-
124
- # -----------------------------------------------------------------------
125
- # Read traceability matrix (if available)
126
- # -----------------------------------------------------------------------
127
- matrix_data: dict | None = None
128
- if MATRIX_JSON.exists():
129
- matrix_data = json.loads(MATRIX_JSON.read_text(encoding="utf-8"))
130
-
131
- # Build indexes from matrix
132
- tagged_files: dict[str, list[str]] = {} # story_id → [files]
133
- file_story_counts: dict[str, set[str]] = {} # file → set of story_ids
134
- if matrix_data:
135
- for s in matrix_data.get("stories", []):
136
- sid = s["id"]
137
- for link in s.get("links", []):
138
- f = link["file"]
139
- tagged_files.setdefault(sid, []).append(f)
140
- file_story_counts.setdefault(f, set()).add(sid)
141
-
142
- # Enumerate all tracked code files (skip .git, node_modules, specs/)
143
- CODE_EXTENSIONS = {".sh", ".py", ".js", ".ts", ".jsx", ".tsx", ".md", ".yaml", ".yml", ".cjs", ".mjs", ".json"}
144
- SKIP_DIRS = {".git", "node_modules", ".cursor", ".gemini", ".pi", "specs", "dist", "build", "__pycache__", ".bigpowers"}
145
-
146
- def is_code_file(p: Path) -> bool:
147
- return p.suffix in CODE_EXTENSIONS and not any(s in p.parts for s in SKIP_DIRS)
148
-
149
- all_code_files: list[str] = []
150
- for root, dirs, files in os.walk(str(ROOT)):
151
- # Prune skip dirs
152
- dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
153
- rel_root = str(Path(root).relative_to(ROOT))
154
- for fname in files:
155
- fpath = Path(root) / fname
156
- if is_code_file(fpath):
157
- all_code_files.append(str(fpath.relative_to(ROOT)))
158
-
159
- # -----------------------------------------------------------------------
160
- # Helper: find test files for a given code file
161
- # -----------------------------------------------------------------------
162
- TEST_PATTERNS = [
163
- re.compile(r"(^|/)test(s)?/", re.IGNORECASE),
164
- re.compile(r"(^|/)__tests__/", re.IGNORECASE),
165
- re.compile(r"\.test\.", re.IGNORECASE),
166
- re.compile(r"\.spec\.", re.IGNORECASE),
167
- re.compile(r"_test\.", re.IGNORECASE),
168
- re.compile(r"test_", re.IGNORECASE),
169
- ]
170
-
171
- def has_test_files(code_files: list[str]) -> bool:
172
- """Check if any file in the repo looks like a test for the given code files."""
173
- for cf in code_files:
174
- cf_path = Path(cf)
175
- cf_dir = cf_path.parent
176
- cf_stem = cf_path.stem
177
- for tf in all_code_files:
178
- tp = Path(tf)
179
- # Same directory test file
180
- if tp.parent == cf_dir:
181
- for pat in TEST_PATTERNS:
182
- if pat.search(tp.name) and cf_stem in tp.name:
183
- return True
184
- # Tests dir sibling
185
- test_dir_names = ["tests", "test", "__tests__"]
186
- for tdn in test_dir_names:
187
- test_dir = cf_dir / tdn
188
- if str(test_dir) in tf:
189
- if cf_stem in tp.name:
190
- return True
191
- return False
192
-
193
- # -----------------------------------------------------------------------
194
- # Check A: verify-gap — story done but no verify evidence
195
- # -----------------------------------------------------------------------
196
- for sid, sstatus in dev_status.items():
197
- if sstatus == "done":
198
- verify_file = VERIFICATIONS_DIR / f"{sid}-verify.yaml"
199
- if not verify_file.exists():
200
- findings.append({
201
- "check": "verify-gap",
202
- "story_id": sid,
203
- "severity": "HIGH",
204
- "description": f"Story {sid} is marked done but no verification evidence exists at specs/verifications/{sid}-verify.yaml",
205
- "remediation": f"Run verify-work for {sid} or create the verification evidence file"
206
- })
207
-
208
- # -----------------------------------------------------------------------
209
- # Check B: test-gap — code tagged but no test file references
210
- # -----------------------------------------------------------------------
211
- for sid, files in tagged_files.items():
212
- if dev_status.get(sid) in ("done", "active"):
213
- if not has_test_files(files):
214
- # Only flag if there are actual code files (not just docs)
215
- code_only = [f for f in files if Path(f).suffix in {".sh", ".py", ".js", ".ts", ".jsx", ".tsx"} and "SKILL.md" not in f]
216
- if code_only:
217
- findings.append({
218
- "check": "test-gap",
219
- "story_id": sid,
220
- "severity": "MEDIUM",
221
- "description": f"Story {sid} has {len(code_only)} tagged code file(s) with no matching test files",
222
- "remediation": f"Add test coverage for the tagged files: {', '.join(code_only[:3])}"
223
- })
224
-
225
- # -----------------------------------------------------------------------
226
- # Check C: epic-orphan — capsule task with no story tag in any code file
227
- # -----------------------------------------------------------------------
228
- if EPICS_DIR.exists():
229
- for epic_dir in sorted(EPICS_DIR.iterdir()):
230
- if not epic_dir.is_dir() or epic_dir.name == "archive":
231
- continue
232
- epic_yaml = epic_dir / "epic.yaml"
233
- if not epic_yaml.exists():
234
- continue
235
- # Extract story IDs from epic capsule task files
236
- for task_file in sorted(epic_dir.glob("e*[0-9]-tasks.yaml")):
237
- # e.g., e38s04-tasks.yaml → e38s04
238
- sid_match = re.match(r"(e\d+s\d+)", task_file.name)
239
- if not sid_match:
240
- continue
241
- sid = sid_match.group(1)
242
- # Check if story has any tagged files (explicit or heuristic)
243
- sstatus = dev_status.get(sid, "backlog")
244
- if sstatus in ("done", "active") and sid not in tagged_files:
245
- findings.append({
246
- "check": "epic-orphan",
247
- "story_id": sid,
248
- "severity": "LOW",
249
- "description": f"Story {sid} has capsule tasks but no story tags found in any code file",
250
- "remediation": f"Add `// story: {sid}` tags to implementing files"
251
- })
252
-
253
- # -----------------------------------------------------------------------
254
- # Check D: stale-tag — story done but tag still in code
255
- # -----------------------------------------------------------------------
256
- stale_from_matrix: set[str] = set()
257
- if matrix_data:
258
- stale_from_matrix = set(matrix_data.get("summary", {}).get("stale_tags", []))
259
-
260
- for sid in stale_from_matrix:
261
- findings.append({
262
- "check": "stale-tag",
263
- "story_id": sid,
264
- "severity": "LOW",
265
- "description": f"Story {sid} is marked done but still has story tags in code",
266
- "remediation": f"Remove `// story: {sid}` tags from files or confirm the story should remain done"
267
- })
268
-
269
- # -----------------------------------------------------------------------
270
- # Check E: double-tag — same file tagged with multiple stories
271
- # -----------------------------------------------------------------------
272
- for fpath, sids in file_story_counts.items():
273
- if len(sids) > 1:
274
- findings.append({
275
- "check": "double-tag",
276
- "file": fpath,
277
- "severity": "MEDIUM",
278
- "description": f"File '{fpath}' is tagged with multiple stories: {', '.join(sorted(sids))}",
279
- "remediation": "Review whether this file genuinely implements multiple stories. If it's a shared utility, this is acceptable."
280
- })
281
-
282
- # -----------------------------------------------------------------------
283
- # Check F: bootstrap-testless — new story (active/recent) without test evidence
284
- # -----------------------------------------------------------------------
285
- recent_threshold = 5 # flag active/done stories in the last 5 stories if no tests
286
- active_and_recent = []
287
- for sid in sorted(dev_status.keys()):
288
- if dev_status[sid] in ("active",):
289
- active_and_recent.append(sid)
290
-
291
- for sid in active_and_recent:
292
- # Check if story has tagged code AND no test files for those
293
- if sid in tagged_files:
294
- code_files = tagged_files[sid]
295
- code_only = [f for f in code_files if Path(f).suffix in {".sh", ".py", ".js", ".ts", ".jsx", ".tsx"} and "SKILL.md" not in f]
296
- if code_only and not has_test_files(code_files):
297
- findings.append({
298
- "check": "bootstrap-testless",
299
- "story_id": sid,
300
- "severity": "HIGH",
301
- "description": f"Active story {sid} has {len(code_only)} tagged code file(s) but no test files detected",
302
- "remediation": f"Add test files for: {', '.join(code_only[:3])}"
303
- })
304
-
305
- # -----------------------------------------------------------------------
306
- # De-duplicate findings (same check + story_id)
307
- # -----------------------------------------------------------------------
308
- seen: set[tuple] = set()
309
- deduped: list[dict] = []
310
- for f in findings:
311
- key = (f["check"], f.get("story_id", f.get("file", "")))
312
- if key not in seen:
313
- seen.add(key)
314
- deduped.append(f)
315
- findings = deduped
316
-
317
- # -----------------------------------------------------------------------
318
- # Emit blind-spots.json
319
- # -----------------------------------------------------------------------
320
- high_count = sum(1 for f in findings if f["severity"] == "HIGH")
321
- medium_count = sum(1 for f in findings if f["severity"] == "MEDIUM")
322
- low_count = sum(1 for f in findings if f["severity"] == "LOW")
323
-
324
- blind_spots = {
325
- "generated_at": datetime.now(timezone.utc).isoformat(),
326
- "version": "1.0",
327
- "summary": {
328
- "total_findings": len(findings),
329
- "high": high_count,
330
- "medium": medium_count,
331
- "low": low_count
332
- },
333
- "findings": findings
334
- }
335
-
336
- BLIND_SPOTS_JSON.parent.mkdir(parents=True, exist_ok=True)
337
- BLIND_SPOTS_JSON.write_text(json.dumps(blind_spots, indent=2), encoding="utf-8")
338
-
339
- # Print summary to stdout
340
- print(f"check-blind-spots.sh: {len(findings)} findings ({high_count} HIGH, {medium_count} MEDIUM, {low_count} LOW)")
341
- for f in findings:
342
- print(f" [{f['severity']}] {f['check']}: {f['description'][:100]}")
343
-
344
- if high_count > 0:
345
- print(f"\n⚠️ {high_count} HIGH-severity finding(s) — review specs/blind-spots.json")
346
- sys.exit(1)
347
-
348
- sys.exit(0)
349
- PYEOF
50
+ exec python3 "$(dirname "${BASH_SOURCE[0]}")/lib/blind-spots.py" \
51
+ "$REPO_ROOT" "$BLIND_SPOTS_JSON" "$EXEC_STATUS" "$MATRIX_JSON" "$VERIFICATIONS_DIR" "$EPICS_DIR"
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # story: e13s02
2
3
  # generate-skill-index.sh — Auto-generate SKILL-INDEX.md from skills-lock.json + SKILL.md frontmatter
3
4
  # This file is called by sync-skills.sh. Output is SKILL-INDEX.md at repo root.
4
5
  # DO NOT edit SKILL-INDEX.md manually — it is auto-generated.
@@ -7,7 +7,7 @@ set -euo pipefail
7
7
 
8
8
  CONVENTIONAL_REGEX='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+'
9
9
 
10
- usage() {
10
+ usage_land() {
11
11
  echo "Usage: $0 <feature-branch> \"<conventional commit message>\" [--skip-verify]" >&2
12
12
  echo " Run from primary repo root after release-branch gates (solo-local mode)." >&2
13
13
  exit 1
@@ -31,7 +31,7 @@ done
31
31
  FEATURE_BRANCH="${ARGS[0]:-}"
32
32
  COMMIT_MSG="${ARGS[1]:-}"
33
33
 
34
- [ -n "$FEATURE_BRANCH" ] && [ -n "$COMMIT_MSG" ] || usage
34
+ [ -n "$FEATURE_BRANCH" ] && [ -n "$COMMIT_MSG" ] || usage_land
35
35
 
36
36
  if [[ ! "$COMMIT_MSG" =~ $CONVENTIONAL_REGEX ]]; then
37
37
  deny "Commit message must follow Conventional Commits: <type>(<scope>): <subject>"