Examples 03 — GitHub README Quality Report
Example 03

GitHub README Quality Report

Fetches a repository's README from raw.githubusercontent.com, trying the main and master branches in parallel and selecting whichever returns HTTP 200. Runs five AI quality probes concurrently, averages two scores deterministically, routes through a quality lane, and appends a test-coverage warning when the AI detects no mention of tests.

What this example teaches

  • Parallel HTTP fetches with deterministic branch selection — fetch main and master simultaneously, pick the 200 response with SelectStringOp, no try/catch
  • A probe fan-out: five AI ops all reading the same input wire, each asking a different question, all running in parallel
  • Score averaging with AddFloatOp + DivFloatOp — once the AI returns two floats, all maths is deterministic
  • The optional suffix pattern: SelectStringOp selects between an empty string and a warning message; StringConcatOp appends it — no conditional code in the driver
  • A custom preprocessing op (StringTruncateOp) that caps input to 8 KB to stay within AI context limits
View source on GitHub
The workflow

Pipeline structure

Parallel fetches feed a preprocessing op; five probes fan out; deterministic averaging feeds quality lanes; an orthogonal bool gate injects a warning suffix.

HTTPGetOp (main)
HTTPGetOp (master)
SelectStringOp
pick 200
TruncateOp
8 KB cap
purpose (str)
doc_score
clarity_score
has_tests (bool)
has_install (bool)
Add + Div
avg score
lane + concat
± warning
det op

Deterministic steps

HTTPGetOp (×2) + IfIntEqOp + SelectStringOp

Both branch fetches run in parallel. IfIntEqOp checks whether the main-branch status equals 200. SelectStringOp picks the corresponding body — pure boolean logic, no AI.

StringTruncateOp

A custom op that caps the README to 8,192 bytes, preventing very large READMEs from blowing AI context budgets. Configured via a max_bytes parameter in Setup().

AddFloatOp + ConstFloat64Op + DivFloatOp

Computes avg_score = (doc_score + clarity_score) / 2. The denominator is a constant wire (2.0) — no hardcoded values in Go driver code.

SelectStringOp + StringConcatOp

If has_tests is false, SelectStringOp routes a warning string; otherwise an empty string. StringConcatOp appends it. No conditional code in the driver.

AI op

AI steps

AIComputeStringToStringOp — purpose

Summarises the project's purpose in one sentence. Runs in parallel with the four score probes.

AIScoreOp (×2) — doc_score, clarity_score

Scores documentation completeness and clarity for new contributors on 0–1. Two independent calls that run concurrently.

AIBoolOp (×2) — has_tests, has_install

Answers yes/no: "does the README mention tests/CI?" and "does it contain installation instructions?". Both run concurrently alongside the score ops.

AIComputeStringToStringOp — quality lane (1 of 3)

Generates an endorsement (excellent ≥ 0.75), a constructive critique (ok ≥ 0.40), or an improvement plan (poor < 0.40), depending on the averaged score.

Implementation

Walkthrough

1. Parallel branch fetches with deterministic selection

Both fetch_main and fetch_master run without any condition — they always execute in parallel. IfIntEqOp converts the HTTP status to a boolean, and SelectStringOp acts as a pure data multiplexer: no imperative branching required.

main.go — Stage 1 (parallel fetches)
// Both fetches run in parallel — no Condition on either vertex.
Vertex("fetch_main").Op("HTTPGetOp").
    Input("URL", "main_url").
    Output("Body", "main_body").
    Output("StatusCode", "main_status").

Vertex("fetch_master").Op("HTTPGetOp").
    Input("URL", "master_url").
    Output("Body", "master_body").

// Was the main-branch fetch a 200?
Vertex("check_main").Op("IfIntEqOp").
    Input("A", "main_status").
    Input("B", "int_200").
    Output("Match", "main_ok").

// Pick the body whose branch actually exists.
Vertex("pick_readme").Op("SelectStringOp").
    Input("Cond", "main_ok").
    Input("IfTrue", "main_body").
    Input("IfFalse", "master_body").
    Output("Result", "readme_raw").

2. Five concurrent AI probes

After truncation, all five probes read from the same readme wire. With no mutual dependencies, the dagor engine schedules all five concurrently — five AI questions answered in roughly the time of one.

main.go — Stage 3 (five parallel probes)
Vertex("purpose_op").Op("AIComputeStringToStringOp").
    Params(map[string]string{"operation": "summarize the purpose in one sentence"}).
    Input("Input", "readme").
    Output("Result", "purpose_str").

Vertex("doc_score_op").Op("AIScoreOp").
    Params(map[string]string{"criterion": "documentation completeness"}).
    Input("Input", "readme").
    Output("Result", "doc_score").

Vertex("has_tests_op").Op("AIBoolOp").
    Params(map[string]string{"predicate": "does this README mention tests or CI?"}).
    Input("Input", "readme").
    Output("Result", "has_tests").

3. Deterministic score averaging

The average is computed with two op vertices — AddFloatOp sums the two score wires, and DivFloatOp divides by a constant wire. Both are typed library ops; no math lives in driver code.

main.go — Stage 4 (average score)
Vertex("sum_scores").Op("AddFloatOp").
    Input("A", "doc_score").
    Input("B", "clarity_score").
    Output("Result", "sum_scores_val").

Vertex("const_2").Op("ConstFloat64Op").
    Params(map[string]string{"Value": "2.0"}).
    Output("Result", "two_f").

Vertex("avg_score_op").Op("DivFloatOp").
    Input("A", "sum_scores_val").
    Input("B", "two_f").
    Output("Result", "avg_score").

4. Optional warning suffix with SelectStringOp

SelectStringOp acts as an in-graph ternary: when has_tests is true it routes an empty string; when false it routes the warning text. StringConcatOp appends whichever was selected. No if statements in the driver.

main.go — Stage 6 (warning injection)
// Registered: operator.RegisterOpFactory("warning_const", builtin.ContextValFactory[string](warningKey{}))
// Injected:   ctx = context.WithValue(ctx, warningKey{}, "\n\nWARNING: tests not mentioned")
Vertex("warning_const").Op("warning_const").
    Output("Result", "warning_text").

Vertex("test_warning").Op("SelectStringOp").
    Input("Cond", "has_tests").
    Input("IfTrue", "empty_text").   // tests mentioned: no warning
    Input("IfFalse", "warning_text"). // no tests: warning appended
    Output("Result", "test_warning_str").

Vertex("final_narrative").Op("StringConcatOp").
    Input("A", "narrative").
    Input("B", "test_warning_str").
    Output("Result", "final_narrative").

Run it

shell
export CLAUDE_API_KEY=<your key>

# Live slug — main branch
go run ./examples/readme-quality/ --slug akennis/dagor

# Live slug — master branch fallback
go run ./examples/readme-quality/ --slug tj/n

# Offline fixture
go run ./examples/readme-quality/ --fixture examples/readme-quality/testdata/dagor.md

Run it as an MCP server

The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the entire workflow as a single MCP tool, assess_readme. The Go SDK derives the tool's input schema from the workflow's UserInput struct and validates every tools/call request against it, so an agent runs the whole fetch → five-probe → score → verdict pipeline with one tool call.

shell
# Speak MCP over stdin/stdout instead of running once
go run ./examples/readme-quality --mcp

Register it with any MCP client by pointing the client at that command:

.mcp.json
{
  "mcpServers": {
    "readme-quality": {
      "command": "go",
      "args": ["run", "./examples/readme-quality", "--mcp"]
    }
  }
}

Token usage

Measured on testdata/dagor.md. Input costs are high because the full README (~2,100 tokens) is re-passed as context to each of the six probes independently.

Op Calls Input tokens Output tokens Total tokens
AIBoolOp 2 4,275 8 4,283
AIScoreOp 2 4,303 14 4,317
AIComputeOp 2 4,300 311 4,611
Total 6 12,878 333 13,211
← Example 02 Next: Weather Advisor →