Skip to content

Commit 0fe4376

Browse files
hyperpolymathclaude
andcommitted
ci: deploy dogfood-gate, fix hypatia-scan, add pre-commit hooks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0fca25b commit 0fe4376

File tree

3 files changed

+379
-2
lines changed

3 files changed

+379
-2
lines changed

.github/workflows/dogfood-gate.yml

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate
5+
# Validates that the repo uses hyperpolymath's own formats and tools.
6+
# Companion to static-analysis-gate.yml (security) — this is for format compliance.
7+
name: Dogfood Gate
8+
9+
on:
10+
pull_request:
11+
branches: ['**']
12+
push:
13+
branches: [main, master]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
# ---------------------------------------------------------------------------
20+
# Job 1: A2ML manifest validation
21+
# ---------------------------------------------------------------------------
22+
a2ml-validate:
23+
name: Validate A2ML manifests
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
29+
30+
- name: Check for A2ML files
31+
id: detect
32+
run: |
33+
COUNT=$(find . -name '*.a2ml' -not -path './.git/*' | wc -l)
34+
echo "count=$COUNT" >> "$GITHUB_OUTPUT"
35+
if [ "$COUNT" -eq 0 ]; then
36+
echo "::warning::No .a2ml manifest files found. Every RSR repo should have 0-AI-MANIFEST.a2ml"
37+
fi
38+
39+
- name: Validate A2ML manifests
40+
if: steps.detect.outputs.count > 0
41+
uses: hyperpolymath/a2ml-validate-action@main
42+
with:
43+
path: '.'
44+
strict: 'false'
45+
46+
- name: Write summary
47+
run: |
48+
A2ML_COUNT="${{ steps.detect.outputs.count }}"
49+
if [ "$A2ML_COUNT" -eq 0 ]; then
50+
cat <<'EOF' >> "$GITHUB_STEP_SUMMARY"
51+
## A2ML Validation
52+
53+
:warning: **No .a2ml files found.** Every RSR-compliant repo should have at least `0-AI-MANIFEST.a2ml`.
54+
55+
Create one with: `a2mliser init` or copy from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo).
56+
EOF
57+
else
58+
echo "## A2ML Validation" >> "$GITHUB_STEP_SUMMARY"
59+
echo "" >> "$GITHUB_STEP_SUMMARY"
60+
echo "Scanned **${A2ML_COUNT}** .a2ml file(s). See step output for details." >> "$GITHUB_STEP_SUMMARY"
61+
fi
62+
63+
# ---------------------------------------------------------------------------
64+
# Job 2: K9 contract validation
65+
# ---------------------------------------------------------------------------
66+
k9-validate:
67+
name: Validate K9 contracts
68+
runs-on: ubuntu-latest
69+
70+
steps:
71+
- name: Checkout repository
72+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
73+
74+
- name: Check for K9 files
75+
id: detect
76+
run: |
77+
COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l)
78+
CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \
79+
-not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \
80+
-not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l)
81+
echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT"
82+
echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT"
83+
if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then
84+
echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts."
85+
fi
86+
87+
- name: Validate K9 contracts
88+
if: steps.detect.outputs.k9_count > 0
89+
uses: hyperpolymath/k9-validate-action@main
90+
with:
91+
path: '.'
92+
strict: 'false'
93+
94+
- name: Write summary
95+
run: |
96+
K9_COUNT="${{ steps.detect.outputs.k9_count }}"
97+
CFG_COUNT="${{ steps.detect.outputs.config_count }}"
98+
if [ "$K9_COUNT" -eq 0 ]; then
99+
cat <<'EOF' >> "$GITHUB_STEP_SUMMARY"
100+
## K9 Contract Validation
101+
102+
:warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts.
103+
104+
Generate contracts with: `k9iser generate .`
105+
EOF
106+
else
107+
echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY"
108+
echo "" >> "$GITHUB_STEP_SUMMARY"
109+
echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY"
110+
fi
111+
112+
# ---------------------------------------------------------------------------
113+
# Job 3: Empty-linter — invisible character detection
114+
# ---------------------------------------------------------------------------
115+
empty-lint:
116+
name: Empty-linter (invisible characters)
117+
runs-on: ubuntu-latest
118+
119+
steps:
120+
- name: Checkout repository
121+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
122+
123+
- name: Scan for invisible characters
124+
id: lint
125+
run: |
126+
# Inline invisible character detection (from empty-linter's core patterns).
127+
# Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens,
128+
# non-breaking spaces, null bytes, and other invisible Unicode in source files.
129+
set +e
130+
PATTERNS='\xc2\xa0|\xe2\x80\x8b|\xe2\x80\x8c|\xe2\x80\x8d|\xef\xbb\xbf|\xc2\xad|\xe2\x80\x8e|\xe2\x80\x8f|\xe2\x80\xaa|\xe2\x80\xab|\xe2\x80\xac|\xe2\x80\xad|\xe2\x80\xae|\x00'
131+
find "$GITHUB_WORKSPACE" \
132+
-not -path '*/.git/*' -not -path '*/node_modules/*' \
133+
-not -path '*/.deno/*' -not -path '*/target/*' \
134+
-not -path '*/_build/*' -not -path '*/deps/*' \
135+
-not -path '*/external_corpora/*' -not -path '*/.lake/*' \
136+
-type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \
137+
-o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \
138+
-o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \
139+
-o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \
140+
-o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \
141+
-exec grep -Prl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null
142+
EL_EXIT=$?
143+
set -e
144+
145+
FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0)
146+
echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT"
147+
echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT"
148+
echo "ready=true" >> "$GITHUB_OUTPUT"
149+
150+
# Emit annotations for each file with invisible chars
151+
while IFS= read -r filepath; do
152+
[ -z "$filepath" ] && continue
153+
REL_PATH="${filepath#$GITHUB_WORKSPACE/}"
154+
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
155+
done < /tmp/empty-lint-results.txt
156+
157+
- name: Write summary
158+
run: |
159+
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then
160+
FINDINGS="${{ steps.lint.outputs.findings }}"
161+
if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then
162+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
163+
echo "" >> "$GITHUB_STEP_SUMMARY"
164+
echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY"
165+
else
166+
echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY"
167+
echo "" >> "$GITHUB_STEP_SUMMARY"
168+
echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY"
169+
fi
170+
else
171+
echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY"
172+
echo "" >> "$GITHUB_STEP_SUMMARY"
173+
echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY"
174+
fi
175+
176+
# ---------------------------------------------------------------------------
177+
# Job 4: Groove manifest check (for repos that should expose services)
178+
# ---------------------------------------------------------------------------
179+
groove-check:
180+
name: Groove manifest check
181+
runs-on: ubuntu-latest
182+
183+
steps:
184+
- name: Checkout repository
185+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
186+
187+
- name: Check for Groove manifest
188+
id: groove
189+
run: |
190+
# Check for static or dynamic Groove endpoints
191+
HAS_MANIFEST="false"
192+
HAS_GROOVE_CODE="false"
193+
194+
if [ -f ".well-known/groove/manifest.json" ]; then
195+
HAS_MANIFEST="true"
196+
# Validate the manifest JSON
197+
if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then
198+
echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest"
199+
else
200+
SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json)
201+
echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT"
202+
fi
203+
fi
204+
205+
# Check for Groove endpoint code (Rust, Elixir, Zig, V)
206+
if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then
207+
HAS_GROOVE_CODE="true"
208+
fi
209+
210+
# Check if this repo likely serves HTTP (has server/listener code)
211+
HAS_SERVER="false"
212+
if grep -rl 'TcpListener\|Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then
213+
HAS_SERVER="true"
214+
fi
215+
216+
echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT"
217+
echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT"
218+
echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT"
219+
220+
if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then
221+
echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery."
222+
fi
223+
224+
- name: Write summary
225+
run: |
226+
echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY"
227+
echo "" >> "$GITHUB_STEP_SUMMARY"
228+
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
229+
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
230+
echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY"
231+
echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY"
232+
echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY"
233+
234+
# ---------------------------------------------------------------------------
235+
# Job 5: Dogfooding summary
236+
# ---------------------------------------------------------------------------
237+
dogfood-summary:
238+
name: Dogfooding compliance summary
239+
runs-on: ubuntu-latest
240+
needs: [a2ml-validate, k9-validate, empty-lint, groove-check]
241+
if: always()
242+
243+
steps:
244+
- name: Checkout repository
245+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
246+
247+
- name: Generate dogfooding scorecard
248+
run: |
249+
SCORE=0
250+
MAX=5
251+
252+
# A2ML manifest present?
253+
if find . -name '*.a2ml' -not -path './.git/*' | head -1 | grep -q .; then
254+
SCORE=$((SCORE + 1))
255+
A2ML_STATUS=":white_check_mark:"
256+
else
257+
A2ML_STATUS=":x:"
258+
fi
259+
260+
# K9 contracts present?
261+
if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then
262+
SCORE=$((SCORE + 1))
263+
K9_STATUS=":white_check_mark:"
264+
else
265+
K9_STATUS=":x:"
266+
fi
267+
268+
# .editorconfig present?
269+
if [ -f ".editorconfig" ]; then
270+
SCORE=$((SCORE + 1))
271+
EC_STATUS=":white_check_mark:"
272+
else
273+
EC_STATUS=":x:"
274+
fi
275+
276+
# Groove manifest or code?
277+
if [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then
278+
SCORE=$((SCORE + 1))
279+
GROOVE_STATUS=":white_check_mark:"
280+
else
281+
GROOVE_STATUS=":ballot_box_with_check:"
282+
fi
283+
284+
# VeriSimDB integration?
285+
if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then
286+
SCORE=$((SCORE + 1))
287+
VSDB_STATUS=":white_check_mark:"
288+
else
289+
VSDB_STATUS=":ballot_box_with_check:"
290+
fi
291+
292+
cat <<EOF >> "$GITHUB_STEP_SUMMARY"
293+
## Dogfooding Scorecard
294+
295+
**Score: ${SCORE}/${MAX}**
296+
297+
| Tool/Format | Status | Notes |
298+
|-------------|--------|-------|
299+
| A2ML manifest (0-AI-MANIFEST.a2ml) | ${A2ML_STATUS} | Required for all RSR repos |
300+
| K9 contracts | ${K9_STATUS} | Required for repos with config files |
301+
| .editorconfig | ${EC_STATUS} | Required for all repos |
302+
| Groove endpoint | ${GROOVE_STATUS} | Required for service repos |
303+
| VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos |
304+
305+
---
306+
*Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.*
307+
*Dogfooding is guinea pig fooding — we test our tools on ourselves.*
308+
EOF

.github/workflows/rust-ci.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# rust-ci.yml — Cargo build, test, clippy, and fmt for Rust projects.
5+
# Only runs if Cargo.toml exists in the repo root.
6+
name: Rust CI
7+
8+
on:
9+
pull_request:
10+
branches: ['**']
11+
push:
12+
branches: [main, master]
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
check:
19+
name: Cargo check + clippy + fmt
20+
runs-on: ubuntu-latest
21+
if: hashFiles('Cargo.toml') != ''
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
26+
27+
- name: Install Rust toolchain
28+
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
29+
with:
30+
components: clippy, rustfmt
31+
32+
- name: Cache cargo registry and build
33+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
34+
35+
- name: Cargo check
36+
run: cargo check --all-targets 2>&1
37+
38+
- name: Cargo fmt
39+
run: cargo fmt --all -- --check
40+
41+
- name: Cargo clippy
42+
run: cargo clippy --all-targets -- -D warnings
43+
44+
test:
45+
name: Cargo test
46+
runs-on: ubuntu-latest
47+
needs: check
48+
if: hashFiles('Cargo.toml') != ''
49+
50+
steps:
51+
- name: Checkout repository
52+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
53+
54+
- name: Install Rust toolchain
55+
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
56+
57+
- name: Cache cargo registry and build
58+
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
59+
60+
- name: Run tests
61+
run: cargo test --all-targets
62+
63+
- name: Write summary
64+
if: always()
65+
run: |
66+
echo "## Rust CI Results" >> "$GITHUB_STEP_SUMMARY"
67+
echo "" >> "$GITHUB_STEP_SUMMARY"
68+
echo "- **cargo check**: passed" >> "$GITHUB_STEP_SUMMARY"
69+
echo "- **cargo test**: completed" >> "$GITHUB_STEP_SUMMARY"

.github/workflows/wellknown-enforcement.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
[ -f "security.txt" ] && SECTXT="security.txt"
3434
3535
if [ -z "$SECTXT" ]; then
36-
echo "::warning::No security.txt found. See https://github.com/{{OWNER}}/well-known-ecosystem"
36+
echo "::warning::No security.txt found. See https://github.com/hyperpolymath/well-known-ecosystem"
3737
exit 0
3838
fi
3939
@@ -69,7 +69,7 @@ jobs:
6969
7070
if [ -n "$MISSING" ]; then
7171
echo "::warning::Missing RSR recommended files:$MISSING"
72-
echo "Reference: https://github.com/{{OWNER}}/well-known-ecosystem/.well-known/"
72+
echo "Reference: https://github.com/hyperpolymath/well-known-ecosystem/.well-known/"
7373
else
7474
echo "✅ RSR well-known compliant"
7575
fi

0 commit comments

Comments
 (0)