bigpowers 2.84.1 → 2.85.0

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.
Files changed (46) hide show
  1. package/.agents/skills/craft-skill/SKILL.md +21 -4
  2. package/.agents/skills/seed-conventions/SKILL.md +21 -4
  3. package/.agents/skills/stocktake-skills/SKILL.md +2 -0
  4. package/.codebuddy/skills/craft-skill/SKILL.md +21 -4
  5. package/.codebuddy/skills/seed-conventions/SKILL.md +21 -4
  6. package/.codebuddy/skills/stocktake-skills/SKILL.md +2 -0
  7. package/.codex/skills/craft-skill/SKILL.md +21 -4
  8. package/.codex/skills/seed-conventions/SKILL.md +21 -4
  9. package/.codex/skills/stocktake-skills/SKILL.md +2 -0
  10. package/.hermes/skills/craft-skill/SKILL.md +21 -4
  11. package/.hermes/skills/seed-conventions/SKILL.md +21 -4
  12. package/.hermes/skills/stocktake-skills/SKILL.md +2 -0
  13. package/.kilocode/rules/craft-skill.md +21 -4
  14. package/.kilocode/rules/seed-conventions.md +21 -4
  15. package/.kilocode/rules/stocktake-skills.md +2 -0
  16. package/.mimocode/skills/craft-skill/SKILL.md +21 -4
  17. package/.mimocode/skills/seed-conventions/SKILL.md +21 -4
  18. package/.mimocode/skills/stocktake-skills/SKILL.md +2 -0
  19. package/.pi/package.json +1 -1
  20. package/.pi/prompts/craft-skill.md +21 -4
  21. package/.pi/prompts/seed-conventions.md +21 -4
  22. package/.pi/prompts/stocktake-skills.md +2 -0
  23. package/.pi/skills/craft-skill/SKILL.md +21 -4
  24. package/.pi/skills/seed-conventions/SKILL.md +21 -4
  25. package/.pi/skills/stocktake-skills/SKILL.md +2 -0
  26. package/.trae/skills/craft-skill/SKILL.md +21 -4
  27. package/.trae/skills/seed-conventions/SKILL.md +21 -4
  28. package/.trae/skills/stocktake-skills/SKILL.md +2 -0
  29. package/.windsurf/rules/craft-skill.md +21 -4
  30. package/.windsurf/rules/seed-conventions.md +21 -4
  31. package/.windsurf/rules/stocktake-skills.md +2 -0
  32. package/.zcode/skills/craft-skill/SKILL.md +21 -4
  33. package/.zcode/skills/seed-conventions/SKILL.md +21 -4
  34. package/.zcode/skills/stocktake-skills/SKILL.md +2 -0
  35. package/CHANGELOG.md +12 -0
  36. package/package.json +1 -1
  37. package/scripts/land-branch.sh +22 -9
  38. package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
  39. package/scripts/lib/__pycache__/srp_engine_links.cpython-312.pyc +0 -0
  40. package/scripts/lib/land-branch-push.sh +106 -0
  41. package/scripts/validate-agentic-ste.sh +229 -0
  42. package/skills/craft-skill/SKILL.md +22 -4
  43. package/skills/seed-conventions/SKILL.md +21 -4
  44. package/skills/stocktake-skills/SKILL.md +2 -0
  45. package/skills-lock.json +3 -3
  46. package/tests/test-land-branch-protected.sh +222 -0
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env bash
2
+ # story: e79s02
3
+ # validate-agentic-ste.sh — mechanical Agentic STE checks for instructional prose.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+
9
+ MAX_WORDS=20
10
+ BANNED_RE='\b(should|might|could|may|consider|try|generally|typically)\b'
11
+
12
+ STRICT=false
13
+ AUDIT=false
14
+ SELF_TEST=false
15
+ TARGETS=()
16
+
17
+ ste_usage() {
18
+ echo "Usage: bash scripts/validate-agentic-ste.sh [--self-test|--strict|--audit] [file|dir ...]"
19
+ echo " (no args) Run built-in fixture self-test"
20
+ echo " --strict Exit 1 on violations (craft-skill gate)"
21
+ echo " --audit WARN-only catalog scan (stocktake)"
22
+ exit 2
23
+ }
24
+
25
+ while [[ $# -gt 0 ]]; do
26
+ case "$1" in
27
+ --strict) STRICT=true; shift ;;
28
+ --audit) AUDIT=true; shift ;;
29
+ --self-test) SELF_TEST=true; shift ;;
30
+ -h|--help) ste_usage ;;
31
+ --) shift; TARGETS+=("$@"); break ;;
32
+ -*) echo "Unknown flag: $1" >&2; ste_usage ;;
33
+ *) TARGETS+=("$1"); shift ;;
34
+ esac
35
+ done
36
+
37
+ VIOLATIONS=0
38
+ WARNINGS=0
39
+
40
+ ste_report_fail() {
41
+ echo "FAIL: $1" >&2
42
+ VIOLATIONS=$((VIOLATIONS + 1))
43
+ }
44
+
45
+ ste_report_warn() {
46
+ echo "WARN: $1" >&2
47
+ WARNINGS=$((WARNINGS + 1))
48
+ }
49
+
50
+ ste_strip_frontmatter() {
51
+ awk '
52
+ BEGIN { n=0; emit=0 }
53
+ /^---$/ { n++; if (n==1) next; if (n==2) { emit=1; next } }
54
+ emit { print }
55
+ ' "$1"
56
+ }
57
+
58
+ ste_extract_prose() {
59
+ local file="$1"
60
+ local body
61
+ if [[ "$(basename "$file")" == "SKILL.md" ]]; then
62
+ body="$(ste_strip_frontmatter "$file")"
63
+ else
64
+ body="$(cat "$file")"
65
+ fi
66
+ printf '%s\n' "$body" | awk '
67
+ BEGIN { in_code=0 }
68
+ /^```/ { in_code = !in_code; next }
69
+ in_code { next }
70
+ /^[[:space:]]*#/ { next }
71
+ /^[[:space:]]*\|/ { next }
72
+ /^[[:space:]]*<!--/ { next }
73
+ /^[[:space:]]*$/ { next }
74
+ /^# story:/ { next }
75
+ /^<!-- story:/ { next }
76
+ { print }
77
+ '
78
+ }
79
+
80
+ ste_count_words() {
81
+ # shellcheck disable=SC2207
82
+ local words=($1)
83
+ echo "${#words[@]}"
84
+ }
85
+
86
+ ste_audit_line() {
87
+ local file="$1"
88
+ local line_no="$2"
89
+ local line="$3"
90
+ local label="${file}:${line_no}"
91
+
92
+ if echo "$line" | grep -qiE "$BANNED_RE"; then
93
+ local word
94
+ word="$(echo "$line" | grep -oiE "$BANNED_RE" | head -1)"
95
+ if $STRICT; then
96
+ ste_report_fail "$label — banned hedge modal '$word'"
97
+ else
98
+ ste_report_warn "$label — banned hedge modal '$word'"
99
+ fi
100
+ fi
101
+
102
+ local sentence
103
+ while IFS= read -r sentence; do
104
+ [[ -z "$sentence" ]] && continue
105
+ local trimmed
106
+ trimmed="$(echo "$sentence" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
107
+ [[ -z "$trimmed" ]] && continue
108
+ local wc
109
+ wc="$(ste_count_words "$trimmed")"
110
+ if (( wc > MAX_WORDS )); then
111
+ if $STRICT; then
112
+ ste_report_fail "$label — sentence has ${wc} words (max ${MAX_WORDS}): ${trimmed:0:60}..."
113
+ else
114
+ ste_report_warn "$label — sentence has ${wc} words (max ${MAX_WORDS}): ${trimmed:0:60}..."
115
+ fi
116
+ fi
117
+ done < <(echo "$line" | sed 's/[.!?]/&\n/g')
118
+ }
119
+
120
+ ste_audit_file() {
121
+ local file="$1"
122
+ [[ -f "$file" ]] || { ste_report_fail "not found: $file"; return; }
123
+
124
+ local prose line_no=0
125
+ prose="$(ste_extract_prose "$file")"
126
+ while IFS= read -r line; do
127
+ line_no=$((line_no + 1))
128
+ ste_audit_line "$file" "$line_no" "$line"
129
+ done <<< "$prose"
130
+ }
131
+
132
+ ste_collect_files() {
133
+ local path="$1"
134
+ if [[ -f "$path" ]]; then
135
+ echo "$path"
136
+ elif [[ -d "$path" ]]; then
137
+ find "$path" -type f \( -name 'SKILL.md' -o -name 'CLAUDE.md' -o -name 'CONVENTIONS.md' -o -name 'AGENTS.md' \) | sort
138
+ fi
139
+ }
140
+
141
+ ste_run_self_test() {
142
+ local tmp
143
+ tmp="$(mktemp -d)"
144
+ trap 'rm -rf "$tmp"' RETURN
145
+
146
+ cat > "$tmp/good-skill.md" <<'EOF'
147
+ ---
148
+ name: fixture-good
149
+ description: Test fixture for Agentic STE validator. Use when running self-test.
150
+ ---
151
+ # Fixture Good
152
+ > **HARD GATE** — Run Preflight before forward work.
153
+ Run tests after every change.
154
+ Write specs to specs/ at project root.
155
+ EOF
156
+
157
+ cat > "$tmp/bad-hedge.md" <<'EOF'
158
+ ---
159
+ name: fixture-bad
160
+ description: Bad fixture.
161
+ ---
162
+ You should consider running tests generally.
163
+ EOF
164
+
165
+ cat > "$tmp/bad-long.md" <<'EOF'
166
+ ---
167
+ name: fixture-long
168
+ description: Long sentence fixture.
169
+ ---
170
+ Run the full local green stack including compliance verification gates sync skills trace stories and catalog drift check before any forward implementation work on this repository.
171
+ EOF
172
+
173
+ local saved_strict=$STRICT saved_audit=$AUDIT
174
+ STRICT=true
175
+ AUDIT=false
176
+ VIOLATIONS=0
177
+
178
+ ste_audit_file "$tmp/good-skill.md"
179
+ if (( VIOLATIONS > 0 )); then
180
+ echo "SELF-TEST FAIL: good fixture should pass" >&2
181
+ return 1
182
+ fi
183
+
184
+ VIOLATIONS=0
185
+ ste_audit_file "$tmp/bad-hedge.md"
186
+ if (( VIOLATIONS == 0 )); then
187
+ echo "SELF-TEST FAIL: hedge fixture should fail" >&2
188
+ return 1
189
+ fi
190
+
191
+ VIOLATIONS=0
192
+ ste_audit_file "$tmp/bad-long.md"
193
+ if (( VIOLATIONS == 0 )); then
194
+ echo "SELF-TEST FAIL: long-sentence fixture should fail" >&2
195
+ return 1
196
+ fi
197
+
198
+ STRICT=$saved_strict
199
+ AUDIT=$saved_audit
200
+ VIOLATIONS=0
201
+ echo "validate-agentic-ste: self-test PASS"
202
+ return 0
203
+ }
204
+
205
+ if $SELF_TEST || [[ ${#TARGETS[@]} -eq 0 ]]; then
206
+ ste_run_self_test
207
+ exit $?
208
+ fi
209
+
210
+ if $AUDIT; then
211
+ STRICT=false
212
+ fi
213
+
214
+ for target in "${TARGETS[@]}"; do
215
+ while IFS= read -r f; do
216
+ [[ -n "$f" ]] && ste_audit_file "$f"
217
+ done < <(ste_collect_files "$target")
218
+ done
219
+
220
+ if (( VIOLATIONS > 0 )); then
221
+ echo "validate-agentic-ste: $VIOLATIONS violation(s)" >&2
222
+ exit 1
223
+ fi
224
+
225
+ if $AUDIT; then
226
+ echo "validate-agentic-ste: audit complete ($WARNINGS warning(s))"
227
+ else
228
+ echo "validate-agentic-ste: PASS"
229
+ fi
@@ -1,5 +1,6 @@
1
1
  # story: e45s02
2
2
  <!-- story: e45s12 -->
3
+ <!-- story: e79s02 -->
3
4
  ---
4
5
  name: craft-skill
5
6
  model: sonnet
@@ -9,7 +10,7 @@ description: Create new bigpowers skills with proper structure, progressive disc
9
10
 
10
11
  # Craft Skill
11
12
 
12
- > **HARD GATE** — Do NOT name a skill without a two-word verb-noun pair. Do NOT merge a new skill without running `sync-skills.sh` — the generated `.cursor/rules/` and `.gemini/` artifacts must match the source SKILL.md.
13
+ > **HARD GATE** — Do NOT name a skill without a two-word verb-noun pair. Do NOT merge a new skill without running `sync-skills.sh`. Generated `.cursor/rules/` and `.gemini/` artifacts MUST match the source SKILL.md.
13
14
 
14
15
  ## CSO Description Discipline (e45s02)
15
16
 
@@ -24,11 +25,25 @@ The YAML `description` is the **Catalog Selection Object** — the only field ag
24
25
 
25
26
  Move process detail into the SKILL.md body or REFERENCE.md — never into `description`.
26
27
 
28
+ ## Agentic STE body discipline (e79s02)
29
+
30
+ Skill-body instructional prose MUST follow [AGENTIC-STE.md](../../docs/AGENTIC-STE.md).
31
+
32
+ | Rule | Limit |
33
+ |------|-------|
34
+ | Sentence length | ≤20 words per instruction sentence |
35
+ | Voice | Imperative, active |
36
+ | Directive terms | MUST, MUST NOT, NEVER, ALWAYS, DO, DO NOT |
37
+ | Banned modals | should, might, could, may, consider, try, generally, typically |
38
+ | Scope | SKILL.md body only — not YAML `description`, not `terse-mode` output |
39
+
40
+ > **HARD GATE** — Do NOT merge a new or edited skill until `bash scripts/validate-agentic-ste.sh --strict skills/<name>/SKILL.md` exits 0. Fix violations before `sync-skills.sh`.
41
+
27
42
  ## Process
28
43
 
29
44
  1. **Gather requirements** — ask user about:
30
45
  - What task/domain does the skill cover?
31
- - What specific use cases should it handle?
46
+ - Which use cases must the skill handle?
32
47
  - Does it need executable scripts or just instructions?
33
48
  - Any reference materials to include?
34
49
  - What specs/ output does it produce (if any)?
@@ -44,7 +59,8 @@ Move process detail into the SKILL.md body or REFERENCE.md — never into `descr
44
59
  - Additional reference files if content exceeds 100 lines
45
60
  - Utility scripts if deterministic operations needed
46
61
 
47
- **Auto-skill from library README:** When user provides a library README or API docs URL, extract: triggers, HARD GATEs, verify commands, specs/ output — draft SKILL.md without inventing APIs not in the source.
62
+ **Auto-skill from library README:** When user provides a library README or API docs URL, extract triggers and HARD GATEs.
63
+ Draft verify commands and specs/ output into SKILL.md. Do NOT invent APIs not in the source.
48
64
 
49
65
  4. Add `model:` frontmatter (`haiku` | `sonnet` | `opus`) per [model-profiles.md](https://github.com/danielvm-git/bigpowers/blob/main/docs/references/model-profiles.md).
50
66
 
@@ -53,10 +69,11 @@ Move process detail into the SKILL.md body or REFERENCE.md — never into `descr
53
69
  5. **Review with user** — present draft and ask:
54
70
  - Does this cover your use cases?
55
71
  - Anything missing or unclear?
56
- - Should any section be more/less detailed?
72
+ - Does any section need more or less detail?
57
73
 
58
74
  6. **Completion-honesty gate (HARD GATE — e45s02)** — Before declaring done:
59
75
  - Run `bash scripts/validate-skill-description.sh skills/<name>/SKILL.md` — must exit 0
76
+ - Run `bash scripts/validate-agentic-ste.sh --strict skills/<name>/SKILL.md` — must exit 0 (e79s02)
60
77
  - Run `bash scripts/sync-skills.sh` — must complete without error
61
78
  - Run `bash scripts/run-skill-verify.sh <name>` if the skill defines a verify command
62
79
  - Show terminal output for each — narration without evidence is rejected
@@ -81,6 +98,7 @@ After drafting, verify:
81
98
  - [ ] Consistent terminology with CONVENTIONS.md
82
99
  - [ ] specs/ output documented if applicable
83
100
  - [ ] `validate-skill-description.sh` exits 0
101
+ - [ ] `validate-agentic-ste.sh --strict` exits 0 (e79s02)
84
102
  - [ ] `sync-skills.sh` run to propagate to Cursor/Gemini
85
103
  - [ ] `bash scripts/validate-skill-catalog.sh` passes for the new skill (HARD GATE — completion honesty)
86
104
 
@@ -10,6 +10,7 @@ description: Generate CLAUDE.md and CONVENTIONS.md for a brand-new project throu
10
10
  # story: e10s02
11
11
  # story: e51s02
12
12
  # story: e45s21
13
+ # story: e79s03
13
14
 
14
15
 
15
16
  # Seed Conventions
@@ -33,13 +34,25 @@ Ask the user these questions (one at a time, wait for each answer):
33
34
  2. **Stack** — "What language, framework, and runtime? (e.g. TypeScript / Next.js / Node 22)"
34
35
  2b. **Stack profile (optional)** — Offer: `swift`, `typescript-vue`, `node-service`, or none. If chosen, merge the matching fragment from `profiles/<name>.md` into generated `CONVENTIONS.md`.
35
36
  3. **Commands** — "What commands do you use for: run, test, build, lint?"
36
- 3b. **Preflight (optional)** — "What single command runs your full local green stack (test + lint + build)? If you don't have one, we'll chain your Test + Lint + Build answers into a **Preflight** row in the Commands table."
37
+ 3b. **Preflight (optional)** — "What command runs test, lint, and build together? If none, chain Test + Lint + Build into one **Preflight** row."
37
38
  4. **Architecture** — "Key modules and relationships in 1–2 sentences."
38
39
  5. **Conventions** — "Any naming, file organization, or patterns all agents must follow?"
39
40
  6. **Never-do list** — "What are the hard stops? Things an agent must never touch?"
40
41
  7. **Defensive code categories** — "Which apply? (Rate limit / Retry / Circuit breaker / Timeout / Graceful degradation)"
41
42
  8. **Local tool wiring (optional)** — "Wire bigpowers for project-local tools? (Cursor, OpenCode, Cline, Aider, Codex CLI)" If yes, generate AGENTS.md spine artifacts per [REFERENCE.md](REFERENCE.md) §Local tool wiring and §AGENTS.md spine. If no, skip — standard seed output unchanged (no AGENTS.md spine unless opted in).
42
43
 
44
+ ## Agentic STE for generated prose (e79s03)
45
+
46
+ When writing instructional lines in `CLAUDE.md`, `AGENTS.md`, or `CONVENTIONS.md`, follow [AGENTIC-STE.md](../../docs/AGENTIC-STE.md):
47
+
48
+ - Use directive vocabulary: MUST, MUST NOT, NEVER, ALWAYS, DO, DO NOT
49
+ - Do NOT use hedge modals listed in AGENTIC-STE.md Rule 3
50
+ - Cap each instruction sentence at 20 words
51
+ - Write imperative, active-voice lines — one instruction per line
52
+ - Do NOT apply Agentic STE to `terse-mode` (output compression is out of scope)
53
+
54
+ After generation, run `bash scripts/validate-agentic-ste.sh --strict CLAUDE.md CONVENTIONS.md` when those files exist in the target project.
55
+
43
56
  ## Generate files
44
57
 
45
58
  After the interview, generate each file using the templates in [REFERENCE.md](REFERENCE.md):
@@ -66,9 +79,11 @@ echo "# Specs\n\nAll planning documents for this project." > specs/README.md
66
79
 
67
80
  **Note:** `specs/state.yaml.lock` is NOT pre-created — acquired/released dynamically.
68
81
 
69
- `specs/state.yaml` carries a top-level `workflow_mode` key (`team-pr` | `solo-git`, default `solo-git`). This is the **canonical integrate-mode signal** for all skills — set it once here and skills such as `release-branch` read it from this file instead of sniffing profile files.
82
+ `specs/state.yaml` carries top-level `workflow_mode` (`team-pr` | `solo-git`, default `solo-git`).
83
+ This is the **canonical integrate-mode signal** for all skills.
84
+ Set it once here. Skills such as `release-branch` read this file instead of sniffing profiles.
70
85
 
71
- When generating `CLAUDE.md`, if the user did not name a Preflight command, chain the Test + Lint + Build interview answers into one **Preflight** row (e.g. `npm test && npm run lint && npm run build`).
86
+ When generating `CLAUDE.md`, chain Test + Lint + Build into one **Preflight** row if the user named no Preflight command.
72
87
 
73
88
  ### Self-installing fenced markers (e45s21)
74
89
 
@@ -80,7 +95,9 @@ Skills that write into `CLAUDE.md` or `AGENTS.md` MUST use **fenced HTML comment
80
95
  <!-- END bigpowers:section-id -->
81
96
  ```
82
97
 
83
- **Merge rule:** On update, replace only the content *between* matching `BEGIN`/`END` pairs. If a marker pair is missing, append a new fenced block at the end of the file — never rewrite the whole file.
98
+ **Merge rule:** On update, replace only content between matching `BEGIN`/`END` pairs.
99
+ If a marker pair is missing, append a new fenced block at file end.
100
+ Never rewrite the whole file.
84
101
 
85
102
  **Standard marker IDs** for seeded projects (see [REFERENCE.md](REFERENCE.md) § Fenced markers):
86
103
 
@@ -9,6 +9,7 @@ effort: standard
9
9
 
10
10
  # story: e09s01
11
11
  <!-- story: e45s12 -->
12
+ <!-- story: e79s04 -->
12
13
 
13
14
 
14
15
  # Stocktake Skills
@@ -28,6 +29,7 @@ Audit SKILL.md catalog for drift, stale triggers, missing HARD GATEs, and INDEX
28
29
  ## Process
29
30
 
30
31
  0. **Catalog validator (code-enforced)** — run `bash scripts/validate-skill-catalog.sh`. Any FAIL is a critical finding before manual review. Add `--archive` to list zero-usage skills from `metrics.skill_timings` for auto-archiving candidates (move to `specs/epics/archive/` or delete after user confirms).
32
+ 0b. **Agentic STE audit (e79s04)** — run `bash scripts/validate-agentic-ste.sh --audit skills/`. Flag hedge words and sentences over 20 words per [AGENTIC-STE.md](../../docs/AGENTIC-STE.md). Record WARN lines in the stocktake report as **STE debt** — do NOT rewrite the full catalog in one pass. Route critical repeat offenders to `evolve-skill`.
31
33
  1. Run `bash scripts/audit-catalog.sh` to verify pi/skills ↔ source SKILL.md sync. Mismatch is a critical finding.
32
34
  2. Run mode; for each skill check: exists, verb-noun, &lt;300 lines total, HARD GATE present, INDEX row matches.
33
35
  3. Write `specs/STOCKTAKE-<date>.md` with findings table (skill, issue, severity).
package/skills-lock.json CHANGED
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "craft-skill": {
50
50
  "description": "Create new bigpowers skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill for the bigpowers lifecycle.",
51
- "sha256": "2caa9d8ae9298b4b",
51
+ "sha256": "07d1cc8eddca50c5",
52
52
  "path": "skills/craft-skillSKILL.md"
53
53
  },
54
54
  "deepen-architecture": {
@@ -308,7 +308,7 @@
308
308
  },
309
309
  "seed-conventions": {
310
310
  "description": "Generate CLAUDE.md and CONVENTIONS.md for a brand-new project through a brief interview, and create the specs/ directory with evolved bigpowers structure (product/, tech-architecture/, verifications/, epics/archive/). Entry point for greenfield projects. Use when starting a new project from scratch, when user asks to set up AI agent conventions, or when there is no CLAUDE.md yet.",
311
- "sha256": "35c335385c4ad48b",
311
+ "sha256": "6cb91bf0b5cdd9b3",
312
312
  "path": "skills/seed-conventionsSKILL.md"
313
313
  },
314
314
  "session-state": {
@@ -343,7 +343,7 @@
343
343
  },
344
344
  "stocktake-skills": {
345
345
  "description": "Sequential subagent batch audit of the bigpowers skill catalog — Quick Scan (changed only) or Full (all skills). Use during sustain phase, before a major release, or when catalog drift is suspected.",
346
- "sha256": "6833c900df48a98a",
346
+ "sha256": "9595b954341217d7",
347
347
  "path": "skills/stocktake-skillsSKILL.md"
348
348
  },
349
349
  "survey-context": {
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env bash
2
+ # story: BUG-2026-07-25-land-branch-protected
3
+ # Regression: land-branch protected-branch push rejection + PR fallback.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
8
+ # shellcheck source=../scripts/lib/land-branch-push.sh
9
+ source "$REPO_ROOT/scripts/lib/land-branch-push.sh"
10
+
11
+ pass_count=0
12
+ fail_count=0
13
+
14
+ assert_true() {
15
+ local label="$1"
16
+ shift
17
+ if "$@"; then
18
+ echo " PASS $label"
19
+ pass_count=$((pass_count + 1))
20
+ else
21
+ echo " FAIL $label"
22
+ fail_count=$((fail_count + 1))
23
+ fi
24
+ }
25
+
26
+ assert_false() {
27
+ local label="$1"
28
+ shift
29
+ if ! "$@"; then
30
+ echo " PASS $label"
31
+ pass_count=$((pass_count + 1))
32
+ else
33
+ echo " FAIL $label"
34
+ fail_count=$((fail_count + 1))
35
+ fi
36
+ }
37
+
38
+ assert_contains() {
39
+ local label="$1"
40
+ local haystack="$2"
41
+ local needle="$3"
42
+ if [[ "$haystack" == *"$needle"* ]]; then
43
+ echo " PASS $label"
44
+ pass_count=$((pass_count + 1))
45
+ else
46
+ echo " FAIL $label (missing: $needle)"
47
+ fail_count=$((fail_count + 1))
48
+ fi
49
+ }
50
+
51
+ echo "=== is_protected_branch_rejection fixtures ==="
52
+
53
+ GH006_FIXTURE=$'remote: error: GH006: Protected branch update failed for refs/heads/main.\nremote: - Changes must be made through a pull request.\nerror: failed to push some refs'
54
+ PR_ONLY_FIXTURE=$'remote: - Changes must be made through a pull request.\nerror: failed to push some refs'
55
+ UNRELATED_FIXTURE=$'error: failed to push some refs\nremote: Permission denied'
56
+
57
+ assert_true "detects GH006 rejection" is_protected_branch_rejection "$GH006_FIXTURE"
58
+ assert_true "detects PR-required message without GH006 code" is_protected_branch_rejection "$PR_ONLY_FIXTURE"
59
+ assert_false "ignores unrelated push failure" is_protected_branch_rejection "$UNRELATED_FIXTURE"
60
+
61
+ echo "=== temp bare remote with rejecting pre-receive hook ==="
62
+
63
+ TMPDIR_ROOT="$(mktemp -d)"
64
+ trap 'rm -rf "$TMPDIR_ROOT"' EXIT
65
+
66
+ BARE="$TMPDIR_ROOT/remote.git"
67
+ WORK="$TMPDIR_ROOT/work"
68
+ MOCK_BIN="$TMPDIR_ROOT/bin"
69
+ mkdir -p "$MOCK_BIN"
70
+
71
+ git init --bare "$BARE" >/dev/null
72
+ mkdir -p "$BARE/hooks"
73
+ cat >"$BARE/hooks/pre-receive" <<'HOOK'
74
+ #!/bin/sh
75
+ while read -r oldrev newrev refname; do
76
+ case "$refname" in
77
+ refs/heads/main)
78
+ if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
79
+ continue
80
+ fi
81
+ echo "remote: error: GH006: Protected branch update failed for refs/heads/main."
82
+ echo "remote: - Changes must be made through a pull request."
83
+ exit 1
84
+ ;;
85
+ esac
86
+ done
87
+ exit 0
88
+ HOOK
89
+ chmod +x "$BARE/hooks/pre-receive"
90
+
91
+ git clone "$BARE" "$WORK" >/dev/null
92
+ git -C "$WORK" config user.email "test@example.com"
93
+ git -C "$WORK" config user.name "test"
94
+ echo "base" >"$WORK/README.md"
95
+ git -C "$WORK" add README.md
96
+ git -C "$WORK" commit -qm "chore: init"
97
+ git -C "$WORK" push origin main >/dev/null
98
+
99
+ git -C "$WORK" checkout -b feat/test >/dev/null
100
+ echo "feature" >>"$WORK/README.md"
101
+ git -C "$WORK" add README.md
102
+ git -C "$WORK" commit -qm "feat: add feature"
103
+ git -C "$WORK" checkout main >/dev/null
104
+ git -C "$WORK" merge --squash feat/test >/dev/null
105
+ git -C "$WORK" commit -qm "feat(test): squash land"
106
+ LAND_SHA="$(git -C "$WORK" rev-parse --short HEAD)"
107
+ LAND_FULL_SHA="$(git -C "$WORK" rev-parse HEAD)"
108
+ RECOVERY_BRANCH="land-recovery/feat/test"
109
+
110
+ cat >"$MOCK_BIN/gh" <<'MOCK'
111
+ #!/usr/bin/env bash
112
+ echo "https://github.com/example/repo/pull/1"
113
+ exit 0
114
+ MOCK
115
+ chmod +x "$MOCK_BIN/gh"
116
+
117
+ cd "$WORK"
118
+ export PATH="$MOCK_BIN:$PATH"
119
+ LAND_PR_FALLBACK=0
120
+ if ! land_push_default_branch main "$LAND_SHA" "feat/test" "feat(test): squash land"; then
121
+ echo " FAIL land_push_default_branch should succeed via PR fallback"
122
+ fail_count=$((fail_count + 1))
123
+ else
124
+ echo " PASS land_push_default_branch succeeds on protected rejection"
125
+ pass_count=$((pass_count + 1))
126
+ fi
127
+
128
+ if [ "$LAND_PR_FALLBACK" = "1" ]; then
129
+ echo " PASS LAND_PR_FALLBACK flag set"
130
+ pass_count=$((pass_count + 1))
131
+ else
132
+ echo " FAIL LAND_PR_FALLBACK flag not set"
133
+ fail_count=$((fail_count + 1))
134
+ fi
135
+
136
+ if git show-ref --verify --quiet "refs/heads/$RECOVERY_BRANCH"; then
137
+ echo " PASS recovery branch exists locally"
138
+ pass_count=$((pass_count + 1))
139
+ else
140
+ echo " FAIL recovery branch missing locally"
141
+ fail_count=$((fail_count + 1))
142
+ fi
143
+
144
+ if git rev-parse main >/dev/null 2>&1 && [ "$(git rev-parse main)" != "$LAND_FULL_SHA" ]; then
145
+ echo " PASS local main reset away from stranded squash commit"
146
+ pass_count=$((pass_count + 1))
147
+ else
148
+ echo " FAIL local main still points at squash commit"
149
+ fail_count=$((fail_count + 1))
150
+ fi
151
+
152
+ if git ls-remote --heads origin "$RECOVERY_BRANCH" | grep -q "$RECOVERY_BRANCH"; then
153
+ echo " PASS recovery branch pushed to origin"
154
+ pass_count=$((pass_count + 1))
155
+ else
156
+ echo " FAIL recovery branch not on origin"
157
+ fail_count=$((fail_count + 1))
158
+ fi
159
+ cd "$REPO_ROOT"
160
+
161
+ echo "=== gh failure prints recovery commands ==="
162
+
163
+ BARE2="$TMPDIR_ROOT/remote2.git"
164
+ WORK2="$TMPDIR_ROOT/work2"
165
+ git init --bare "$BARE2" >/dev/null
166
+ mkdir -p "$BARE2/hooks"
167
+ cp "$BARE/hooks/pre-receive" "$BARE2/hooks/pre-receive"
168
+ chmod +x "$BARE2/hooks/pre-receive"
169
+
170
+ cat >"$MOCK_BIN/gh" <<'MOCK'
171
+ #!/usr/bin/env bash
172
+ echo "gh: authentication required" >&2
173
+ exit 1
174
+ MOCK
175
+ chmod +x "$MOCK_BIN/gh"
176
+
177
+ git clone "$BARE2" "$WORK2" >/dev/null
178
+ git -C "$WORK2" config user.email "test@example.com"
179
+ git -C "$WORK2" config user.name "test"
180
+ echo "base" >"$WORK2/README.md"
181
+ git -C "$WORK2" add README.md
182
+ git -C "$WORK2" commit -qm "chore: init"
183
+ git -C "$WORK2" push origin main >/dev/null
184
+ git -C "$WORK2" checkout -b feat/broken >/dev/null
185
+ echo "feature" >>"$WORK2/README.md"
186
+ git -C "$WORK2" add README.md
187
+ git -C "$WORK2" commit -qm "feat: broken land"
188
+ git -C "$WORK2" checkout main >/dev/null
189
+ git -C "$WORK2" merge --squash feat/broken >/dev/null
190
+ git -C "$WORK2" commit -qm "feat(broken): squash land"
191
+ BROKEN_SHA="$(git -C "$WORK2" rev-parse --short HEAD)"
192
+ BROKEN_FULL_SHA="$(git -C "$WORK2" rev-parse HEAD)"
193
+ BROKEN_RECOVERY="land-recovery/feat/broken"
194
+
195
+ cd "$WORK2"
196
+ export PATH="$MOCK_BIN:$PATH"
197
+ set +e
198
+ stderr_file="$(mktemp)"
199
+ land_push_default_branch main "$BROKEN_SHA" "feat/broken" "feat(broken): squash land" 2>"$stderr_file"
200
+ status=$?
201
+ set -e
202
+ stderr="$(cat "$stderr_file")"
203
+ rm -f "$stderr_file"
204
+
205
+ if [ "$status" -ne 0 ]; then
206
+ echo " PASS land_push_default_branch exits non-zero when gh fails"
207
+ pass_count=$((pass_count + 1))
208
+ else
209
+ echo " FAIL expected non-zero exit when gh fails"
210
+ fail_count=$((fail_count + 1))
211
+ fi
212
+
213
+ assert_contains "recovery commands mention git branch" "$stderr" "git branch $BROKEN_RECOVERY $BROKEN_FULL_SHA"
214
+ assert_contains "recovery commands mention gh pr create" "$stderr" "gh pr create --base main --head $BROKEN_RECOVERY"
215
+ cd "$REPO_ROOT"
216
+
217
+ echo ""
218
+ echo "Results: $pass_count passed, $fail_count failed"
219
+ if [ "$fail_count" -gt 0 ]; then
220
+ exit 1
221
+ fi
222
+ echo "All land-branch protected-branch tests passed."