bigpowers 2.88.12 → 2.88.14
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/.agents/skills/gate-trace/SKILL.md +2 -2
- package/.cline/skills/gate-trace/SKILL.md +2 -2
- package/.codebuddy/skills/gate-trace/SKILL.md +2 -2
- package/.codex/skills/gate-trace/SKILL.md +2 -2
- package/.copilot/skills/gate-trace/SKILL.md +2 -2
- package/.cursor/rules/gate-trace.mdc +2 -2
- package/.gemini/extensions/bigpowers/commands/prompts/gate-trace.md +2 -2
- package/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
- package/.gemini/extensions/bigpowers/skills/gate-trace/SKILL.md +2 -2
- package/.hermes/skills/gate-trace/SKILL.md +2 -2
- package/.kilocode/rules/gate-trace.md +2 -2
- package/.mimocode/skills/gate-trace/SKILL.md +2 -2
- package/.opencode/skills/gate-trace/SKILL.md +2 -2
- package/.pi/package.json +1 -1
- package/.pi/prompts/gate-trace.md +2 -2
- package/.pi/skills/gate-trace/SKILL.md +2 -2
- package/.qwen/skills/gate-trace/SKILL.md +2 -2
- package/.trae/skills/gate-trace/SKILL.md +2 -2
- package/.windsurf/rules/gate-trace.md +2 -2
- package/.zcode/skills/gate-trace/SKILL.md +2 -2
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/scripts/golden-g13-script-orphans.sh +7 -5
- package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
- package/scripts/lib/golden-suite-gates.sh +43 -3
- package/scripts/run-story-verify.sh +112 -6
- package/scripts/test-ci-pipeline-contract.sh +162 -0
- package/scripts/test-golden-gate-runner.sh +59 -0
- package/skills/gate-trace/SKILL.md +2 -2
- package/skills-lock.json +1 -1
- package/scripts/lib/__pycache__/link_utils.cpython-314.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-314.pyc +0 -0
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -69,8 +69,8 @@ gate_trace:
|
|
|
69
69
|
## Integration Points
|
|
70
70
|
|
|
71
71
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
72
|
-
- **
|
|
73
|
-
blocks
|
|
72
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
73
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
74
74
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
75
75
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
76
76
|
|
|
@@ -69,8 +69,8 @@ gate_trace:
|
|
|
69
69
|
## Integration Points
|
|
70
70
|
|
|
71
71
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
72
|
-
- **
|
|
73
|
-
blocks
|
|
72
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
73
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
74
74
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
75
75
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
76
76
|
|
|
@@ -71,8 +71,8 @@ gate_trace:
|
|
|
71
71
|
## Integration Points
|
|
72
72
|
|
|
73
73
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
74
|
-
- **
|
|
75
|
-
blocks
|
|
74
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
75
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
76
76
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
77
77
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
78
78
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
package/.pi/package.json
CHANGED
|
@@ -69,8 +69,8 @@ gate_trace:
|
|
|
69
69
|
## Integration Points
|
|
70
70
|
|
|
71
71
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
72
|
-
- **
|
|
73
|
-
blocks
|
|
72
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
73
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
74
74
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
75
75
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
76
76
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
|
@@ -70,8 +70,8 @@ gate_trace:
|
|
|
70
70
|
## Integration Points
|
|
71
71
|
|
|
72
72
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
73
|
-
- **
|
|
74
|
-
blocks
|
|
73
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
74
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
75
75
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
76
76
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
77
77
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.88.14](https://github.com/danielvm-git/bigpowers/compare/v2.88.13...v2.88.14) (2026-10-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ci:** gate release on every check in one test-build-release pipeline ([#162](https://github.com/danielvm-git/bigpowers/issues/162)) ([14250d8](https://github.com/danielvm-git/bigpowers/commit/14250d843011da03f766de59f18371276fe16478))
|
|
7
|
+
|
|
8
|
+
## [2.88.13](https://github.com/danielvm-git/bigpowers/compare/v2.88.12...v2.88.13) (2026-10-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ci:** cut golden suite critical path by removing duplicate gate work ([#161](https://github.com/danielvm-git/bigpowers/issues/161)) ([2db0bb9](https://github.com/danielvm-git/bigpowers/commit/2db0bb98663ae7f44e1990f06e9f90b6db60a773))
|
|
14
|
+
|
|
1
15
|
## [2.88.12](https://github.com/danielvm-git/bigpowers/compare/v2.88.11...v2.88.12) (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
|
|
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` |
|
|
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
|
@@ -53,12 +53,14 @@ is_manual_utility() { # $1 = script path
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
is_referenced() { # $1 = script basename, $2... = candidate file globs already expanded
|
|
56
|
+
# One grep over all candidate files per script, not one per (script, file)
|
|
57
|
+
# pair — the per-file loop forked ~100×N greps and took ~1 min in CI.
|
|
56
58
|
local name="$1"; shift
|
|
57
59
|
local f
|
|
58
|
-
|
|
59
|
-
[[ "$f" == *"/$name" || "$
|
|
60
|
-
|
|
61
|
-
done
|
|
60
|
+
while IFS= read -r f; do
|
|
61
|
+
[[ "$f" == *"/$name" || "${f##*/}" == "$name" ]] && continue # self
|
|
62
|
+
return 0
|
|
63
|
+
done < <(grep -lF -- "$name" "$@" 2>/dev/null)
|
|
62
64
|
return 1
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -161,7 +163,7 @@ if [[ -z "$ORPHANS" ]]; then
|
|
|
161
163
|
fi
|
|
162
164
|
|
|
163
165
|
echo " orphaned scripts (present but reachable from no gate, doc, or workflow):"
|
|
164
|
-
printf '%s' "$ORPHANS"
|
|
166
|
+
printf '%s\n' "$ORPHANS"
|
|
165
167
|
echo -e "${RED}FAIL${NC} wire them into a gate, reference them from a skill/doc, tag them '# bp-manual-utility: <reason>', or delete them"
|
|
166
168
|
echo "G-13: FAIL"
|
|
167
169
|
exit 1
|
|
Binary file
|
|
Binary file
|
|
@@ -72,8 +72,13 @@ GOLDEN_GATES=(
|
|
|
72
72
|
"import-boundaries:bash scripts/check-import-boundaries.sh:false"
|
|
73
73
|
"skill-catalog:bash scripts/validate-skill-catalog.sh:false"
|
|
74
74
|
"specs-parse:bash scripts/validate-specs-yaml.sh:false"
|
|
75
|
+
# The runner itself: failing output is shown, skips are named and reasoned.
|
|
76
|
+
"golden-gate-runner:bash scripts/test-golden-gate-runner.sh:false"
|
|
77
|
+
# One pipeline, release needs: every gate, pinned runner and actions.
|
|
78
|
+
"ci-pipeline-contract-selftest:bash scripts/test-ci-pipeline-contract.sh --self-test:false"
|
|
79
|
+
"ci-pipeline-contract:bash scripts/test-ci-pipeline-contract.sh:false"
|
|
75
80
|
# Tier 2 of the verify arc (#106): executes the verify: of every story marked
|
|
76
|
-
# done. Tier 1 (SKILL.md) is run-skill-verify.sh in
|
|
81
|
+
# done. Tier 1 (SKILL.md) is run-skill-verify.sh in test-build-release.yml.
|
|
77
82
|
"story-verify-selftest:bash scripts/run-story-verify.sh --self-test:false"
|
|
78
83
|
"story-verify:bash scripts/run-story-verify.sh:false"
|
|
79
84
|
)
|
|
@@ -120,15 +125,48 @@ golden_agent_dry_run() {
|
|
|
120
125
|
return 0
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
# GOLDEN_SKIP_GATES (comma list) skips gates another CI job already runs —
|
|
129
|
+
# test-build-release.yml skips story-verify because its story-verify job runs the
|
|
130
|
+
# same command. Local Preflight leaves it unset, so every gate runs there. A
|
|
131
|
+
# name that matches no gate is a FAIL: a typo must not look like a skip.
|
|
132
|
+
golden_is_skipped() { # $1 = gate name
|
|
133
|
+
case ",${GOLDEN_SKIP_GATES:-}," in *",$1,"*) return 0 ;; esac
|
|
134
|
+
return 1
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
golden_check_skip_names() {
|
|
138
|
+
local skip gate known
|
|
139
|
+
for skip in $(printf '%s' "${GOLDEN_SKIP_GATES:-}" | tr ',' ' '); do
|
|
140
|
+
[[ -z "$skip" ]] && continue
|
|
141
|
+
known=false
|
|
142
|
+
for gate in "${GOLDEN_GATES[@]}"; do
|
|
143
|
+
[[ "${gate%%:*}" == "$skip" ]] && known=true
|
|
144
|
+
done
|
|
145
|
+
$known && continue
|
|
146
|
+
echo -e "[$skip] ${GOLDEN_RED}FAIL${GOLDEN_NC} (GOLDEN_SKIP_GATES names no such gate)"
|
|
147
|
+
GATE_RESULTS+=(" - name: $skip" " exit_code: 2" " duration_seconds: 0" " status: fail" " error: unknown gate in GOLDEN_SKIP_GATES")
|
|
148
|
+
(( FAIL_COUNT += 1 ))
|
|
149
|
+
done
|
|
150
|
+
}
|
|
151
|
+
|
|
123
152
|
golden_run_deterministic_gates() {
|
|
124
|
-
local gate name cmd is_optional main_cmd script_path
|
|
153
|
+
local gate name cmd is_optional main_cmd script_path gate_out
|
|
125
154
|
PASS_COUNT=0 FAIL_COUNT=0 SKIP_COUNT=0
|
|
126
155
|
GATE_RESULTS=()
|
|
156
|
+
golden_check_skip_names
|
|
127
157
|
|
|
128
158
|
for gate in "${GOLDEN_GATES[@]}"; do
|
|
129
159
|
IFS=':' read -r name cmd is_optional <<< "$gate"
|
|
130
160
|
echo -n "[$name] "
|
|
131
161
|
|
|
162
|
+
if golden_is_skipped "$name"; then
|
|
163
|
+
local reason="${GOLDEN_SKIP_REASON:-listed in GOLDEN_SKIP_GATES}"
|
|
164
|
+
echo -e "${GOLDEN_YELLOW}SKIP${GOLDEN_NC} ($reason)"
|
|
165
|
+
GATE_RESULTS+=(" - name: $name" " exit_code: skipped" " duration_seconds: 0" " status: skipped" " note: $reason")
|
|
166
|
+
(( SKIP_COUNT += 1 ))
|
|
167
|
+
continue
|
|
168
|
+
fi
|
|
169
|
+
|
|
132
170
|
main_cmd=$(echo "$cmd" | awk '{print $1}')
|
|
133
171
|
if [[ "$main_cmd" == "bash" ]]; then
|
|
134
172
|
script_path=$(echo "$cmd" | awk '{print $2}')
|
|
@@ -148,7 +186,7 @@ golden_run_deterministic_gates() {
|
|
|
148
186
|
|
|
149
187
|
local START_TIME END_TIME DURATION_MS DURATION_SEC GATE_EXIT
|
|
150
188
|
START_TIME=$(date +%s%N)
|
|
151
|
-
eval "$cmd"
|
|
189
|
+
gate_out=$(eval "$cmd" 2>&1) && GATE_EXIT=$? || GATE_EXIT=$?
|
|
152
190
|
END_TIME=$(date +%s%N)
|
|
153
191
|
DURATION_MS=$(( (END_TIME - START_TIME) / 1000000 ))
|
|
154
192
|
DURATION_SEC=$(echo "scale=2; $DURATION_MS / 1000" | bc 2>/dev/null || echo "0")
|
|
@@ -159,6 +197,8 @@ golden_run_deterministic_gates() {
|
|
|
159
197
|
(( PASS_COUNT += 1 ))
|
|
160
198
|
else
|
|
161
199
|
echo -e "${GOLDEN_RED}FAIL${GOLDEN_NC} (${DURATION_SEC}s, exit $GATE_EXIT)"
|
|
200
|
+
# Without the tail, CI shows FAIL with no reason (#158: compliance).
|
|
201
|
+
printf '%s\n' "$gate_out" | tail -20 | sed 's/^/ | /'
|
|
162
202
|
GATE_RESULTS+=(" - name: $name" " exit_code: $GATE_EXIT" " duration_seconds: $DURATION_SEC" " status: fail")
|
|
163
203
|
(( FAIL_COUNT += 1 ))
|
|
164
204
|
fi
|
|
@@ -41,7 +41,7 @@ PASS=0; FAIL=0; SKIP=0
|
|
|
41
41
|
TARGET="${1:-}"
|
|
42
42
|
|
|
43
43
|
extract_records() {
|
|
44
|
-
"$PYTHON" "$REPO_ROOT/scripts/lib/extract-story-verify.py" "$REPO_ROOT"
|
|
44
|
+
"$PYTHON" "$REPO_ROOT/scripts/lib/extract-story-verify.py" "${EXTRACT_ROOT:-$REPO_ROOT}"
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
# Report a story whose directive can never fail. Kept separate from execution so
|
|
@@ -58,6 +58,55 @@ reject_non_executable() {
|
|
|
58
58
|
FAIL=$((FAIL + 1))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
# Many stories share one verify (e61s02…e76s02 all run the same installer
|
|
62
|
+
# test); same command at the same commit gives the same result, so run it once
|
|
63
|
+
# per gate_all and reuse the verdict for every story that declares it.
|
|
64
|
+
# Sets RUN_RC, RUN_OUT, and RUN_REUSED (1 when nothing new ran).
|
|
65
|
+
run_memoized() {
|
|
66
|
+
local cmd="$1" key
|
|
67
|
+
key=$(printf '%s' "$cmd" | cksum | tr -d ' ')
|
|
68
|
+
if [ -n "${MEMO_DIR:-}" ] && [ -f "$MEMO_DIR/$key.rc" ]; then
|
|
69
|
+
RUN_RC=$(cat "$MEMO_DIR/$key.rc")
|
|
70
|
+
RUN_OUT=$(cat "$MEMO_DIR/$key.out")
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
RUN_REUSED=0
|
|
74
|
+
RUN_OUT=$(run_with_timeout "$cmd") && RUN_RC=0 || RUN_RC=$?
|
|
75
|
+
if [ -n "${MEMO_DIR:-}" ]; then
|
|
76
|
+
printf '%s' "$RUN_RC" > "$MEMO_DIR/$key.rc"
|
|
77
|
+
printf '%s' "$RUN_OUT" > "$MEMO_DIR/$key.out"
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# A plain `a && b && c` chain shares no shell state between segments when it
|
|
82
|
+
# has no quoting, expansion, grouping, pipes, `;`, cd, export or assignment —
|
|
83
|
+
# so each segment can be cached on its own (test-adapter-render.sh prefixes ten
|
|
84
|
+
# different story verifies). Anything else is cached as one whole command.
|
|
85
|
+
is_plain_and_chain() {
|
|
86
|
+
case "$1" in
|
|
87
|
+
*\'*|*\"*|*\$*|*\`*|*\(*|*\)*|*\{*|*\}*|*\|*|*\;*|*=*|*\<*) return 1 ;;
|
|
88
|
+
esac
|
|
89
|
+
printf '%s' "$1" | grep -qE '(^|&&)[[:space:]]*(cd|export|source|\.)[[:space:]]' && return 1
|
|
90
|
+
return 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
run_verify_cmd() {
|
|
94
|
+
local cmd="$1" seg
|
|
95
|
+
RUN_REUSED=1
|
|
96
|
+
if ! is_plain_and_chain "$cmd"; then
|
|
97
|
+
run_memoized "$cmd"
|
|
98
|
+
return
|
|
99
|
+
fi
|
|
100
|
+
local rest="$cmd"
|
|
101
|
+
while :; do
|
|
102
|
+
seg=${rest%%&&*}
|
|
103
|
+
run_memoized "$(printf '%s' "$seg" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
104
|
+
[ "$RUN_RC" -eq 0 ] || return
|
|
105
|
+
[ "$seg" = "$rest" ] && return
|
|
106
|
+
rest=${rest#*&&}
|
|
107
|
+
done
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
run_story_verify() {
|
|
62
111
|
local sid="$1" src="$2" cmd="$3"
|
|
63
112
|
|
|
@@ -70,20 +119,23 @@ run_story_verify() {
|
|
|
70
119
|
return 1
|
|
71
120
|
fi
|
|
72
121
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
run_verify_cmd "$cmd"
|
|
123
|
+
local note=""
|
|
124
|
+
[ "$RUN_REUSED" -eq 1 ] && note=" (shared result)"
|
|
125
|
+
if [ "$RUN_RC" -eq 0 ]; then
|
|
126
|
+
echo "PASS: $sid$note"
|
|
76
127
|
PASS=$((PASS + 1))
|
|
77
128
|
return 0
|
|
78
129
|
fi
|
|
79
|
-
echo "FAIL: $sid [$src] — $cmd"
|
|
80
|
-
echo " output: $(echo "$
|
|
130
|
+
echo "FAIL: $sid [$src]$note — $cmd"
|
|
131
|
+
echo " output: $(echo "$RUN_OUT" | tail -3 | tr '\n' ' ')"
|
|
81
132
|
FAIL=$((FAIL + 1))
|
|
82
133
|
return 1
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
gate_all() {
|
|
86
137
|
local sid src status cmd
|
|
138
|
+
MEMO_DIR=$(mktemp -d)
|
|
87
139
|
while IFS=$'\t' read -r sid src status cmd; do
|
|
88
140
|
[ -z "${sid:-}" ] && continue
|
|
89
141
|
if [ -n "$TARGET" ] && [ "$sid" != "$TARGET" ]; then
|
|
@@ -96,6 +148,58 @@ gate_all() {
|
|
|
96
148
|
fi
|
|
97
149
|
run_story_verify "$sid" "$src" "$cmd" || true
|
|
98
150
|
done < <(extract_records)
|
|
151
|
+
rm -rf "$MEMO_DIR"
|
|
152
|
+
MEMO_DIR=""
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Shared-command proof: stories that declare the same verify run it once, yet
|
|
156
|
+
# each story still gets its own verdict — a failing shared command fails every
|
|
157
|
+
# story that declares it, never just the first.
|
|
158
|
+
self_test_shared_commands() {
|
|
159
|
+
local root="$1/shared" out runs
|
|
160
|
+
mkdir -p "$root/specs/epics/e98-shared"
|
|
161
|
+
cat > "$root/specs/epics/e98-shared/epic.yaml" <<YAML
|
|
162
|
+
id: e98
|
|
163
|
+
title: shared-command capsule
|
|
164
|
+
stories:
|
|
165
|
+
- id: e98s01
|
|
166
|
+
status: done
|
|
167
|
+
verify: printf x >> $root/ok-runs && test -d $root/specs
|
|
168
|
+
- id: e98s02
|
|
169
|
+
status: done
|
|
170
|
+
verify: printf x >> $root/ok-runs && test -d $root/specs
|
|
171
|
+
- id: e98s03
|
|
172
|
+
status: done
|
|
173
|
+
verify: printf x >> $root/bad-runs && test -f $root/missing
|
|
174
|
+
- id: e98s04
|
|
175
|
+
status: done
|
|
176
|
+
verify: printf x >> $root/bad-runs && test -f $root/missing
|
|
177
|
+
- id: e98s05
|
|
178
|
+
status: done
|
|
179
|
+
verify: printf x >> $root/seg-runs && test -d $root/specs
|
|
180
|
+
- id: e98s06
|
|
181
|
+
status: done
|
|
182
|
+
verify: printf x >> $root/seg-runs && test -d $root/specs/epics
|
|
183
|
+
- id: e98s07
|
|
184
|
+
status: done
|
|
185
|
+
verify: printf x >> $root/seg-runs && test -f $root/missing
|
|
186
|
+
YAML
|
|
187
|
+
out=$(EXTRACT_ROOT="$root" TARGET="" PASS=0 FAIL=0 SKIP=0 gate_all)
|
|
188
|
+
local problems=0
|
|
189
|
+
runs=$(wc -c < "$root/ok-runs" | tr -d ' ')
|
|
190
|
+
[ "$runs" = "1" ] || { echo "FAIL: self-test — shared passing command ran $runs times, want 1"; problems=$((problems + 1)); }
|
|
191
|
+
runs=$(wc -c < "$root/bad-runs" | tr -d ' ')
|
|
192
|
+
[ "$runs" = "1" ] || { echo "FAIL: self-test — shared failing command ran $runs times, want 1"; problems=$((problems + 1)); }
|
|
193
|
+
runs=$(wc -c < "$root/seg-runs" | tr -d ' ')
|
|
194
|
+
[ "$runs" = "1" ] || { echo "FAIL: self-test — shared && segment ran $runs times, want 1"; problems=$((problems + 1)); }
|
|
195
|
+
local sid
|
|
196
|
+
for sid in e98s01 e98s02 e98s05 e98s06; do
|
|
197
|
+
echo "$out" | grep -q "^PASS: $sid" || { echo "FAIL: self-test — $sid not reported PASS"; problems=$((problems + 1)); }
|
|
198
|
+
done
|
|
199
|
+
for sid in e98s03 e98s04 e98s07; do
|
|
200
|
+
echo "$out" | grep -q "^FAIL: $sid" || { echo "FAIL: self-test — $sid not reported FAIL"; problems=$((problems + 1)); }
|
|
201
|
+
done
|
|
202
|
+
[ "$problems" -eq 0 ]
|
|
99
203
|
}
|
|
100
204
|
|
|
101
205
|
# Anti-vacuity proof: seed a capsule whose done story must fail, and one that
|
|
@@ -178,6 +282,8 @@ EOF
|
|
|
178
282
|
rm -rf "$tmp_root"
|
|
179
283
|
trap - EXIT
|
|
180
284
|
|
|
285
|
+
self_test_shared_commands "$tmp_root" || problems=$((problems + 1))
|
|
286
|
+
|
|
181
287
|
if [ "$checked" -lt 5 ]; then
|
|
182
288
|
echo "FAIL: self-test — extractor found $checked/5 fixture stories"
|
|
183
289
|
return 1
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: BUG-2026-10-25-release-not-gated
|
|
3
|
+
# test-ci-pipeline-contract.sh — the CI shape the portfolio template requires
|
|
4
|
+
# (danielvm-git/.github, docs/concept/github-actions-best-practices.md):
|
|
5
|
+
#
|
|
6
|
+
# 1. one pipeline, .github/workflows/test-build-release.yml, holds every gate
|
|
7
|
+
# job and the release job; the workflows it replaced are gone
|
|
8
|
+
# 2. release needs: every gate job and runs only on push to main — a release
|
|
9
|
+
# that does not wait for the gates publishes unverified builds
|
|
10
|
+
# 3. no ubuntu-latest; every non-local action is pinned to a 40-char SHA
|
|
11
|
+
# 4. every job has timeout-minutes
|
|
12
|
+
# 5. release and Pages never cancel a run in progress
|
|
13
|
+
#
|
|
14
|
+
# Agent workflows (agent-locks.yml, generated agentics-maintenance.yml) are
|
|
15
|
+
# exempt from 3–4 per migration plan v2.
|
|
16
|
+
#
|
|
17
|
+
# Usage: bash scripts/test-ci-pipeline-contract.sh [--self-test] [workflows-dir]
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
20
|
+
resolve_repo_root
|
|
21
|
+
cd "$REPO_ROOT"
|
|
22
|
+
source scripts/lib/python-env.sh
|
|
23
|
+
resolve_python >/dev/null || exit 1
|
|
24
|
+
|
|
25
|
+
check_dir() { # $1 = workflows dir; prints violations, exits non-zero on any
|
|
26
|
+
"$PYTHON" - "$1" <<'PY'
|
|
27
|
+
import glob, os, re, sys, yaml
|
|
28
|
+
|
|
29
|
+
d = sys.argv[1]
|
|
30
|
+
GATES = {"sync", "verification-gates", "skill-verify", "story-verify"}
|
|
31
|
+
RETIRED = ["publish.yml", "sync-skills.yml", "golden-suite.yml"]
|
|
32
|
+
EXEMPT = {"agent-locks.yml", "agentics-maintenance.yml"}
|
|
33
|
+
bad = []
|
|
34
|
+
|
|
35
|
+
def load(path):
|
|
36
|
+
with open(path) as f:
|
|
37
|
+
return yaml.safe_load(f) or {}
|
|
38
|
+
|
|
39
|
+
def cancels(block):
|
|
40
|
+
# Literal true cancels; an expression or false does not cancel a push run.
|
|
41
|
+
return isinstance(block, dict) and block.get("cancel-in-progress") is True
|
|
42
|
+
|
|
43
|
+
pipe_path = os.path.join(d, "test-build-release.yml")
|
|
44
|
+
if not os.path.isfile(pipe_path):
|
|
45
|
+
bad.append("test-build-release.yml missing")
|
|
46
|
+
else:
|
|
47
|
+
pipe = load(pipe_path)
|
|
48
|
+
jobs = pipe.get("jobs", {})
|
|
49
|
+
missing = GATES - set(jobs)
|
|
50
|
+
if missing:
|
|
51
|
+
bad.append(f"pipeline lacks gate jobs: {sorted(missing)}")
|
|
52
|
+
rel = jobs.get("release")
|
|
53
|
+
if not rel:
|
|
54
|
+
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"}
|
|
59
|
+
if not gate_jobs <= needs:
|
|
60
|
+
bad.append(f"release does not need: {sorted(gate_jobs - needs)}")
|
|
61
|
+
cond = str(rel.get("if", ""))
|
|
62
|
+
if "github.event_name == 'push'" not in cond or "refs/heads/main" not in cond:
|
|
63
|
+
bad.append("release is not limited to push on main")
|
|
64
|
+
if cancels(pipe.get("concurrency")) or cancels(rel.get("concurrency")):
|
|
65
|
+
bad.append("release can be cancelled in progress")
|
|
66
|
+
|
|
67
|
+
for name in RETIRED:
|
|
68
|
+
if os.path.exists(os.path.join(d, name)):
|
|
69
|
+
bad.append(f"retired workflow still present: {name}")
|
|
70
|
+
|
|
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
|
+
for path in sorted(glob.glob(os.path.join(d, "*.yml"))):
|
|
76
|
+
name = os.path.basename(path)
|
|
77
|
+
if name in EXEMPT:
|
|
78
|
+
continue
|
|
79
|
+
for job_name, job in (load(path).get("jobs") or {}).items():
|
|
80
|
+
where = f"{name}:{job_name}"
|
|
81
|
+
if "uses" in job: # reusable-workflow call: no runner of its own
|
|
82
|
+
continue
|
|
83
|
+
if "ubuntu-latest" in str(job.get("runs-on", "")):
|
|
84
|
+
bad.append(f"{where}: runs-on ubuntu-latest")
|
|
85
|
+
if "timeout-minutes" not in job:
|
|
86
|
+
bad.append(f"{where}: no timeout-minutes")
|
|
87
|
+
for step in job.get("steps", []):
|
|
88
|
+
uses = step.get("uses")
|
|
89
|
+
if uses and not uses.startswith("./") and not re.search(r"@[0-9a-f]{40}$", uses):
|
|
90
|
+
bad.append(f"{where}: action not SHA-pinned: {uses}")
|
|
91
|
+
|
|
92
|
+
for b in bad:
|
|
93
|
+
print(f"FAIL: {b}")
|
|
94
|
+
sys.exit(1 if bad else 0)
|
|
95
|
+
PY
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Anti-vacuity: a conforming fixture passes, and each rule fails on its own breach.
|
|
99
|
+
self_test() {
|
|
100
|
+
local tmp ok=1
|
|
101
|
+
tmp=$(mktemp -d)
|
|
102
|
+
trap "rm -rf '$tmp'" EXIT
|
|
103
|
+
local pin="actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"
|
|
104
|
+
write_fixture() { # $1 = dir
|
|
105
|
+
mkdir -p "$1"
|
|
106
|
+
cat > "$1/test-build-release.yml" <<YAML
|
|
107
|
+
on: push
|
|
108
|
+
jobs:
|
|
109
|
+
sync: {runs-on: ubuntu-22.04, timeout-minutes: 10, steps: [{uses: $pin}]}
|
|
110
|
+
verification-gates: {runs-on: ubuntu-22.04, timeout-minutes: 15, steps: [{run: "true"}]}
|
|
111
|
+
skill-verify: {runs-on: ubuntu-22.04, timeout-minutes: 10, steps: [{run: "true"}]}
|
|
112
|
+
story-verify: {runs-on: ubuntu-22.04, timeout-minutes: 10, steps: [{run: "true"}]}
|
|
113
|
+
release:
|
|
114
|
+
needs: [sync, verification-gates, skill-verify, story-verify]
|
|
115
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
116
|
+
runs-on: ubuntu-22.04
|
|
117
|
+
timeout-minutes: 15
|
|
118
|
+
concurrency: {group: release, cancel-in-progress: false}
|
|
119
|
+
steps: [{run: "true"}]
|
|
120
|
+
YAML
|
|
121
|
+
}
|
|
122
|
+
expect() { # $1 = label, $2 = want pass|fail, $3 = dir
|
|
123
|
+
if check_dir "$3" >/dev/null; then got=pass; else got=fail; fi
|
|
124
|
+
if [ "$got" = "$2" ]; then echo "PASS ($1)"; else echo "FAIL ($1): got $got, want $2"; ok=0; fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
write_fixture "$tmp/good"; expect "conforming pipeline passes" pass "$tmp/good"
|
|
128
|
+
|
|
129
|
+
write_fixture "$tmp/no-needs"
|
|
130
|
+
sed -i.bak 's/needs: \[sync, verification-gates, skill-verify, story-verify\]/needs: [sync]/' "$tmp/no-needs/test-build-release.yml"
|
|
131
|
+
expect "release missing gate needs fails" fail "$tmp/no-needs"
|
|
132
|
+
|
|
133
|
+
write_fixture "$tmp/latest"
|
|
134
|
+
sed -i.bak 's/runs-on: ubuntu-22.04, timeout-minutes: 15/runs-on: ubuntu-latest, timeout-minutes: 15/' "$tmp/latest/test-build-release.yml"
|
|
135
|
+
expect "ubuntu-latest fails" fail "$tmp/latest"
|
|
136
|
+
|
|
137
|
+
write_fixture "$tmp/tag"
|
|
138
|
+
sed -i.bak "s|$pin|actions/checkout@v7|" "$tmp/tag/test-build-release.yml"
|
|
139
|
+
expect "tag-pinned action fails" fail "$tmp/tag"
|
|
140
|
+
|
|
141
|
+
write_fixture "$tmp/cancel"
|
|
142
|
+
sed -i.bak 's/cancel-in-progress: false/cancel-in-progress: true/' "$tmp/cancel/test-build-release.yml"
|
|
143
|
+
expect "cancellable release fails" fail "$tmp/cancel"
|
|
144
|
+
|
|
145
|
+
write_fixture "$tmp/retired"; : > "$tmp/retired/publish.yml"
|
|
146
|
+
expect "retired workflow present fails" fail "$tmp/retired"
|
|
147
|
+
|
|
148
|
+
[ "$ok" -eq 1 ] && echo "OK: pipeline contract self-test" || { echo "pipeline contract self-test FAILED"; exit 1; }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
152
|
+
self_test
|
|
153
|
+
exit 0
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
dir="${1:-.github/workflows}"
|
|
157
|
+
if check_dir "$dir"; then
|
|
158
|
+
echo "OK: CI pipeline contract ($dir)"
|
|
159
|
+
else
|
|
160
|
+
echo "CI pipeline contract violated — see danielvm-git/.github docs/concept/github-actions-best-practices.md"
|
|
161
|
+
exit 1
|
|
162
|
+
fi
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: BUG-2026-10-25-ci-critical-path
|
|
3
|
+
# test-golden-gate-runner.sh — regression guard for golden_run_deterministic_gates.
|
|
4
|
+
#
|
|
5
|
+
# Runs the real runner against a fixture gate list and asserts:
|
|
6
|
+
# (a) a failing gate prints the tail of its output (CI used to show only FAIL)
|
|
7
|
+
# (b) GOLDEN_SKIP_GATES reports a named gate as SKIP with its reason
|
|
8
|
+
# (c) an unknown name in GOLDEN_SKIP_GATES is a FAIL, so a typo cannot
|
|
9
|
+
# silently disable nothing while claiming to skip something
|
|
10
|
+
# (d) without GOLDEN_SKIP_GATES every gate runs — the default stays full
|
|
11
|
+
#
|
|
12
|
+
# Usage: bash scripts/test-golden-gate-runner.sh
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/skill-common.sh"
|
|
16
|
+
resolve_repo_root
|
|
17
|
+
cd "$REPO_ROOT"
|
|
18
|
+
|
|
19
|
+
FAILURES=0
|
|
20
|
+
pass() { echo "PASS $1"; }
|
|
21
|
+
fail() { echo "FAIL $1"; FAILURES=$((FAILURES + 1)); }
|
|
22
|
+
|
|
23
|
+
# Run the runner in a subshell with a fixture gate list; prints its stdout and
|
|
24
|
+
# the final counters as "COUNTS pass fail skip".
|
|
25
|
+
run_fixture() {
|
|
26
|
+
(
|
|
27
|
+
source scripts/lib/golden-suite-gates.sh
|
|
28
|
+
GOLDEN_GATES=(
|
|
29
|
+
"ok-gate:true:false"
|
|
30
|
+
"bad-gate:sh -c 'echo marker-line-from-failing-gate; exit 3':false"
|
|
31
|
+
"covered-gate:true:false"
|
|
32
|
+
)
|
|
33
|
+
golden_run_deterministic_gates
|
|
34
|
+
echo "COUNTS $PASS_COUNT $FAIL_COUNT $SKIP_COUNT"
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
out=$(GOLDEN_SKIP_GATES="" run_fixture)
|
|
39
|
+
echo "$out" | grep -q 'marker-line-from-failing-gate' \
|
|
40
|
+
&& pass "(a) failing gate output is printed" \
|
|
41
|
+
|| fail "(a) failing gate output is missing"
|
|
42
|
+
echo "$out" | grep -q '^COUNTS 2 1 0$' \
|
|
43
|
+
&& pass "(d) no skip list runs every gate" \
|
|
44
|
+
|| fail "(d) default run counts wrong: $(echo "$out" | grep COUNTS)"
|
|
45
|
+
|
|
46
|
+
out=$(GOLDEN_SKIP_GATES="covered-gate" GOLDEN_SKIP_REASON="runs in its own CI job" run_fixture)
|
|
47
|
+
echo "$out" | grep -q '^\[covered-gate\] .*SKIP.*runs in its own CI job' \
|
|
48
|
+
&& pass "(b) skipped gate reported with reason" \
|
|
49
|
+
|| fail "(b) skipped gate not reported with reason"
|
|
50
|
+
echo "$out" | grep -q '^COUNTS 1 1 1$' \
|
|
51
|
+
&& pass "(b) skipped gate counted as skip, not pass" \
|
|
52
|
+
|| fail "(b) skip counts wrong: $(echo "$out" | grep COUNTS)"
|
|
53
|
+
|
|
54
|
+
out=$(GOLDEN_SKIP_GATES="no-such-gate" run_fixture)
|
|
55
|
+
echo "$out" | grep -q 'no-such-gate' && echo "$out" | grep -q '^COUNTS 2 2 0$' \
|
|
56
|
+
&& pass "(c) unknown skip name fails the run" \
|
|
57
|
+
|| fail "(c) unknown skip name accepted: $(echo "$out" | grep COUNTS)"
|
|
58
|
+
|
|
59
|
+
[ "$FAILURES" -eq 0 ] && echo "OK: golden gate runner" || { echo "$FAILURES check(s) failed"; exit 1; }
|
|
@@ -75,8 +75,8 @@ gate_trace:
|
|
|
75
75
|
## Integration Points
|
|
76
76
|
|
|
77
77
|
- **release-branch SKILL.md** — pre-PR gate (FAIL blocks merge).
|
|
78
|
-
- **
|
|
79
|
-
blocks
|
|
78
|
+
- **test-build-release.yml** — its `sync` job runs `trace-stories.sh --strict` as the "Traceability gate" step (FAIL
|
|
79
|
+
blocks the release job), which shares this skill's coverage data (`traceability-matrix.json`) but
|
|
80
80
|
does not invoke a gate-trace PASS/CONCERNS/FAIL/WAIVED verdict directly.
|
|
81
81
|
- **verify-work SKILL.md** — runs blind-spot checks; gate-trace consumes the result.
|
|
82
82
|
|
package/skills-lock.json
CHANGED
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
},
|
|
144
144
|
"gate-trace": {
|
|
145
145
|
"description": "\"Deterministic traceability quality gate — reads coverage matrix + blind-spot data, applies decision rules with oracle confidence downgrade, emits PASS/CONCERNS/FAIL/WAIVED verdict. Use before release-branch to gate merges on traceability.\"",
|
|
146
|
-
"sha256": "
|
|
146
|
+
"sha256": "282c950cf9db124a",
|
|
147
147
|
"path": "skills/gate-trace/SKILL.md"
|
|
148
148
|
},
|
|
149
149
|
"generate-allure-report": {
|
|
Binary file
|
|
Binary file
|