bigpowers 2.88.14 → 2.88.16

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.14",
3
+ "version": "2.88.16",
4
4
  "description": "81 skills — 73 agent skills synthesizing 17 years of software engineering discipline into a prescriptive methodology for solo developers",
5
5
  "keywords": [
6
6
  "gemini-extension"
package/.pi/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.14",
3
+ "version": "2.88.16",
4
4
  "description": "81 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.88.16](https://github.com/danielvm-git/bigpowers/compare/v2.88.15...v2.88.16) (2026-10-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **receipts:** read golden suite counts from deterministic block ([#166](https://github.com/danielvm-git/bigpowers/issues/166)) ([56ecb93](https://github.com/danielvm-git/bigpowers/commit/56ecb9373e730ecbdca45e7a08129dfc0324d27e))
7
+
8
+ ## [2.88.15](https://github.com/danielvm-git/bigpowers/compare/v2.88.14...v2.88.15) (2026-10-25)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ci:** gate the GitHub Pages deploy on every check in test-build-release ([#164](https://github.com/danielvm-git/bigpowers/issues/164)) ([5f3e4de](https://github.com/danielvm-git/bigpowers/commit/5f3e4de59454d5ffe95d932a3da873db7b4e1d80))
14
+
1
15
  ## [2.88.14](https://github.com/danielvm-git/bigpowers/compare/v2.88.13...v2.88.14) (2026-10-25)
2
16
 
3
17
 
package/CLAUDE.md CHANGED
@@ -68,7 +68,7 @@ Stack: Markdown / Bash (documentation-based; skills integrate with Claude Code,
68
68
  | Lint | `bash scripts/sync-skills.sh` (validates SKILL.md syntax) |
69
69
  | Validate specs YAML | `bash scripts/validate-specs-yaml.sh` |
70
70
  | Typecheck | N/A (Markdown / Bash project) |
71
- | CI platform | GitHub Actions — one pipeline `.github/workflows/test-build-release.yml` (sync, verification-gates, skill-verify, story-verify → release); plus `docs-site.yml`, `publish-wiki.yml` |
71
+ | CI platform | GitHub Actions — one pipeline `.github/workflows/test-build-release.yml` (sync, verification-gates, skill-verify, story-verify → release, and → pages-build → pages-deploy when docs paths change); plus `publish-wiki.yml` |
72
72
  | Compliance | `npm run compliance` |
73
73
  | Verification Gates | `bash scripts/run-verification-gates.sh` |
74
74
  | Traceability | `bash scripts/trace-stories.sh --strict` | grep for story tags (traceability check) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bigpowers",
3
- "version": "2.88.14",
3
+ "version": "2.88.16",
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": {
@@ -43,8 +43,9 @@ if os.path.isdir(gd):
43
43
  try:
44
44
  with open(reports[-1]) as f:
45
45
  import yaml; data = yaml.safe_load(f)
46
- sm = data.get('summary',{})
47
- sections['golden_suite'] = {'value':{'pass_rate':sm.get('pass_rate'),'passed':sm.get('passed'),'total':sm.get('total'),'overall':sm.get('overall','unknown'),'timestamp':data.get('timestamp',''),'commit':data.get('git_commit','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/benchmarks/reports/GOLDEN-*.yaml (latest)'}
46
+ sm = data.get('deterministic',{})
47
+ verdict = (data.get('summary') or {}).get('combined_verdict','unknown')
48
+ sections['golden_suite'] = {'value':{'pass_rate':sm.get('pass_rate'),'passed':sm.get('passed'),'total':sm.get('total'),'overall':verdict,'timestamp':data.get('timestamp',''),'commit':data.get('git_commit','')},'generated_at':'$TIMESTAMP','source':'measured','command':'specs/benchmarks/reports/GOLDEN-*.yaml (latest)'}
48
49
  except Exception as e:
49
50
  print(f'build-receipts: golden error: {e}', file=sys.stderr)
50
51
  sections['golden_suite'] = {'source':'absent','generated_at':'$TIMESTAMP','command':'specs/benchmarks/reports/GOLDEN-*.yaml'}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # story: BUG-2026-10-25-release-not-gated
2
+ # story: BUG-2026-10-25-release-not-gated BUG-2026-10-25-pages-not-gated
3
3
  # test-ci-pipeline-contract.sh — the CI shape the portfolio template requires
4
4
  # (danielvm-git/.github, docs/concept/github-actions-best-practices.md):
5
5
  #
@@ -10,6 +10,8 @@
10
10
  # 3. no ubuntu-latest; every non-local action is pinned to a 40-char SHA
11
11
  # 4. every job has timeout-minutes
12
12
  # 5. release and Pages never cancel a run in progress
13
+ # 6. the Pages deploy (any job using actions/deploy-pages) lives in the
14
+ # pipeline, needs: every gate job and runs only for main
13
15
  #
14
16
  # Agent workflows (agent-locks.yml, generated agentics-maintenance.yml) are
15
17
  # exempt from 3–4 per migration plan v2.
@@ -40,6 +42,27 @@ def cancels(block):
40
42
  # Literal true cancels; an expression or false does not cancel a push run.
41
43
  return isinstance(block, dict) and block.get("cancel-in-progress") is True
42
44
 
45
+ def needs_of(job):
46
+ needs = job.get("needs", [])
47
+ return {needs} if isinstance(needs, str) else set(needs)
48
+
49
+ def ancestors(jobs, name):
50
+ seen, todo = set(), list(needs_of(jobs.get(name, {})))
51
+ while todo:
52
+ n = todo.pop()
53
+ if n not in seen:
54
+ seen.add(n)
55
+ todo.extend(needs_of(jobs.get(n, {})))
56
+ return seen
57
+
58
+ def deploys_pages(job):
59
+ return any(str(st.get("uses", "")).startswith("actions/deploy-pages@")
60
+ for st in job.get("steps") or [])
61
+
62
+ def main_only(cond):
63
+ return ("refs/heads/main" in cond and
64
+ ("github.event_name == 'push'" in cond or "workflow_dispatch" in cond))
65
+
43
66
  pipe_path = os.path.join(d, "test-build-release.yml")
44
67
  if not os.path.isfile(pipe_path):
45
68
  bad.append("test-build-release.yml missing")
@@ -49,13 +72,30 @@ else:
49
72
  missing = GATES - set(jobs)
50
73
  if missing:
51
74
  bad.append(f"pipeline lacks gate jobs: {sorted(missing)}")
75
+ # Gate jobs: the four named ones plus any other job that needs nothing —
76
+ # a new root job is a gate until it says otherwise with a needs: edge.
77
+ gate_jobs = (GATES | {n for n, j in jobs.items() if not needs_of(j)}) - {"release"}
52
78
  rel = jobs.get("release")
53
79
  if not rel:
54
80
  bad.append("pipeline has no release job")
55
- else:
56
- needs = rel.get("needs", [])
57
- needs = {needs} if isinstance(needs, str) else set(needs)
58
- gate_jobs = set(jobs) - {"release"}
81
+ pages = [n for n, j in jobs.items() if deploys_pages(j)]
82
+ if not pages:
83
+ bad.append("pipeline has no Pages deploy job")
84
+ for name in pages:
85
+ job, up = jobs[name], ancestors(jobs, name)
86
+ if not gate_jobs <= up:
87
+ bad.append(f"{name}: Pages deploy does not need: {sorted(gate_jobs - up)}")
88
+ conds = [str(jobs[n].get("if", "")) for n in up | {name} if n in jobs]
89
+ if any("always()" in c or "cancelled()" in c for c in conds):
90
+ bad.append(f"{name}: Pages deploy runs even when a gate fails (always()/cancelled())")
91
+ if not any(main_only(c) for c in conds):
92
+ bad.append(f"{name}: Pages deploy is not limited to main")
93
+ if cancels(pipe.get("concurrency")) or cancels(job.get("concurrency")):
94
+ bad.append(f"{name}: Pages deploy can be cancelled in progress")
95
+ if not isinstance(job.get("concurrency"), dict) or not job["concurrency"].get("group"):
96
+ bad.append(f"{name}: Pages deploy has no concurrency group")
97
+ if rel:
98
+ needs = needs_of(rel)
59
99
  if not gate_jobs <= needs:
60
100
  bad.append(f"release does not need: {sorted(gate_jobs - needs)}")
61
101
  cond = str(rel.get("if", ""))
@@ -68,16 +108,14 @@ for name in RETIRED:
68
108
  if os.path.exists(os.path.join(d, name)):
69
109
  bad.append(f"retired workflow still present: {name}")
70
110
 
71
- docs = os.path.join(d, "docs-site.yml")
72
- if os.path.isfile(docs) and cancels(load(docs).get("concurrency")):
73
- bad.append("docs-site.yml: Pages deploy can be cancelled in progress")
74
-
75
111
  for path in sorted(glob.glob(os.path.join(d, "*.yml"))):
76
112
  name = os.path.basename(path)
77
113
  if name in EXEMPT:
78
114
  continue
79
115
  for job_name, job in (load(path).get("jobs") or {}).items():
80
116
  where = f"{name}:{job_name}"
117
+ if name != "test-build-release.yml" and deploys_pages(job):
118
+ bad.append(f"{where}: Pages deploy outside the pipeline (no needs: edge to the gates)")
81
119
  if "uses" in job: # reusable-workflow call: no runner of its own
82
120
  continue
83
121
  if "ubuntu-latest" in str(job.get("runs-on", "")):
@@ -101,6 +139,7 @@ self_test() {
101
139
  tmp=$(mktemp -d)
102
140
  trap "rm -rf '$tmp'" EXIT
103
141
  local pin="actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"
142
+ local deploy="actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128"
104
143
  write_fixture() { # $1 = dir
105
144
  mkdir -p "$1"
106
145
  cat > "$1/test-build-release.yml" <<YAML
@@ -117,6 +156,18 @@ jobs:
117
156
  timeout-minutes: 15
118
157
  concurrency: {group: release, cancel-in-progress: false}
119
158
  steps: [{run: "true"}]
159
+ pages-build:
160
+ needs: [sync, verification-gates, skill-verify, story-verify]
161
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
162
+ runs-on: ubuntu-22.04
163
+ timeout-minutes: 15
164
+ steps: [{run: "true"}]
165
+ pages-deploy:
166
+ needs: pages-build
167
+ runs-on: ubuntu-22.04
168
+ timeout-minutes: 15
169
+ concurrency: {group: pages, cancel-in-progress: false}
170
+ steps: [{uses: "$deploy"}]
120
171
  YAML
121
172
  }
122
173
  expect() { # $1 = label, $2 = want pass|fail, $3 = dir
@@ -145,6 +196,28 @@ YAML
145
196
  write_fixture "$tmp/retired"; : > "$tmp/retired/publish.yml"
146
197
  expect "retired workflow present fails" fail "$tmp/retired"
147
198
 
199
+ write_fixture "$tmp/pages-needs"
200
+ sed -i.bak '/pages-build:/{n;s/needs: \[.*\]/needs: [sync]/;}' "$tmp/pages-needs/test-build-release.yml"
201
+ expect "Pages deploy missing gate needs fails" fail "$tmp/pages-needs"
202
+
203
+ write_fixture "$tmp/pages-always"
204
+ sed -i.bak '/pages-deploy:/{n;s/needs: pages-build/needs: pages-build\
205
+ if: always()/;}' "$tmp/pages-always/test-build-release.yml"
206
+ expect "Pages deploy with always() fails" fail "$tmp/pages-always"
207
+
208
+ write_fixture "$tmp/pages-anyref"
209
+ sed -i.bak '/pages-build:/{n;n;s/if: .*/if: github.event_name == '"'push'"'/;}' "$tmp/pages-anyref/test-build-release.yml"
210
+ expect "Pages deploy not limited to main fails" fail "$tmp/pages-anyref"
211
+
212
+ write_fixture "$tmp/pages-cancel"
213
+ sed -i.bak 's/group: pages, cancel-in-progress: false/group: pages, cancel-in-progress: true/' "$tmp/pages-cancel/test-build-release.yml"
214
+ expect "cancellable Pages deploy fails" fail "$tmp/pages-cancel"
215
+
216
+ write_fixture "$tmp/pages-split"
217
+ printf 'on: push\njobs:\n deploy: {runs-on: ubuntu-22.04, timeout-minutes: 15, steps: [{uses: %s}]}\n' \
218
+ "$deploy" > "$tmp/pages-split/docs-site.yml"
219
+ expect "Pages deploy in a separate workflow fails" fail "$tmp/pages-split"
220
+
148
221
  [ "$ok" -eq 1 ] && echo "OK: pipeline contract self-test" || { echo "pipeline contract self-test FAILED"; exit 1; }
149
222
  }
150
223