agentic examples

The Changelog Scout

The Changelog Scout: reads real git history, classifies every commit through a contract-checked capability, and drafts the release notes - features first, fixes second, docs summarized in one line.

Developer Experience Round 3 Andrew Kane exit 0

source on github

bundle exec ruby examples/changelog_scout.rb

a real captured run

RELEASE NOTES (last 1 commits, drafted in 210ms)
============================================================
## Features
- The live tier (real LLM runs, recorded once) + artifact pipeline

source

# frozen_string_literal: true

# The Changelog Scout: reads real git history, classifies every commit
# through a contract-checked capability, and drafts the release notes -
# features first, fixes second, docs summarized in one line.
#
#   bundle exec ruby examples/changelog_scout.rb [commit_count]
#
# Runs offline against the current repo. Swap the classifier lambda for
# an LLM client when you want prose instead of parsing - the contract
# stays identical.

require class="s">"bundler/setup"
require class="s">"agentic"

ROOT = File.expand_path(class="s">"..", __dir__)
count = (ARGV.first || 40).to_i

# --- the classifier: one commit in, one classified entry out ---------------
spec = Agentic:class="y">:CapabilitySpecification.new(
  name: class="s">"classify_commit",
  description: class="s">"Classify one commit subject for release notes",
  version: class="s">"1.0.0",
  inputs: {subject: {type: class="s">"string", required: true}},
  outputs: {
    kind: {type: class="s">"string", required: true},
    note: {type: class="s">"string", required: true},
    breaking: {type: class="s">"boolean", required: true}
  }
)
Agentic.register_capability(spec, Agentic:class="y">:CapabilityProvider.new(
  capability: spec,
  implementation: ->(inputs) {
    subject = inputs[class="y">:subject]
    kind = subject[/\A(feat|fix|docs|refactor|test|chore)/, 1] || class="s">"other"
    note = subject.sub(/\A\w+(\([^)]*\))?!?:\s*/, class="s">"").sub(/\A(.)/) { $1.upcase }
    {kind: kind, note: note, breaking: subject.include?(class="s">"!:")}
  }
))

scribe = Agentic:class="y">:Agent.build { |a| a.name = class="s">"Scribe" }
scribe.add_capability(class="s">"classify_commit")

# --- the plan: classify commits in parallel, then one writer fans in --------
subjects = `git -C #{ROOT} log -#{count} --pretty=format:%s`
  .force_encoding(Encoding:class="y">:UTF_8).lines.map(&class="y">:strip)

orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 8)
classifications = subjects.map.with_index do |subject, i|
  task = Agentic:class="y">:Task.new(
    description: class="s">"commit #{i + 1}",
    agent_spec: {class="s">"name" => class="s">"Scribe", class="s">"instructions" => class="s">"classify"},
    payload: subject
  )
  orchestrator.add_task(task, agent: ->(t) {
    scribe.execute_capability(class="s">"classify_commit", {subject: t.payload})
  })
  task
end

notes = Agentic:class="y">:Task.new(
  description: class="s">"release notes",
  agent_spec: {class="s">"name" => class="s">"Editor", class="s">"instructions" => class="s">"draft the notes"}
)
orchestrator.add_task(notes, classifications, agent: ->(t) {
  entries = classifications.map { |c| t.output_of(c) }
  grouped = entries.group_by { |e| e[class="y">:kind] }

  sections = []
  sections << class="s">"## Breaking\n" + entries.select { |e| e[class="y">:breaking] }.map { |e| class="s">"- #{e[class="y">:note]}" }.join(class="s">"\n") if entries.any? { |e| e[class="y">:breaking] }
  {class="s">"feat" => class="s">"## Features", class="s">"fix" => class="s">"## Fixes", class="s">"refactor" => class="s">"## Internals"}.each do |kind, heading|
    items = grouped[kind] or next
    sections << class="s">"#{heading}\n#{items.map { |e| "- #{e[class="y">:note]}class="s">" }.join("\nclass="s">")}"
  end
  quiet = grouped.slice(class="s">"docs", class="s">"test", class="s">"chore", class="s">"other").values.flatten.size
  sections << class="s">"_...plus #{quiet} documentation, test, and housekeeping commits._" if quiet.positive?
  sections.join(class="s">"\n\n")
})

result = orchestrator.execute_plan

puts class="s">"RELEASE NOTES (last #{subjects.size} commits, drafted in #{(result.execution_time * 1000).round}ms)"
puts class="s">"=" * 60
puts result.results[notes.id].output