[object Object]

← back to Cli Printing Press

feat(cli): add live_api_verification scorecard dimension (#239)

440f654d8262c564e8d2f3bd8cbcdb7691fd1c8b · 2026-04-22 10:24:36 -0700 · Matt Van Horn

Makes live-API verification a distinct Tier 2 scorecard signal so a
CLI that passed verify against the real API is distinguishable from
one that only passed against a mock server.

scoreLiveAPIVerification reads VerifyReport.Mode and PassRate:
- nil report, mock mode, structural mode, or missing Mode -> unscored
- live mode -> PassRate / 10, capped at 10 when PassRate >= 95

When unscored, the dimension's 10-point slot is subtracted from
tier2Max, so grades for existing CLIs (no verify or mock-backed verify)
stay unchanged.

selfimprove.GenerateFixPlans produces a targeted remediation plan for
the new dimension, but only when it scored below 5 AND was actually
scored - an unscored dim means verify never ran live, which is the
prerequisite to improve, not a scoring problem.

Also reconciles the Tier 1/Tier 2 weight discrepancy: the scorecard
code uses 50/50 weighting, the AGENTS.md glossary said 60/40. The
code is authoritative; AGENTS.md now matches.

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 440f654d8262c564e8d2f3bd8cbcdb7691fd1c8b
Author: Matt Van Horn <mvanhorn@users.noreply.github.com>
Date:   Wed Apr 22 10:24:36 2026 -0700

    feat(cli): add live_api_verification scorecard dimension (#239)
    
    Makes live-API verification a distinct Tier 2 scorecard signal so a
    CLI that passed verify against the real API is distinguishable from
    one that only passed against a mock server.
    
    scoreLiveAPIVerification reads VerifyReport.Mode and PassRate:
    - nil report, mock mode, structural mode, or missing Mode -> unscored
    - live mode -> PassRate / 10, capped at 10 when PassRate >= 95
    
    When unscored, the dimension's 10-point slot is subtracted from
    tier2Max, so grades for existing CLIs (no verify or mock-backed verify)
    stay unchanged.
    
    selfimprove.GenerateFixPlans produces a targeted remediation plan for
    the new dimension, but only when it scored below 5 AND was actually
    scored - an unscored dim means verify never ran live, which is the
    prerequisite to improve, not a scoring problem.
    
    Also reconciles the Tier 1/Tier 2 weight discrepancy: the scorecard
    code uses 50/50 weighting, the AGENTS.md glossary said 60/40. The
    code is authoritative; AGENTS.md now matches.
    
    Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 AGENTS.md                                    |   2 +-
 internal/pipeline/scorecard.go               |  58 ++++++++-
 internal/pipeline/scorecard_live_api_test.go | 170 +++++++++++++++++++++++++++
 internal/pipeline/scorecard_tier2_test.go    |   4 +-
 internal/pipeline/selfimprove.go             |  48 ++++++--
 5 files changed, 268 insertions(+), 14 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 46f2d72d..f999970f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -89,7 +89,7 @@ Key terms used throughout this repo. Several have overloaded meanings — the gl
 | **dogfood** | Generation-time structural validation of a printed CLI against its source spec. Catches dead flags, invalid API paths, auth mismatches. Subcommand: `printing-press dogfood`. Compare with **doctor** (shipped in the CLI for end-users) and **verify** (runtime behavioral). |
 | **cliutil** | The generator-owned Go package emitted into every printed CLI at `internal/cliutil/`. Houses shared helpers meant for agent-authored novel code to import: `cliutil.FanoutRun` for aggregation commands (per-source error collection, bounded concurrency, source-order output), `cliutil.CleanText` for HTML/JSON-LD text normalization. **Generator-reserved namespace** — agents authoring novel code in Phase 3 must not put their code in `internal/cliutil/` or name their own helpers that collide with cliutil's exports. |
 | **shipcheck** | The three-part verification block that gates publishing: dogfood + verify + scorecard, run together. All three must pass before a printed CLI ships. |
-| **scorecard** / **scoring** | Two-tier quality assessment. Tier 1: infrastructure (12 dimensions, 60 pts). Tier 2: domain correctness (6 dimensions, 40 pts). Total /100 with letter grades. Subcommand: `printing-press scorecard`. |
+| **scorecard** / **scoring** | Two-tier quality assessment with a 50/50 weighted composite. Tier 1: infrastructure (16 string-matching dimensions, raw max 160, normalized to 0-50). Tier 2: domain correctness (7 semantic dimensions, raw max 60 when live verify ran, normalized to 0-50). Total /100 with letter grades. Source of truth: `internal/pipeline/scorecard.go` (tier1Max / tier2Max). Subcommand: `printing-press scorecard`. |
 | **doctor** | Self-diagnostic command shipped inside every printed CLI for end-users to run. Checks environment, auth config, and connectivity at the user's runtime. Unlike dogfood (which validates at generation time), doctor runs post-install. |
 | **auth doctor** | Subcommand on the printing-press binary (`printing-press auth doctor`). Scans every installed printed CLI's `tools-manifest.json` under `~/printing-press/library/<api>/` and reports env-var status (ok / suspicious / not_set / no_auth / unknown) with redacted fingerprints. Diagnostic only — never gates, never probes the network. Lives in `internal/authdoctor/`. |
 | **local library** | `~/printing-press/library/<api-slug>/` — where printed CLIs land after a successful run. Directory is keyed by API slug (e.g., `notion`), not CLI name. Local directory, not a git repo. |
diff --git a/internal/pipeline/scorecard.go b/internal/pipeline/scorecard.go
index db02b865..06663015 100644
--- a/internal/pipeline/scorecard.go
+++ b/internal/pipeline/scorecard.go
@@ -63,6 +63,7 @@ type SteinerScore struct {
 	SyncCorrectness       int    `json:"sync_correctness"`        // 0-10
 	TypeFidelity          int    `json:"type_fidelity"`           // 0-5
 	DeadCode              int    `json:"dead_code"`               // 0-5
+	LiveAPIVerification   int    `json:"live_api_verification"`   // 0-10; unscored when verify ran in mock/structural mode or was skipped
 	Total                 int    `json:"total"`                   // 0-100 (weighted: 50% infrastructure + 50% domain)
 	Percentage            int    `json:"percentage"`              // 0-100
 	CalibrationNote       string `json:"calibration_note,omitempty"`
@@ -144,6 +145,18 @@ func RunScorecard(outputDir, pipelineDir, specPath string, verifyReport *VerifyR
 	sc.Steinberger.TypeFidelity = scoreTypeFidelity(outputDir)
 	sc.Steinberger.DeadCode = scoreDeadCode(outputDir)
 
+	// LiveAPIVerification is scored only when verify ran in live mode (real
+	// API, not a mock server and not structural-only). Mock-backed verify
+	// passes and live verify passes look identical in dogfood output; making
+	// this a distinct dimension in the scorecard lets reviewers tell them
+	// apart at a glance and gives selfimprove a targeted fix plan when a
+	// shipped CLI has never been exercised against the real API.
+	if liveScore, scored := scoreLiveAPIVerification(verifyReport); scored {
+		sc.Steinberger.LiveAPIVerification = liveScore
+	} else {
+		sc.UnscoredDimensions = append(sc.UnscoredDimensions, "live_api_verification")
+	}
+
 	// Tier 1: Infrastructure (string-matching, 150 max; 140 when no MCP surface)
 	tier1Raw := sc.Steinberger.OutputModes +
 		sc.Steinberger.Auth +
@@ -169,13 +182,14 @@ func RunScorecard(outputDir, pipelineDir, specPath string, verifyReport *VerifyR
 		}
 	}
 
-	// Tier 2: Domain Correctness (semantic, 50 max)
+	// Tier 2: Domain Correctness (semantic, 60 max when live verify ran)
 	tier2Raw := sc.Steinberger.PathValidity +
 		sc.Steinberger.AuthProtocol +
 		sc.Steinberger.DataPipelineIntegrity +
 		sc.Steinberger.SyncCorrectness +
 		sc.Steinberger.TypeFidelity +
-		sc.Steinberger.DeadCode
+		sc.Steinberger.DeadCode +
+		sc.Steinberger.LiveAPIVerification
 
 	// Weighted composite: Tier 1 = 50%, Tier 2 = 50% of final 100-point scale.
 	// Tier 1 max is 160 with MCP + cache_freshness, 150 without MCP, 150 without
@@ -188,7 +202,14 @@ func RunScorecard(outputDir, pipelineDir, specPath string, verifyReport *VerifyR
 		tier1Max -= 10
 	}
 	tier1Normalized := (tier1Raw * 50) / tier1Max // scale 0-tier1Max to 0-50
-	tier2Max := 50
+	// Tier 2 max is 60 when live verify ran, 50 otherwise. The live-verify
+	// dimension is opt-in: a CLI that only ran mock-backed verify keeps the
+	// same effective tier2 denominator it had before the dimension existed,
+	// so grades for existing CLIs do not shift when this gate lands.
+	tier2Max := 60
+	if sc.IsDimensionUnscored("live_api_verification") {
+		tier2Max -= 10
+	}
 	if sc.IsDimensionUnscored("path_validity") {
 		tier2Max -= 10
 	}
@@ -738,6 +759,37 @@ func scoreCacheFreshness(dir string) (int, bool) {
 	return score, true
 }
 
+// scoreLiveAPIVerification returns a 0-10 score reflecting whether verify
+// ran against the real API and how many of its checks passed. It returns
+// (0, false) in every case where the signal is absent or untrustworthy:
+// no verify report threaded in, verify ran against a mock, or verify ran
+// in structural-only mode. A scored result always means the CLI has at
+// least been exercised against its real backend.
+//
+// PassRate is already 0-100 (e.g., 91.0 for 91%), matching the existing
+// calibration path in RunScorecard. The 95% cap at 10 mirrors the
+// established convention that near-perfect is perfect for grading.
+func scoreLiveAPIVerification(verifyReport *VerifyReport) (int, bool) {
+	if verifyReport == nil {
+		return 0, false
+	}
+	if verifyReport.Mode != "live" {
+		return 0, false
+	}
+	if verifyReport.PassRate >= 95 {
+		return 10, true
+	}
+	// Linear scale below the cap: 0% → 0, 10% → 1, ..., 94% → 9.
+	score := int(verifyReport.PassRate / 10)
+	if score < 0 {
+		score = 0
+	}
+	if score > 10 {
+		score = 10
+	}
+	return score, true
+}
+
 func scoreBreadth(dir string) int {
 	cliDir := filepath.Join(dir, "internal", "cli")
 	entries, err := os.ReadDir(cliDir)
diff --git a/internal/pipeline/scorecard_live_api_test.go b/internal/pipeline/scorecard_live_api_test.go
new file mode 100644
index 00000000..ea649318
--- /dev/null
+++ b/internal/pipeline/scorecard_live_api_test.go
@@ -0,0 +1,170 @@
+package pipeline
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// Verify the scoreLiveAPIVerification helper in isolation. It answers a
+// simple question - did verify run live, and if so how well did it do -
+// so its tests focus on the boundary between scored and unscored plus
+// the PassRate to score mapping.
+func TestScoreLiveAPIVerification(t *testing.T) {
+	t.Run("nil report is unscored", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(nil)
+		assert.Equal(t, 0, score)
+		assert.False(t, scored)
+	})
+
+	t.Run("mock mode is unscored", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "mock", PassRate: 100})
+		assert.Equal(t, 0, score)
+		assert.False(t, scored, "mock verify must not count as live verification, even at 100% pass")
+	})
+
+	t.Run("structural mode is unscored", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "structural", PassRate: 100})
+		assert.Equal(t, 0, score)
+		assert.False(t, scored)
+	})
+
+	t.Run("empty mode is unscored", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{PassRate: 100})
+		assert.Equal(t, 0, score)
+		assert.False(t, scored, "missing Mode defaults to unscored rather than rewarding pass rate from an unknown source")
+	})
+
+	t.Run("live 100% scores 10 and caps", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "live", PassRate: 100})
+		assert.True(t, scored)
+		assert.Equal(t, 10, score)
+	})
+
+	t.Run("live 95% hits the cap at 10", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "live", PassRate: 95})
+		assert.True(t, scored)
+		assert.Equal(t, 10, score, "95% should saturate the dimension")
+	})
+
+	t.Run("live 94% scores 9", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "live", PassRate: 94})
+		assert.True(t, scored)
+		assert.Equal(t, 9, score)
+	})
+
+	t.Run("live 50% scores 5", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "live", PassRate: 50})
+		assert.True(t, scored)
+		assert.Equal(t, 5, score)
+	})
+
+	t.Run("live 0% scores 0 but is scored", func(t *testing.T) {
+		score, scored := scoreLiveAPIVerification(&VerifyReport{Mode: "live", PassRate: 0})
+		assert.True(t, scored, "a live run with every check failing still produces a scored signal - it means the CLI was exercised")
+		assert.Equal(t, 0, score)
+	})
+}
+
+// Verify the scorecard wires LiveAPIVerification correctly end-to-end:
+// when a live VerifyReport is passed in, the dimension is populated and
+// is NOT in UnscoredDimensions; when the report is nil the dimension is
+// in UnscoredDimensions so tier2 normalizes as before.
+func TestRunScorecard_LiveAPIVerificationWiring(t *testing.T) {
+	t.Run("nil verify report adds live_api_verification to UnscoredDimensions", func(t *testing.T) {
+		dir := t.TempDir()
+		pipelineDir := t.TempDir()
+		sc, err := RunScorecard(dir, pipelineDir, "", nil)
+		assert.NoError(t, err)
+		assert.Contains(t, sc.UnscoredDimensions, "live_api_verification",
+			"nil verify report must mark live_api_verification unscored so existing CLIs grade unchanged")
+		assert.Equal(t, 0, sc.Steinberger.LiveAPIVerification)
+	})
+
+	t.Run("mock-backed verify also marks dimension unscored", func(t *testing.T) {
+		dir := t.TempDir()
+		pipelineDir := t.TempDir()
+		mock := &VerifyReport{Mode: "mock", PassRate: 100}
+		sc, err := RunScorecard(dir, pipelineDir, "", mock)
+		assert.NoError(t, err)
+		assert.Contains(t, sc.UnscoredDimensions, "live_api_verification",
+			"mock-backed verify and live verify must be distinguishable in the scorecard")
+	})
+
+	t.Run("live verify at 100 populates the dimension and removes it from UnscoredDimensions", func(t *testing.T) {
+		dir := t.TempDir()
+		pipelineDir := t.TempDir()
+		live := &VerifyReport{Mode: "live", PassRate: 100}
+		sc, err := RunScorecard(dir, pipelineDir, "", live)
+		assert.NoError(t, err)
+		assert.NotContains(t, sc.UnscoredDimensions, "live_api_verification")
+		assert.Equal(t, 10, sc.Steinberger.LiveAPIVerification)
+	})
+
+	t.Run("live verify at 70 scores 7", func(t *testing.T) {
+		dir := t.TempDir()
+		pipelineDir := t.TempDir()
+		live := &VerifyReport{Mode: "live", PassRate: 70}
+		sc, err := RunScorecard(dir, pipelineDir, "", live)
+		assert.NoError(t, err)
+		assert.Equal(t, 7, sc.Steinberger.LiveAPIVerification)
+	})
+
+	t.Run("json output exposes live_api_verification field", func(t *testing.T) {
+		dir := t.TempDir()
+		pipelineDir := t.TempDir()
+		live := &VerifyReport{Mode: "live", PassRate: 80}
+		sc, err := RunScorecard(dir, pipelineDir, "", live)
+		assert.NoError(t, err)
+		data, err := json.Marshal(sc)
+		assert.NoError(t, err)
+		assert.Contains(t, string(data), `"live_api_verification":8`)
+	})
+}
+
+// Guard R5 from the plan: landing LiveAPIVerification must not change
+// tier 2 normalization for CLIs that never ran live verify. When the
+// dimension is unscored, its 10-point slot is subtracted from tier2Max,
+// so the effective denominator matches what it was before this gate
+// landed.
+//
+// Note: we deliberately do not compare nil vs mock here. The scorecard
+// has a pre-existing calibration (line ~211) that raises Total to a
+// floor based on any verifyReport.PassRate regardless of mode. That
+// calibration is out of scope for this change. What we test here is
+// the unscored-dim math, which is what R5 actually constrains.
+func TestRunScorecard_UnscoredLiveDimDoesNotShrinkTier2(t *testing.T) {
+	dir := t.TempDir()
+	pipelineDir := t.TempDir()
+	sc, err := RunScorecard(dir, pipelineDir, "", nil)
+	assert.NoError(t, err)
+	assert.Contains(t, sc.UnscoredDimensions, "live_api_verification")
+	// With all spec-dependent and verify-dependent dims unscored, tier2
+	// normalization still runs. The Total being well-formed (0-100) is
+	// the invariant we care about here.
+	assert.GreaterOrEqual(t, sc.Steinberger.Total, 0)
+	assert.LessOrEqual(t, sc.Steinberger.Total, 100)
+}
+
+// A readable sanity check that live verify at a high pass rate actually
+// lifts the Total in a meaningful way. This prevents a future refactor
+// from silently converting the dimension into dead weight.
+func TestRunScorecard_LiveVerifyLiftsTotal(t *testing.T) {
+	dir := t.TempDir()
+	pipelineDir := t.TempDir()
+
+	scNil, err := RunScorecard(dir, pipelineDir, "", nil)
+	assert.NoError(t, err)
+	scLive, err := RunScorecard(dir, pipelineDir, "", &VerifyReport{Mode: "live", PassRate: 100})
+	assert.NoError(t, err)
+
+	assert.GreaterOrEqual(t, scLive.Steinberger.Total, scNil.Steinberger.Total,
+		"live verify at 100 must produce at least the same Total as no verify; a live-tested CLI should never score lower than an untested one")
+
+	// Sanity: the dimension name appears in the scorecard JSON (not just the struct).
+	data, err := json.Marshal(scLive)
+	assert.NoError(t, err)
+	assert.True(t, strings.Contains(string(data), "live_api_verification"))
+}
diff --git a/internal/pipeline/scorecard_tier2_test.go b/internal/pipeline/scorecard_tier2_test.go
index 1eea0324..16f32871 100644
--- a/internal/pipeline/scorecard_tier2_test.go
+++ b/internal/pipeline/scorecard_tier2_test.go
@@ -514,7 +514,7 @@ func runLinks() string {
 		pipelineDir := t.TempDir()
 		sc, err := RunScorecard(dir, pipelineDir, "", nil)
 		assert.NoError(t, err)
-		assert.ElementsMatch(t, []string{"mcp_token_efficiency", "cache_freshness", "path_validity", "auth_protocol"}, sc.UnscoredDimensions)
+		assert.ElementsMatch(t, []string{"mcp_token_efficiency", "cache_freshness", "path_validity", "auth_protocol", "live_api_verification"}, sc.UnscoredDimensions)
 		assert.NotContains(t, sc.GapReport, "path_validity scored 0/10 - needs improvement")
 		assert.NotContains(t, sc.GapReport, "auth_protocol scored 0/10 - needs improvement")
 	})
@@ -843,7 +843,7 @@ func runLinks() string {
 		body := string(data)
 		assert.True(t, strings.Contains(body, `"path_validity":0`))
 		assert.True(t, strings.Contains(body, `"auth_protocol":0`))
-		assert.True(t, strings.Contains(body, `"unscored_dimensions":["mcp_token_efficiency","cache_freshness","path_validity","auth_protocol"]`))
+		assert.True(t, strings.Contains(body, `"unscored_dimensions":["mcp_token_efficiency","cache_freshness","path_validity","auth_protocol","live_api_verification"]`))
 	})
 }
 
diff --git a/internal/pipeline/selfimprove.go b/internal/pipeline/selfimprove.go
index c26f8b63..e13bbe82 100644
--- a/internal/pipeline/selfimprove.go
+++ b/internal/pipeline/selfimprove.go
@@ -9,14 +9,15 @@ import (
 
 // templateMapping maps Steinberger dimensions to the template files responsible.
 var templateMapping = map[string][]string{
-	"output_modes":   {"root.go.tmpl", "helpers.go.tmpl"},
-	"auth":           {"config.go.tmpl", "auth.go.tmpl"},
-	"error_handling": {"helpers.go.tmpl"},
-	"terminal_ux":    {"helpers.go.tmpl"},
-	"readme":         {"readme.md.tmpl"},
-	"doctor":         {"doctor.go.tmpl"},
-	"agent_native":   {"root.go.tmpl", "helpers.go.tmpl"},
-	"local_cache":    {},
+	"output_modes":          {"root.go.tmpl", "helpers.go.tmpl"},
+	"auth":                  {"config.go.tmpl", "auth.go.tmpl"},
+	"error_handling":        {"helpers.go.tmpl"},
+	"terminal_ux":           {"helpers.go.tmpl"},
+	"readme":                {"readme.md.tmpl"},
+	"doctor":                {"doctor.go.tmpl"},
+	"agent_native":          {"root.go.tmpl", "helpers.go.tmpl"},
+	"local_cache":           {},
+	"live_api_verification": {},
 }
 
 // dimensionAdvice maps each dimension to concrete improvement guidance.
@@ -83,6 +84,26 @@ Templates to modify: root.go.tmpl (add flags), helpers.go.tmpl (add dry-run logi
 - Provides --clear-cache flag to purge
 
 Note: No existing template covers this - a new cache.go.tmpl is needed.`,
+
+	"live_api_verification": `Re-run verify against the real API instead of the mock server.
+A low score here means the CLI has never been exercised end-to-end against
+its real backend, so nothing catches wire-level breakage - wrong base URL,
+auth-header mismatch, pagination shape, content-type quirks.
+
+Steps to improve:
+1. Set the CLI's auth env var (e.g., GITHUB_TOKEN, HUBSPOT_API_KEY) to a
+   real read-only credential.
+2. Re-run verify with the credential set so VerifyConfig.APIKey is non-empty.
+3. Investigate any command that fails live but passes mock - the mock is
+   lying about one of: response shape, pagination cursor, error envelope.
+4. Fix the generator or the printed CLI depending on which side is wrong,
+   then re-run verify until PassRate is at or above 95%.
+
+Template surface: no template change usually needed; this dimension
+measures runtime behavior against a real endpoint, not generated-code
+structure. If the fix requires a template change, use the template that
+owns the failing surface (client.go.tmpl for HTTP details, root.go.tmpl
+for flag wiring, etc.).`,
 }
 
 // GenerateFixPlans creates a fix plan markdown file for each Steinberger
@@ -104,13 +125,24 @@ func GenerateFixPlans(scorecard *Scorecard, pipelineDir string) ([]string, error
 		{"doctor", scorecard.Steinberger.Doctor},
 		{"agent_native", scorecard.Steinberger.AgentNative},
 		{"local_cache", scorecard.Steinberger.LocalCache},
+		{"live_api_verification", scorecard.Steinberger.LiveAPIVerification},
 	}
 
+	// live_api_verification is Tier 2 and opt-in: when verify didn't run
+	// live, the dimension is unscored (score 0 but also flagged in
+	// UnscoredDimensions). We don't write a fix plan for an unscored dim
+	// because the operator already knows verify didn't run - that's the
+	// prerequisite, not a scoring problem.
+	liveUnscored := scorecard.IsDimensionUnscored("live_api_verification")
+
 	var plans []string
 	for _, d := range dimensions {
 		if d.score >= 5 {
 			continue
 		}
+		if d.name == "live_api_verification" && liveUnscored {
+			continue
+		}
 
 		planPath := filepath.Join(pipelineDir, fmt.Sprintf("fix-%s-plan.md", d.name))
 		content := buildFixPlan(scorecard.APIName, d.name, d.score)

← 00b9a0d4 feat(cli): reimplementation gate in absorb scoring and dogfo  ·  back to Cli Printing Press  ·  feat(cli): generate <cli> which <capability> resolver in eve 9dd5632a →