Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed-10686-attribution-identity-footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Most effective models now recognizes contributor PR attribution followed by Hive's identity footer, restoring merged counts and ranking eligibility on rescan while still rejecting attribution followed by ordinary prose ([#10686](https://github.com/hivecommons/hive/issues/10686)).
37 changes: 37 additions & 0 deletions src/pkg/dashboard/contribute_effective_models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dashboard

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -53,6 +54,42 @@ func TestAggregateContributeEffectiveModelsThresholdRankingAndPrivacy(t *testing
}
}

func TestAggregateContributeEffectiveModelsIdentityFooterAttribution(t *testing.T) {
now := time.Date(2026, 10, 5, 12, 0, 0, 0, time.UTC)
body := "Done\n\nβ€” hive: backend=codex model=gpt-6-astra effort=medium\n\n---\n🐝 **Hive Agent**: `contributor` | **SHA:** `671737a3b`"
var prs []ghpkg.PullRequest
var runs []TaskRunRecord
for number := 1; number <= effectiveModelsDefaultMinMergedPRs; number++ {
meta, ok := ghpkg.ParseAttributionTrailer(body)
if !ok {
t.Fatal("production contributor PR body lost its attribution")
}
prs = append(prs, ghpkg.PullRequest{
Repo: "org/repo", Number: number, HiveAttributed: ok,
HiveBackend: meta.Backend, HiveModel: meta.Model,
CreatedAt: now, MergedAt: now, Rework: ghpkg.PRReworkStats{FirstPass: true},
})
runs = append(runs, TaskRunRecord{
TS: now.Format(time.RFC3339), Username: "contributor", Backend: "codex", Model: "gpt-6-astra",
Outcome: outcomeCompleted, PRVerified: true, PRURL: fmt.Sprintf("https://github.com/org/repo/pull/%d", number),
})
}
for _, filter := range []string{"all", "contributor"} {
below := aggregateContributeEffectiveModels(prs[:4], runs, "30d", filter, effectiveModelsDefaultMinMergedPRs, now)
if len(below.Ranked) != 0 || len(below.Insufficient) != 1 || below.Insufficient[0].MergedPRs != 4 {
t.Fatalf("%s below threshold = %+v", filter, below)
}
got := aggregateContributeEffectiveModels(prs, runs, "30d", filter, effectiveModelsDefaultMinMergedPRs, now)
if len(got.Ranked) != 1 || len(got.Insufficient) != 0 {
t.Fatalf("%s at threshold = %+v", filter, got)
}
row := got.Ranked[0]
if row.Model != "gpt-6-astra" || row.Backend != "codex" || row.MergedPRs != 5 || row.PRs != 5 || row.EffectivenessRank != 1 {
t.Fatalf("%s attribution/ranking = %+v", filter, row)
}
}
}

func TestHandleContributeEffectiveModelsPublicAggregate(t *testing.T) {
s := covApiServer(t)
now := time.Now()
Expand Down
13 changes: 10 additions & 3 deletions src/pkg/github/attribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,18 @@ func HasAttributionTrailer(body string) bool {
return strings.Contains(body, AttributionTrailerPrefix)
}

// attributionIdentityFooter matches only the terminal block emitted by
// bin/gh-wrapper.sh's _identity_footer, not arbitrary trailing Markdown.
var attributionIdentityFooter = regexp.MustCompile("\\n[ \\t]*---[ \\t]*\\n🐝 \\*\\*Hive Agent\\*\\*:(?: `[^`\\n]+`)?(?: \\| \\*\\*Instance:\\*\\* `[^`\\n]+`)? \\| \\*\\*SHA:\\*\\* `[^`\\n]+`[ \\t\\n]*$")

// ParseAttributionTrailer extracts the launch metadata from the final visible
// `β€” hive:` line in a PR body. Mentions of the template earlier in the body are
// deliberately ignored so docs and quoted examples do not count as agent PRs.
// `β€” hive:` line in a PR body, optionally followed by Hive's identity footer.
// Mentions of the template earlier in the body are deliberately ignored so
// docs and quoted examples do not count as agent PRs.
func ParseAttributionTrailer(body string) (InvocationMeta, bool) {
lines := strings.Split(strings.ReplaceAll(body, "\r\n", "\n"), "\n")
body = strings.ReplaceAll(body, "\r\n", "\n")
body = attributionIdentityFooter.ReplaceAllString(body, "")
lines := strings.Split(body, "\n")
trailer := ""
for i := len(lines) - 1; i >= 0; i-- {
line := strings.TrimSpace(lines[i])
Expand Down
80 changes: 80 additions & 0 deletions src/pkg/github/attribution_identity_footer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package github

import (
"context"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

gh "github.com/google/go-github/v72/github"
)

const contributorAttributionBody = "Done\n\nβ€” hive: backend=codex model=gpt-6-astra effort=medium\n\n---\n🐝 **Hive Agent**: `contributor` | **SHA:** `671737a3b`"

func TestParseAttributionTrailerWithIdentityFooter(t *testing.T) {
for _, body := range []string{
contributorAttributionBody,
contributorAttributionBody + "\n\n",
strings.ReplaceAll(contributorAttributionBody, "\n", "\r\n"),
strings.ReplaceAll(contributorAttributionBody, " | **SHA:**", " | **Instance:** `hive-bold-fox` | **SHA:**"),
strings.ReplaceAll(contributorAttributionBody, " `contributor`", ""),
} {
meta, ok := ParseAttributionTrailer(body)
if !ok || meta.Backend != "codex" || meta.Model != "gpt-6-astra" {
t.Errorf("ParseAttributionTrailer(%q) = %+v, %v", body, meta, ok)
}
}
}

func TestParseAttributionTrailerDoesNotScanPastOrdinaryContent(t *testing.T) {
for _, body := range []string{
contributorAttributionBody + "\nMore prose",
strings.ReplaceAll(contributorAttributionBody, "\n\n---", "\nMore prose\n\n---"),
strings.ReplaceAll(contributorAttributionBody, "\n\n---", "\n\n"),
strings.ReplaceAll(contributorAttributionBody, "**SHA:**", "**Other:**"),
strings.ReplaceAll(contributorAttributionBody, "β€” hive:", "> β€” hive:"),
"```text\n" + contributorAttributionBody + "\n```",
strings.ReplaceAll(contributorAttributionBody, "\n\n---", "\n```\n\n---"),
contributorAttributionBody + "\n\n---\n🐝 **Hive Agent**: `contributor` | **SHA:** `unknown`",
} {
if meta, ok := ParseAttributionTrailer(body); ok {
t.Errorf("accepted nonterminal or quoted attribution in %q: %+v", body, meta)
}
}
}

func TestCollectAttributedPRsWithIdentityFooter(t *testing.T) {
pr := &gh.PullRequest{Number: gh.Ptr(10480), Body: gh.Ptr(contributorAttributionBody)}
prs := collectAttributedPRsFromList("hivecommons/hive", []*gh.PullRequest{pr})
if len(prs) != 1 || !prs[0].HiveAttributed || prs[0].HiveBackend != "codex" || prs[0].HiveModel != "gpt-6-astra" {
t.Fatalf("attributed PRs = %+v", prs)
}
}

func TestFetchAttributedClosedPRsWithIdentityFooter(t *testing.T) {
now := time.Now().UTC().Truncate(time.Second)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/repos/o/r/pulls" || r.URL.Query().Get("state") != "closed" {
t.Errorf("unexpected request: %s", r.URL)
http.NotFound(w, r)
return
}
pr := attributedClosedPR(10480, now)
pr["body"] = contributorAttributionBody
pr["merged_at"] = now.Format(time.RFC3339)
writeJSON(t, w, []map[string]any{pr})
}))
defer srv.Close()
c := NewClientForTest(srv.URL, "o", []string{"r"}, slog.New(slog.NewTextHandler(io.Discard, nil)))
prs, err := c.fetchAttributedClosedPRs(context.Background(), "o", "r", "r")
if err != nil {
t.Fatal(err)
}
if len(prs) != 1 || prs[0].HiveBackend != "codex" || prs[0].HiveModel != "gpt-6-astra" || !prs[0].MergedAt.Equal(now) {
t.Fatalf("merged attributed PRs = %+v", prs)
}
}
Loading