Skip to content

Commit ec91f80

Browse files
authored
ci: deepen shared GitHub Actions rails
Add shared reusable workflow timeouts, Blacksmith-backed Codex Rails validation, and Ruby coverage for authorship classification.
1 parent df06683 commit ec91f80

4 files changed

Lines changed: 110 additions & 2 deletions

File tree

.github/workflows/agent-authorship-label.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ permissions:
2727
jobs:
2828
label:
2929
runs-on: ${{ inputs.runner_label }}
30+
timeout-minutes: 10
3031
steps:
3132
- name: Checkout org workflow helpers
3233
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

.github/workflows/codex-rails-check.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,35 @@ on:
1212
- ".github/workflows/**"
1313
- ".github/workflow-templates/**"
1414
- "profile/**"
15+
- "test/**"
1516
workflow_call:
1617
inputs:
1718
require_agents:
1819
description: "Fail when the repository does not have AGENTS.md"
1920
required: false
2021
type: boolean
2122
default: false
23+
runner_label:
24+
description: "Runner label used for the validation job"
25+
required: false
26+
type: string
27+
default: blacksmith-4vcpu-ubuntu-2404
2228

2329
permissions:
2430
contents: read
2531

2632
jobs:
2733
validate:
28-
runs-on: ubuntu-latest
34+
runs-on: ${{ inputs.runner_label || 'blacksmith-4vcpu-ubuntu-2404' }}
35+
timeout-minutes: 10
2936
steps:
3037
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
3138

3239
- name: Validate issue template YAML
3340
shell: bash
3441
run: |
3542
set -euo pipefail
36-
shopt -s nullglob
43+
shopt -s globstar nullglob
3744
files=(.github/ISSUE_TEMPLATE/*.yml .github/ISSUE_TEMPLATE/*.yaml)
3845
if [ "${#files[@]}" -eq 0 ]; then
3946
echo "No issue template YAML files found."
@@ -132,3 +139,16 @@ jobs:
132139
end
133140
' "${skill}"
134141
done
142+
143+
- name: Run repo Ruby tests
144+
shell: bash
145+
run: |
146+
set -euo pipefail
147+
shopt -s globstar nullglob
148+
tests=(test/**/*_test.rb test/*_test.rb)
149+
if [ "${#tests[@]}" -eq 0 ]; then
150+
echo "No Ruby tests found."
151+
exit 0
152+
fi
153+
154+
ruby -Itest "${tests[@]}"

profile/AGENT_AUTHORSHIP.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ authorship labels, and applies the label that matches the current PR commit set.
7676
It only mutates labels when the desired label set changed, so repeated
7777
`synchronize` events do not remove and re-add the same label.
7878

79+
For production repositories, pin the reusable workflow to an immutable
80+
`evalops/.github` commit SHA. When pinning, pass the same SHA as `helper_ref` so
81+
the workflow and helper scripts are resolved from the same reviewed revision.
82+
7983
## Audit Indexing
8084

8185
Audit ingestion should parse trailers from every commit merged to protected
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
require "minitest/autorun"
5+
require "open3"
6+
require "tempfile"
7+
8+
class ClassifyAgentAuthorshipTest < Minitest::Test
9+
ROOT = File.expand_path("..", __dir__)
10+
SCRIPT = File.join(ROOT, ".github/scripts/classify-agent-authorship.rb")
11+
12+
def test_untrailered_commits_are_agent_assisted
13+
outputs = classify([{ "sha" => "abc", "message" => "fix: regular change" }])
14+
15+
assert_equal "agent-assisted", outputs.fetch("label")
16+
assert_equal "1", outputs.fetch("total_commits")
17+
assert_equal "0", outputs.fetch("agent_commits")
18+
assert_equal "1", outputs.fetch("untrailered_commits")
19+
assert_equal "0", outputs.fetch("incomplete_agent_commits")
20+
end
21+
22+
def test_complete_maestro_trailers_are_agent_authored
23+
outputs = classify([{ "sha" => "abc", "message" => <<~MSG }])
24+
feat: ship change
25+
26+
Co-Authored-By: Maestro <maestro@evalops.dev>
27+
Maestro-Version: 2026.04.28 / gpt-5
28+
Maestro-Prompt-Id: prompt-123
29+
Maestro-Approvals-Id: approval-456
30+
MSG
31+
32+
assert_equal "agent-authored", outputs.fetch("label")
33+
assert_equal "1", outputs.fetch("agent_commits")
34+
assert_equal "0", outputs.fetch("untrailered_commits")
35+
assert_equal "0", outputs.fetch("incomplete_agent_commits")
36+
end
37+
38+
def test_mixed_authorship_and_incomplete_trailers_are_reported
39+
outputs = classify(
40+
[
41+
{ "sha" => "abc", "message" => <<~MSG },
42+
feat: partial agent change
43+
44+
Co-Authored-By: Maestro <maestro@evalops.dev>
45+
Maestro-Version: 2026.04.28 / gpt-5
46+
MSG
47+
{ "sha" => "def", "message" => "docs: human follow-up" },
48+
],
49+
)
50+
51+
assert_equal "mixed-authorship", outputs.fetch("label")
52+
assert_equal "1", outputs.fetch("agent_commits")
53+
assert_equal "1", outputs.fetch("untrailered_commits")
54+
assert_equal "1", outputs.fetch("incomplete_agent_commits")
55+
end
56+
57+
def test_github_output_file_gets_same_outputs
58+
Tempfile.create("github-output") do |file|
59+
outputs = classify(
60+
[{ "sha" => "abc", "message" => "fix: regular change" }],
61+
github_output: file.path,
62+
)
63+
file_outputs = parse_outputs(File.read(file.path))
64+
65+
assert_equal outputs, file_outputs
66+
end
67+
end
68+
69+
private
70+
71+
def classify(commits, github_output: nil)
72+
input = commits.map(&:to_json).join("\n")
73+
args = ["ruby", SCRIPT]
74+
args += ["--github-output", github_output] if github_output
75+
stdout, stderr, status = Open3.capture3(*args, stdin_data: input)
76+
assert status.success?, stderr
77+
parse_outputs(stdout)
78+
end
79+
80+
def parse_outputs(text)
81+
text.each_line(chomp: true).to_h { |line| line.split("=", 2) }
82+
end
83+
end

0 commit comments

Comments
 (0)