Skip to content

feat: add BinEval evals support#43641

Merged
pelikhan merged 10 commits into
mainfrom
copilot/add-bineval-support
Jul 6, 2026
Merged

feat: add BinEval evals support#43641
pelikhan merged 10 commits into
mainfrom
copilot/add-bineval-support

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Adds first-class BinEval-style evaluation support: workflows declare atomic binary YES/NO questions in frontmatter, which are evaluated by a small LLM after safe_outputs completes, with results stored as JSONL artifacts and persisted to a git branch for historical comparison.

Schema

evals:
  - id: builds
    question: Does the generated code compile?
  - id: tests
    question: Are all tests passing?
  - id: focused
    question: Is the implementation limited to the requested change?

Extended form with model override:

evals:
  questions:
    - id: builds
      question: Does the generated code compile?
  engine-config:
    model: openai/gpt-4o
  runs-on: ubuntu-latest

Changes

Types & schema

  • pkg/constants/job_constants.goEvalsJobName, EvalsArtifactName, EvalsResultFilename, EvalsBranchPrefix
  • main_workflow_schema.jsonevals property with shorthand (list) and extended (object with engine-config/runs-on) forms
  • FrontmatterConfigEvals any raw field; WorkflowDataEvals *EvalsConfig parsed field

Config parser (evals_config.go)

  • EvalDefinition / EvalsConfig structs; parseEvalsFromFrontmatter() handles both forms
  • Validation: unique IDs, non-empty questions; evalsBranchName()evals/<workflow-id>

Job builder (evals_job.go, evals_steps.go)

  • buildEvalsJob()evals job positioned after safe_outputs, before conclusion; depends on agent+activation+safe_outputs (when present); ensureConclusionIsLastJob() auto-wires the tail dependency
  • Step pipeline: agent context prep → LLM harness via github-script → GitHub Models API (default openai/gpt-4o-mini, configurable) → step summary Markdown table → eval artifact upload → git branch persistence to evals/<workflow-id>/evals.jsonl
  • Response parsing: any text before the final YES/NO line is captured as rationale

Pipeline wiring

  • buildJobs() calls buildEvalsJob() after buildSafeOutputsJobs()
  • extractAdditionalConfigurations() calls parseEvalsFromFrontmatter()

Copilot AI and others added 2 commits July 5, 2026 22:46
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…n custom RunsOn test

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

@pelikhan pelikhan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot the evals job is essentially the detection job with a different harness, different artifact name and output names. Parameterize the details job function to reuse code. The evals harness must also use awf, agent engine etc...
mark feature as experimental

"type": "string",
"description": "Unique identifier for the evaluation question."
},
"question": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot add support for a different model per question

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an optional model field to each question item in both the shorthand list and extended object forms. The per-question override takes precedence over the top-level model field.

}
}
},
"engine-config": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove config, use same config as top level engine config
Add model field to setup default model for all questions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed engine-config and replaced it with a simple model string field at the top level of the extended form (same string type as the engine model field). Done in the schema, EvalsConfig, and the frontmatter parser.

Comment thread pkg/parser/schemas/main_workflow_schema.json
Comment thread pkg/workflow/evals_steps.go Outdated
)

// defaultEvalsModel is the default LLM used for evaluations when no engine-config is provided.
const defaultEvalsModel = "openai/gpt-4o-mini"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot use "small"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed defaultEvalsModel to "small" so the runtime picks an appropriate small/cheap model.

Comment thread pkg/workflow/evals_steps.go Outdated
const defaultEvalsModel = "openai/gpt-4o-mini"

// defaultEvalsModelEndpoint is the GitHub Models inference endpoint.
const defaultEvalsModelEndpoint = "https://models.github.ai/inference/chat/completions"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove field

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the defaultEvalsModelEndpoint Go constant. The endpoint is now defined as GITHUB_MODELS_ENDPOINT inside run_evals_harness.cjs.

Comment thread pkg/workflow/evals_steps.go Outdated
// buildEvalsHarnessScript returns the JavaScript body that drives the evaluation harness.
// The script is injected into the github-script action's `script:` field.
func buildEvalsHarnessScript(endpoint string) string {
return `const fs = require('fs');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot refactor into a JavaScript file in actions/setup/js

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to actions/setup/js/run_evals_harness.cjs. The step now requires it via the setup_globals + require() pattern used by other downstream jobs (e.g. setup_threat_detection.cjs).

Comment thread pkg/workflow/evals_steps.go Outdated
fmt.Fprintf(&step, " uses: %s\n", getCachedActionPin("actions/github-script", data))
step.WriteString(" with:\n")
step.WriteString(" script: |\n")
step.WriteString(" const fs = require('fs');\n")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot refactor js into actions/aetup/js

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to actions/setup/js/render_evals_summary.cjs. The step uses the same setup_globals + require() pattern.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category feature
Risk 🔴 High
Score 42/100
Action 🕐 defer

Score breakdown:

  • Impact: 28/50 — new BinEval evals subsystem, adds frontmatter schema + Go compiler jobs
  • Urgency: 8/30 — DRAFT, no CI data, 0 days old
  • Quality: 6/20 — draft PR, good description, tests included, no prior human review

Recommendation: Defer — PR is in DRAFT state. Contains significant new feature (+1175/-7 in 11 Go files) adding binary eval support to workflow frontmatter. Hold for author to mark ready and CI to pass.

Generated by 🔧 PR Triage Agent · 66.7 AIC · ⌖ 8.33 AIC · ⊞ 5.5K ·

Copilot AI and others added 2 commits July 6, 2026 01:28
- Mark evals as experimental in schema description
- Add per-question model override field to EvalDefinition
- Replace engine-config with top-level model field in EvalsConfig/schema
- Use $ref: github_actions_runs_on for evals.runs-on schema field
- Change default model hint from openai/gpt-4o-mini to small
- Remove defaultEvalsModelEndpoint constant
- Extract harness JS to actions/setup/js/run_evals_harness.cjs
- Extract summary JS to actions/setup/js/render_evals_summary.cjs
- Update steps to require CJS modules via setup_globals pattern
- Fix lint warnings in new JS files (getErrorMessage, String(), try/catch)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Add comment to evalQ.Model field explaining per-question override
- Log count of per-question model overrides in parseEvalsFromFrontmatter
- Restructure parseAnswer nested ternary into explicit if/else
- Add indeterminate field to parseAnswer return to avoid calling it twice
- Reuse parseAnswer result instead of calling it twice in main loop

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot the evals job is essentially the detection job with a different harness, different artifact name and output names. Parameterize the details job function to reuse code. The evals harness must ...

Addressed the actionable parts of this feedback:

  • Experimental: Added ⚠️ Experimental. to the evals schema description.
  • JS extraction: The harness and summary inline scripts are now in actions/setup/js/run_evals_harness.cjs and actions/setup/js/render_evals_summary.cjs, loaded via the same setup_globals + require() pattern used by the detection job's setup_threat_detection.cjs.
  • Schema simplification: Removed engine-config, replaced with a simple model string field (same style as top-level engine model); runs-on now uses $ref: github_actions_runs_on.

The deeper "use AWF/agent engine for execution" and full detection-job code-share refactor would require a more substantial rework of the harness architecture — happy to do that as a follow-up once the feature direction is confirmed.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

1 similar comment
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Hey @copilot-swe-agent 👋 — great work on the BinEval evals feature! This is a well-structured addition that brings first-class binary YES/NO evaluation support to agentic workflows.

The PR is focused, well-described, and ships with solid test coverage for both the config parser and the job builder (evals_config_test.go + evals_job_test.go). The schema supports both shorthand and extended forms, and the pipeline wiring (positioning after safe_outputs, before conclusion, with auto-wiring of the tail dependency) looks thoughtful.

This looks ready for review. 🟢

Generated by ✅ Contribution Check · 275.8 AIC · ⌖ 21.7 AIC · ⊞ 6.2K ·

@pelikhan

pelikhan commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@copilot

  • compiler must emit experimental feature warning
  • Remove current job compilation implementation, it will be reimplemented . Leave it as TODO. Remove JavaScript harness that was added.

Only keep JSON schema changes, compiler support.

Copilot AI and others added 2 commits July 6, 2026 01:51
…remove JS harness files

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial plumbing for “BinEval”-style binary evaluation questions in workflow frontmatter, including schema validation, parsing/validation, and compiler wiring. The implementation currently stops short of actually emitting an evals job (the job builder is a stub), so the feature is effectively configuration-only with an “experimental/pending” warning.

Changes:

  • Introduces evals frontmatter support: schema + raw frontmatter field + parsed EvalsConfig on WorkflowData.
  • Adds EvalsConfig parsing/validation logic and unit tests.
  • Adds evals-related job/artifact/branch constants and wires the compiler to attempt building an evals job (currently a no-op).
Show a summary per file
File Description
pkg/workflow/workflow_data.go Adds WorkflowData.Evals to hold parsed evals configuration.
pkg/workflow/frontmatter_types.go Adds raw evals frontmatter field (any) with explanatory comment.
pkg/workflow/evals_job.go Adds placeholder buildEvalsJob stub (currently returns nil).
pkg/workflow/evals_config.go Adds EvalsConfig / EvalDefinition types and frontmatter parsing + validation helpers.
pkg/workflow/evals_config_test.go Adds unit tests for evals parsing, validation, and helpers.
pkg/workflow/compiler_orchestrator_workflow.go Parses evals config during orchestration and emits an experimental warning when configured.
pkg/workflow/compiler_jobs.go Wires buildEvalsJob() into job build sequence (currently no-op).
pkg/parser/schemas/main_workflow_schema.json Adds evals schema (shorthand + extended forms) and includes various string/emoji normalization changes.
pkg/constants/job_constants.go Adds evals job name + artifact/filename/branch constants and marks evals as a built-in job name.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/9 changed files
  • Comments generated: 7
  • Review effort level: Low

Comment thread pkg/workflow/evals_job.go
Comment thread pkg/workflow/evals_config.go
Comment thread pkg/workflow/evals_config.go Outdated
Comment on lines +197 to +199
func evalsBranchName(workflowID string) string {
return "evals/" + workflowID
}
Comment thread pkg/constants/job_constants.go
Comment on lines +108 to +110
// EvalsBranchPrefix is the git branch prefix for persisting evaluation results.
// Results are stored in branches named evals/<workflow-id>/evals.jsonl.
const EvalsBranchPrefix = "evals"
Comment on lines 76 to 85
var KnownBuiltInJobNames = map[string]struct{}{
string(AgentJobName): {},
string(ActivationJobName): {},
string(PreActivationJobName): {},
string(PreActivationHyphenJobName): {},
string(DetectionJobName): {},
string(EvalsJobName): {},
string(SafeOutputsJobName): {},
string(SafeOutputsHyphenJobName): {},
string(UploadAssetsJobName): {},
Comment on lines +11614 to +11616
"evals": {
"description": "⚠️ Experimental. BinEval binary evaluation questions to run after safe-outputs and before the conclusion job. Can be a plain list of questions (shorthand) or an object with questions, model, and runs-on fields.",
"oneOf": [
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (597 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/43641-add-bineval-evals-support.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-43641: Add BinEval-Style Evals Support

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 43641-add-bineval-evals-support.md for PR #43641).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 65.8 AIC · ⌖ 9.84 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 88/100 — Excellent

Analyzed 20 test(s): 19 design, 1 implementation, 0 violation(s).

📊 Metrics (20 tests)
Metric Value
Analyzed 20 (Go: 20, JS: 0)
✅ Design 19 (95%)
⚠️ Implementation 1 (5%)
Edge/error coverage 13 (65%)
Duplicate clusters 0
Inflation No (1.06:1)
🚨 Violations 0
Test File Classification Issues
TestParseEvalsFromFrontmatter_Nil_WhenAbsent evals_config_test.go:17 design_test
TestParseEvalsFromFrontmatter_Nil_WhenExplicitNull evals_config_test.go:24 design_test
TestParseEvalsFromFrontmatter_ShorthandForm evals_config_test.go:31 design_test minor: assertions lack descriptive messages
TestParseEvalsFromFrontmatter_ExtendedForm evals_config_test.go:50 design_test
TestParseEvalsFromFrontmatter_ExtendedForm_WithModel evals_config_test.go:68 design_test
TestParseEvalsFromFrontmatter_PerQuestionModel evals_config_test.go:84 design_test ✅ has descriptive messages
TestParseEvalsFromFrontmatter_InvalidType evals_config_test.go:99 design_test
TestValidateEvals_RejectsEmptyQuestions evals_config_test.go:110 design_test
TestValidateEvals_RejectsDuplicateID evals_config_test.go:116 design_test
TestValidateEvals_RejectsEmptyQuestion evals_config_test.go:129 design_test
TestValidateEvals_AcceptsValidQuestions evals_config_test.go:141 implementation_test happy-path only
TestValidateEvals_NilConfig evals_config_test.go:151 design_test
TestParseEvalDefinition_MissingID evals_config_test.go:159 design_test
TestParseEvalDefinition_MissingQuestion evals_config_test.go:165 design_test
TestParseEvalDefinition_EmptyID evals_config_test.go:171 design_test
TestParseEvalDefinition_TrimsWhitespace evals_config_test.go:177 design_test
TestEvalsBranchName evals_config_test.go:188 design_test
TestHasEvals_NilConfig evals_config_test.go:198 design_test
TestHasEvals_EmptyQuestions evals_config_test.go:203 design_test
TestHasEvals_NonEmpty evals_config_test.go:208 design_test
⚠️ Flagged Tests (1)

TestValidateEvals_AcceptsValidQuestions (evals_config_test.go:141) — implementation_test. Pure happy-path: confirms valid config passes validateEvals with a single require.NoError. No error/edge assertions. Consider adding a single-question minimal config to strengthen the contract.

Minor (non-blocking): Assertion message context — Several tests (e.g. TestParseEvalsFromFrontmatter_ShorthandForm) call assert.Equal/assert.Len without a trailing descriptive message. Only TestParseEvalsFromFrontmatter_PerQuestionModel uses them consistently. Adding messages speeds up debugging when assertions fail.

Verdict

Passed. 5% implementation tests (threshold: 30%). No guideline violations (build tag present on line 1, no mock libraries used). Score: 88/100.

Score breakdown: design ratio 19/20 × 40 = 38 + edge coverage 13/20 × 30 = 19.5 + no duplicates +20 + no inflation +10 = 87.5 → 88

References:

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 60.1 AIC · ⌖ 25.8 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 88/100. 5% implementation tests (threshold: 30%). No guideline violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — two correctness issues that produce misleading errors to users, plus a semantic mismatch (full validation + warning but zero output) that needs a clear policy decision before shipping.

### Issues requiring changes

Correctness — silent failures in parser (2 issues):

  • questions: with a wrong type (non-list) silently falls through to "at least one question required" — the real cause is completely hidden
  • Non-string model: values are silently discarded at both the config and per-question levels, leaving the user on the default model with no diagnostic

Semantic contract (1 issue):

  • The compiler validates evals config fully and emits a warning, but buildEvalsJob is a permanent (nil, nil) stub. A user who resolves all validation errors still gets a compiled workflow with zero evals logic and no further signal. This needs either: (A) skip validation+warning until the job is real, or (B) make it a hard compile error when evals are declared.

Stale documentation (2 issues):

  • parseEvalsFromFrontmatter godoc shows engine-config: which the parser never reads — users following the example will get a confusing error
  • FrontmatterConfig.Evals comment still references the removed engine-config override

Low / advisory:

  • EvalDefinition yaml struct tags describe an unmarshal path that doesn't exist

🔎 Code quality review by PR Code Quality Reviewer · 143.9 AIC · ⌖ 5.7 AIC · ⊞ 5.4K
Comment /review to run again

Comment thread pkg/workflow/evals_config.go Outdated
if err != nil {
return nil, fmt.Errorf("evals.questions: %w", err)
}
cfg.Questions = questions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent type failure for non-list questions value swallows the real error — if questions is present but not a []any (e.g. a string, map, or integer), the inner type assertion fails silently, cfg.Questions stays nil, and the user gets "at least one question is required" with no indication that their questions field has the wrong shape.

💡 Suggested fix

Add an explicit else branch after the inner type assertion:

if questionsRaw, ok := v["questions"]; ok {
    if questionsList, ok := questionsRaw.([]any); ok {
        questions, err := parseEvalDefinitions(questionsList)
        if err != nil {
            return nil, fmt.Errorf("evals.questions: %w", err)
        }
        cfg.Questions = questions
    } else {
        return nil, fmt.Errorf("evals.questions: must be an array, got %T", questionsRaw)
    }
}

The current code path reaches validateEvals with an empty questions slice and produces a misleading "at least one question is required" error that completely obscures the root cause.

Comment thread pkg/workflow/evals_config.go
Comment thread pkg/workflow/evals_config.go Outdated
// - id: builds
// question: Does the generated code compile?
// engine-config:
// model: gpt-4o-mini

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Godoc example shows engine-config key that the parser never reads — the comment documents engine-config: model: gpt-4o-mini as valid frontmatter, but the implementation was updated to read model directly at the top level of the extended object. Any user following this example will get "at least one question is required" with no useful error.

💡 Suggested fix

Update the godoc to match the actual schema:

// Extended form:
//
//   evals:
//     questions:
//       - id: builds
//         question: Does the generated code compile?
//     model: openai/gpt-4o-mini   # optional, overrides per-question
//     runs-on: ubuntu-latest      # optional runner override

Comment thread pkg/workflow/frontmatter_types.go
Comment thread pkg/workflow/evals_job.go
Comment thread pkg/workflow/evals_config.go
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Summary

Reviewed using /tdd, /codebase-design, and /grill-with-docs. Found 4 actionable issues in the 4 new Go files; the test suite and architecture are strong.

📋 Issues to address (4 inline comments)
# File Issue Skill
1 evals_config.go:52 Doc comment shows engine-config: {model:...} — doesn't match schema or parser (model: is top-level) /grill-with-docs
2 evals_config.go:198 evalsBranchName hardcodes "evals/" instead of using EvalsBranchPrefix constant /codebase-design
3 main_workflow_schema.json Extended form missing required: ["questions"] and minItems: 1; schema-layer validation is weaker than Go-layer /tdd
4 evals_config.go:78 Wrong-type questions value silently produces 0 questions with no error /codebase-design

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 86.7 AIC · ⌖ 5.72 AIC · ⊞ 6.7K ·
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd, /codebase-design, and /grill-with-docs — requesting changes on 4 focused issues.

📋 Key Themes & Highlights

Key Issues (by priority)

  1. Doc/schema mismatch on engine-config — the Go doc comment and PR description show engine-config: { model: ... } but both the schema and the parser use a top-level model key. Users following the example will silently get no model override.
  2. Constant not used in evalsBranchNameEvalsBranchPrefix is declared in job_constants.go but the function hardcodes "evals/" instead.
  3. Schema missing required: ["questions"] on extended form — an empty evals: { model: gpt-4o } passes JSON Schema but fails Go validation; the schema should catch this earlier. Also missing minItems: 1 on both array forms.
  4. Silent data-loss when questions is wrong type — if questions: is not an array, the parser silently produces 0 questions with no error.

Positive Highlights

  • ✅ Comprehensive test suite (211 lines) — well-structured and covers both shorthand and extended forms
  • ✅ Clean separation into config parsing, validation, and job assembly phases
  • ✅ Nil-safe HasEvals() method and consistent pointer-receiver pattern
  • ✅ Whitespace trimming in ID and question fields prevents subtle validation bypasses
  • ✅ Good use of experimental warning + IncrementWarningCount() to signal non-production readiness

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 86.7 AIC · ⌖ 5.72 AIC · ⊞ 6.7K
Comment /matt to run again

Comment thread pkg/workflow/evals_config.go Outdated
// evalsBranchName returns the git branch name used to persist evaluation results.
// Format: evals/<workflow-id>
func evalsBranchName(workflowID string) string {
return "evals/" + workflowID

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] evalsBranchName hardcodes the prefix string instead of using the EvalsBranchPrefix constant defined for this exact purpose — a silent divergence risk if the prefix is ever changed.

💡 Suggested fix
func evalsBranchName(workflowID string) string {
	return constants.EvalsBranchPrefix + "/" + workflowID
}

Import github.com/github/gh-aw/pkg/constants and reference the constant, so that both places stay in sync automatically.

@copilot please address this.

Comment thread pkg/workflow/evals_config.go Outdated
// - id: builds
// question: Does the generated code compile?
//
// # Extended — object with questions list and optional engine-config

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] The doc comment shows engine-config:\n model: gpt-4o-mini but the schema and Go parser both use a top-level model key, not a nested engine-config object. Users following this example will silently get no model override applied.

💡 Suggested fix

Update the doc comment to match the actual schema:

//	# Extended — object with questions list and optional config
//	evals:
//	  questions:
//	    - id: builds
//	      question: Does the generated code compile?
//	  model: openai/gpt-4o-mini   # top-level, not under engine-config
//	  runs-on: ubuntu-latest

The PR description also repeats the same engine-config mismatch and should be updated.

@copilot please address this.

Comment thread pkg/parser/schemas/main_workflow_schema.json
}

func TestParseEvalsFromFrontmatter_InvalidType(t *testing.T) {
c := NewCompiler()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] No test covers the extended form with a missing questions key — e.g. evals: { model: gpt-4o }. The parser silently skips the questions block and then validateEvals returns an error, but the error path is untested.

💡 Suggested test
func TestParseEvalsFromFrontmatter_ExtendedForm_NoQuestions(t *testing.T) {
    c := NewCompiler()
    _, err := c.parseEvalsFromFrontmatter(map[string]any{
        "evals": map[string]any{"model": "openai/gpt-4o"},
    })
    require.Error(t, err)
    assert.Contains(t, err.Error(), "at least one question")
}

@copilot please address this.


case map[string]any:
// Extended form: object with questions and optional model/runs-on
if questionsRaw, ok := v["questions"]; ok {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] When questions is present in the extended form but is not a []any (e.g. a single string), the inner type assertion silently falls through and cfg.Questions remains nil — no error is returned. This is a silent data-loss path.

💡 Suggested fix

Add an else-error branch after the inner type assertion:

if questionsList, ok := questionsRaw.([]any); ok {
    // ... existing parsing
} else {
    return nil, fmt.Errorf("evals.questions: must be an array")
}

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BinEval evals support — review

The overall shape of this PR is solid: schema, parser, and validation are well-structured, test coverage is comprehensive, and the experimental stub approach (warn + no-op job builder) is a safe way to land the plumbing without breaking existing workflows.

Four actionable issues found:

  1. Doc/schema mismatch (evals_config.go line 58): the parseEvalsFromFrontmatter doc comment still shows engine-config: model: as the extended syntax, but both the JSON schema and the parser accept model: at the top level of the extended object. The schema also sets additionalProperties: false, so the documented form would fail validation silently.

  2. Silent swallow of wrong questions type (evals_config.go line 77): when the extended form's questions value is not a []any, the inner type assertion silently falls through and the downstream validateEvals error is generic. An explicit type check with a targeted message improves debuggability.

  3. Dead helper evalsBranchName (evals_config.go line 167): this function is defined but never called — the job stub that would use it is a no-op. Go will not error on an unexported dead function, but it will drift from the final design. Either defer it to the job-implementation PR or anchor it explicitly.

  4. Unused yaml: struct tags (evals_config.go line 175): EvalDefinition carries yaml: tags that are never used — the struct is populated by manual map unpacking. These tags create a false impression about how unmarshalling works.

Items 1 and 2 are blocking (correctness / user-facing error quality). Items 3 and 4 are maintainability issues that should be addressed before merge.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 100.8 AIC · ⌖ 6.28 AIC · ⊞ 4.9K

Comment thread pkg/workflow/evals_config.go Outdated
// - id: builds
// question: Does the generated code compile?
// engine-config:
// model: gpt-4o-mini

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment shows engine-config: model: gpt-4o-mini as the extended-form syntax, but the schema and parser both use a top-level model: key directly (not nested under engine-config:). The JSON schema also marks the extended object as additionalProperties: false, so using engine-config: would silently fail schema validation.

Update the comment to match the actual accepted syntax:

//  # Extended — object with questions list and optional model/runs-on
//  evals:
//    questions:
//      - id: builds
//        question: Does the generated code compile?
//    model: gpt-4o-mini
//    runs-on: ubuntu-latest

@copilot please address this.

Comment thread pkg/workflow/evals_config.go
Comment thread pkg/workflow/evals_config.go
Comment thread pkg/workflow/evals_config.go

func TestValidateEvals_AcceptsValidQuestions(t *testing.T) {
cfg := &EvalsConfig{
Questions: []EvalDefinition{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestValidateEvals_RejectsEmptyQuestion sets Question: " " (whitespace-only). The validator checks strings.TrimSpace(q.Question) == "", so this is valid test coverage for the validator path.

However, there is no test for the extended form where questions key is present but contains something other than a list (e.g., map[string]any{"questions": "not-a-list"}). As noted in the implementation comment, this silently falls through and produces a misleading error. A regression test here would pin the expected error message.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved blocking review feedback, and rerun checks once the branch is current.

Unresolved review feedback to address:

  • github-actions — parser should surface targeted errors for wrong-type questions and non-string model values
  • github-actions — resolve the semantic mismatch between validating/warning on evals and compiling no evals job output
  • github-actions — update stale engine-config documentation/comments to match the actual model schema
  • github-actions — clean up maintainability issues like the dead evalsBranchName helper / misleading yaml tags if still present

Please also make sure the ADR requirement is satisfied in the PR body before final handoff.

Run: https://github.com/github/gh-aw/actions/runs/28764674525

Generated by 👨‍🍳 PR Sous Chef · 4.08 AIC · ⌖ 9.33 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 6, 2026 03:16
…ifactName

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved blocking review feedback, and rerun checks once the branch is current.

Unresolved review feedback to address:...

Addressed all four items in commits fix: targeted parse errors, remove dead evalsBranchName, fix EvalsArtifactName and fix: rename ambiguous test for question-level non-string model:

  • Targeted parse errors: evals.questions now returns "must be a list of question objects, got <type>" when the value isn't []any; evals.model and per-question model now return "must be a string, got <type>" instead of silently ignoring the wrong type. Tests added for all three cases.
  • Stale engine-config comment: Updated the parseEvalsFromFrontmatter docstring to show the model field directly instead of the removed engine-config block.
  • Dead evalsBranchName helper: Removed from evals_config.go along with its TestEvalsBranchName test.
  • Misleading yaml struct tags: Removed yaml: tags from EvalDefinition fields — the struct is populated manually via parseEvalDefinition, not via YAML unmarshaling.
  • EvalsArtifactName consistency: Changed "eval""evals" to match the job name and evals.jsonl filename. ADR updated accordingly.

Copilot AI requested a review from gh-aw-bot July 6, 2026 03:18
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, ensure this branch is current with main, complete any remaining work, and rerun checks so maintainers can review quickly.

Run: https://github.com/github/gh-aw/actions/runs/28768103968

Generated by 👨‍🍳 PR Sous Chef · 6.16 AIC · ⌖ 13.2 AIC · ⊞ 7.1K ·
Comment /souschef to run again

…upport

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, ensure this branch is current with main, complete any remaining work, and rerun checks so maintainers can review quickly.

Run: https://github.com/github...

Branch merged with main (d7b9d3a). Local make fmt, make lint, and unit tests all pass. Ready for CI re-trigger by a maintainer.

@pelikhan pelikhan merged commit 5c0a525 into main Jul 6, 2026
29 checks passed
@pelikhan pelikhan deleted the copilot/add-bineval-support branch July 6, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants