← back to Cli Printing Press
docs(plans): update gauntlet findings after parser fixes - 4/10 pass (was 3/10)
2501e565af31b2663a2b04ab2ce341a5a9af8146 · 2026-03-24 09:44:42 -0700 · Matt Van Horn
Parser fixes (3f096bc) unlocked Jira (317 paths, 7/7 gates).
Remaining 6 failures are deeper - $ and / chars leak through templates,
not just type mapping. Next fixes need to target generator/templates/*.tmpl.
Co-Authored-By: GPT-5.4 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
A docs/plans/2026-03-24-fix-parser-6-bugs-gauntlet-failures-plan.mdM docs/plans/dogfood-gauntlet-findings.md
Diff
commit 2501e565af31b2663a2b04ab2ce341a5a9af8146
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Tue Mar 24 09:44:42 2026 -0700
docs(plans): update gauntlet findings after parser fixes - 4/10 pass (was 3/10)
Parser fixes (3f096bc) unlocked Jira (317 paths, 7/7 gates).
Remaining 6 failures are deeper - $ and / chars leak through templates,
not just type mapping. Next fixes need to target generator/templates/*.tmpl.
Co-Authored-By: GPT-5.4 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
...-24-fix-parser-6-bugs-gauntlet-failures-plan.md | 284 +++++++++++++++++++++
docs/plans/dogfood-gauntlet-findings.md | 9 +-
2 files changed, 290 insertions(+), 3 deletions(-)
diff --git a/docs/plans/2026-03-24-fix-parser-6-bugs-gauntlet-failures-plan.md b/docs/plans/2026-03-24-fix-parser-6-bugs-gauntlet-failures-plan.md
new file mode 100644
index 00000000..3266ec44
--- /dev/null
+++ b/docs/plans/2026-03-24-fix-parser-6-bugs-gauntlet-failures-plan.md
@@ -0,0 +1,284 @@
+---
+title: "Fix 6 Parser Bugs from Dogfood Gauntlet - 3/10 to 8/10"
+type: fix
+status: completed
+date: 2026-03-24
+origin: docs/plans/dogfood-gauntlet-findings.md
+---
+
+# Fix 6 Parser Bugs from Dogfood Gauntlet
+
+## Overview
+
+The dogfood gauntlet ran 10 new API specs through the generator. 3 passed, 7 failed. Six distinct bugs in the parser and type generator cause all 7 failures. Fixing them should bring the pass rate from 3/10 to at least 8/10.
+
+## The Bugs (Priority Order)
+
+| Bug | Affects | Root Cause | Fix Location |
+|-----|---------|------------|--------------|
+| 1. `$ref`/`$schema` in type names | Fly.io, Vercel, Jira | Schema names with `$` prefix pass through to Go identifiers | `parser.go:mapTypes`, `parser.go:874` |
+| 2. Spec title too long for CLI name | Spotify | `cleanSpecName` has no length cap | `parser.go:cleanSpecName` (line 1526) |
+| 3. Missing `servers` field | Supabase | Parser requires base_url but spec has no servers array | `parser.go:42-56`, `spec.go:Validate` |
+| 4. `/` in enum values | Trello | Enum values like `modelTypes/card` become Go identifiers | `parser.go:mapTypes` field names, `generator/templates/types.go.tmpl` |
+| 5. Server URL templates unresolved | Sentry, Telegram | `{region}` in server URL used verbatim | `parser.go:44-46` |
+| 6. Missing `info.title` | Cloudflare | Spec uses `x-api-name` extension, no `info.title` | `parser.go:34-40` |
+
+## Acceptance Criteria
+
+- [ ] Fly.io (51 paths) passes all 7 gates
+- [ ] Spotify (68 paths) passes all 7 gates with auto-derived name
+- [ ] Vercel (85 paths) passes all 7 gates
+- [ ] Supabase (105 paths) passes all 7 gates
+- [ ] Trello (264 paths) passes all 7 gates
+- [ ] Jira (317 paths) passes all 7 gates
+- [ ] Sentry doctor shows resolved URL (not `{region}` literal)
+- [ ] Telegram doctor shows resolved URL (not `{token}` literal)
+- [ ] Cloudflare (1716 paths) at minimum parses without crash
+- [ ] `go test ./...` passes after every fix
+- [ ] Existing specs (petstore, stytch, discord, gmail) still pass all 7 gates
+
+## Implementation Units
+
+### Unit 1: Sanitize `$` from Schema Names and Field Names
+
+**Files:** `internal/openapi/parser.go`
+
+**Root cause:** `mapTypes` at line 874 uses the raw schema name (`name` from `doc.Components.Schemas`) as the Go type name. Schema names like `$ref`, `$schema`, `components.schemas.something` contain characters that are invalid in Go identifiers.
+
+Similarly, `collectTypeProperties` at line 920 uses raw field names that may contain `$`.
+
+**Fix:**
+1. In `mapTypes` (line 874), sanitize the schema name before using it:
+```go
+for _, name := range names {
+ goName := sanitizeTypeName(name)
+ if goName == "" {
+ continue
+ }
+ // ... rest uses goName instead of name for the type key
+```
+
+2. In `collectTypeProperties` (line 920), sanitize field names:
+```go
+for name, prop := range schema.Properties {
+ sanitized := sanitizeFieldName(name)
+ if sanitized == "" || strings.HasPrefix(sanitized, "_") {
+ continue
+ }
+ properties[sanitized] = prop
+}
+```
+
+3. Add `sanitizeTypeName` function:
+```go
+func sanitizeTypeName(name string) string {
+ // Remove $ prefix (JSON Schema $ref, $schema, $id)
+ name = strings.TrimLeft(name, "$")
+ // Remove dots, slashes, backslashes
+ name = strings.NewReplacer(
+ ".", "_",
+ "/", "_",
+ "\\", "_",
+ "-", "_",
+ " ", "_",
+ ).Replace(name)
+ // Remove any remaining non-alphanumeric/underscore chars
+ var b strings.Builder
+ for _, r := range name {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
+ b.WriteRune(r)
+ }
+ }
+ result := b.String()
+ // Must start with letter
+ if len(result) > 0 && !unicode.IsLetter(rune(result[0])) {
+ result = "T" + result
+ }
+ return result
+}
+```
+
+4. Add `sanitizeFieldName` (same logic but lowercase-first):
+```go
+func sanitizeFieldName(name string) string {
+ name = strings.TrimLeft(name, "$")
+ name = strings.NewReplacer(".", "_", "/", "_", "\\", "_").Replace(name)
+ var b strings.Builder
+ for _, r := range name {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
+ b.WriteRune(r)
+ }
+ }
+ return b.String()
+}
+```
+
+**Verification:** Fly.io, Vercel, and Jira pass `go vet` gate.
+
+**Execution note:** Fix-first. This unlocks 3 APIs at once. Run all 3 after the fix to verify.
+
+### Unit 2: Cap Spec Title Length for CLI Name
+
+**Files:** `internal/openapi/parser.go`
+
+**Root cause:** `cleanSpecName` (line 1526) filters noise words but has no length cap. Spotify's title "Spotify Web API with fixes and improvements from sonallux" produces a 60+ char CLI name that panics during command registration.
+
+**Fix:** After the filtered tokens are joined (line 1575), cap at 3 tokens max:
+
+```go
+// Cap at 3 meaningful tokens for CLI name
+if len(filtered) > 3 {
+ filtered = filtered[:3]
+}
+name := toKebabCase(strings.Join(filtered, " "))
+```
+
+Also add "with", "and", "from", "for", "the", "by", "of", "in", "on", "to" to the `noiseWords` map so they're stripped before the cap.
+
+**Verification:** Spotify generates as `spotify-web` or `spotify` (not the 60+ char monstrosity). Passes all 7 gates.
+
+### Unit 3: Handle Missing `servers` Field
+
+**Files:** `internal/openapi/parser.go`, `internal/spec/spec.go`
+
+**Root cause:** When `doc.Servers` is empty or nil, `baseURL` stays empty string. Then `spec.Validate()` rejects it with "base_url is required".
+
+**Fix:**
+1. In `parser.go` after the servers block (around line 56), if both `baseURL` and `basePath` are empty, set a placeholder:
+```go
+if baseURL == "" && basePath == "" {
+ warnf("no servers defined in spec; generated CLI will require base_url in config")
+ // Use a placeholder that doctor will report as "not configured"
+ baseURL = "https://api.example.com"
+}
+```
+
+2. In `spec.go:Validate()`, relax the base_url check - allow empty string but warn (or remove the requirement entirely since doctor already checks it at runtime).
+
+**Verification:** Supabase parses without error and passes all 7 gates. Doctor shows the placeholder URL.
+
+### Unit 4: Sanitize Enum Values with Slashes
+
+**Files:** `internal/openapi/parser.go`
+
+**Root cause:** Trello uses enum values like `modelTypes/card`, `modelTypes/board`. These flow through to the `types.go.tmpl` template where they become Go identifiers, causing syntax errors from `/`.
+
+The fix overlaps with Unit 1 - the `sanitizeFieldName` function should also be applied to enum values. But the enum values flow through the `mapSchemaType` function and the spec's `TypeField.Type` string.
+
+**Fix:** The enum values are used in the generated types template. Check where enum types are collected in `mapTypes` / `mapSchemaType` and apply sanitization:
+
+```go
+// In mapSchemaType or wherever enum values are extracted:
+for i, v := range enumValues {
+ enumValues[i] = sanitizeEnumValue(v)
+}
+```
+
+```go
+func sanitizeEnumValue(val string) string {
+ return strings.NewReplacer("/", "_", ".", "_", " ", "_", "-", "_").Replace(val)
+}
+```
+
+**Verification:** Trello passes `go build` gate.
+
+### Unit 5: Resolve Server URL Templates
+
+**Files:** `internal/openapi/parser.go`
+
+**Root cause:** OpenAPI 3.0 server URLs can contain template variables like `https://{region}.sentry.io`. The `kin-openapi` library parses `doc.Servers[0].Variables` which contains default values. The parser ignores these and uses the raw URL with `{variable}` literals.
+
+**Fix:** After extracting the server URL (line 45), resolve any template variables:
+
+```go
+serverURL := strings.TrimRight(strings.TrimSpace(doc.Servers[0].URL), "/")
+// Resolve server URL template variables
+if strings.Contains(serverURL, "{") && doc.Servers[0].Variables != nil {
+ for varName, variable := range doc.Servers[0].Variables {
+ if variable != nil && variable.Default != "" {
+ serverURL = strings.ReplaceAll(serverURL, "{"+varName+"}", variable.Default)
+ }
+ }
+}
+// If any unresolved variables remain, strip them with a sensible fallback
+if strings.Contains(serverURL, "{") {
+ // Remove the template variable and surrounding dots/slashes
+ // e.g., "{token}" in Telegram's "https://api.telegram.org/bot{token}" -> just strip it
+ for {
+ start := strings.Index(serverURL, "{")
+ if start == -1 {
+ break
+ }
+ end := strings.Index(serverURL, "}")
+ if end == -1 {
+ break
+ }
+ serverURL = serverURL[:start] + serverURL[end+1:]
+ }
+ serverURL = strings.ReplaceAll(serverURL, "//", "/")
+ serverURL = strings.TrimRight(serverURL, "/")
+}
+```
+
+**Verification:** Sentry doctor shows `https://us.sentry.io` (using default region). Telegram doctor shows resolved URL without `{token}`.
+
+### Unit 6: Fallback to `x-api-name` for Missing `info.title`
+
+**Files:** `internal/openapi/parser.go`
+
+**Root cause:** Cloudflare's spec has no `info.title` but uses `x-api-name` extension. The parser falls back to "api" as the name, then `spec.Validate()` fails with "name is required" because the spec also lacks other required fields.
+
+**Fix:** In the `Parse` function (lines 31-40), check for common extensions:
+
+```go
+name := "api"
+description := ""
+version := ""
+if doc.Info != nil {
+ if v := cleanSpecName(doc.Info.Title); v != "" && v != "api" {
+ name = v
+ } else if ext, ok := doc.Info.Extensions["x-api-name"]; ok {
+ if s, ok := ext.(string); ok {
+ name = cleanSpecName(s)
+ }
+ }
+ description = strings.TrimSpace(doc.Info.Description)
+ version = strings.TrimSpace(doc.Info.Version)
+}
+```
+
+**Verification:** Cloudflare parses without "name is required" error. May still fail on other gates due to spec size (1716 paths) but parsing should succeed.
+
+## Execution Loop
+
+After each unit, run the dogfood loop:
+
+```bash
+go test ./...
+go build -o ./printing-press ./cmd/printing-press
+
+# Re-run the failing APIs
+for api in flyio spotify vercel supabase trello jira cloudflare; do
+ rm -rf /tmp/dogfood-${api}-cli 2>/dev/null
+ ./printing-press generate --spec <url> --output /tmp/dogfood-${api}-cli 2>&1 | tail -3
+done
+```
+
+Track which APIs flip from FAIL to PASS after each fix.
+
+## Scope Boundaries
+
+- Only modify parser.go and spec.go - don't change templates unless enum sanitization requires it
+- Don't fix the "flat api resource" problem (Sentry/LaunchDarkly) - that's a separate enhancement
+- Don't fix the truncated description in --help banner - cosmetic, separate fix
+- Don't change the resource/endpoint limits (50/50)
+- Every fix must not break existing passing specs (petstore, stytch, discord, gmail, telegram, sentry, launchdarkly)
+
+## Sources
+
+- Dogfood gauntlet findings: `docs/plans/dogfood-gauntlet-findings.md`
+- Parser: `internal/openapi/parser.go` (1900+ lines)
+- Spec types: `internal/spec/spec.go`
+- Type template: `internal/generator/templates/types.go.tmpl`
+- Existing sanitizers: `sanitizeResourceName` (line 1495), `cleanSpecName` (line 1526)
+- kin-openapi server variables: `doc.Servers[0].Variables` (map of `*openapi3.ServerVariable`)
diff --git a/docs/plans/dogfood-gauntlet-findings.md b/docs/plans/dogfood-gauntlet-findings.md
index 47f44730..1179d93c 100644
--- a/docs/plans/dogfood-gauntlet-findings.md
+++ b/docs/plans/dogfood-gauntlet-findings.md
@@ -1,6 +1,6 @@
# Dogfood Gauntlet Findings - 2026-03-24
-## Scorecard: 3/10 Pass All 7 Gates
+## Scorecard: 4/10 Pass All 7 Gates (was 3/10 before parser fixes)
| # | API | Paths | Result | Failure Point | Root Cause |
|---|-----|-------|--------|---------------|------------|
@@ -15,9 +15,12 @@
| 9 | Jira | 317 | FAIL | go vet | types.go: malformed type definitions from deeply nested $ref schemas with numeric keys |
| 10 | Cloudflare | 1716 | FAIL | parse | `name is required` - spec missing required `info.title` field (Cloudflare uses `x-api-name` extension) |
-## Grade: D (3/10 pass)
+## Grade: D (4/10 pass after parser fixes, was 3/10)
-Below the 7/10 target. Generator needs targeted fixes for common patterns.
+Below the 7/10 target. Parser fixes (commit 3f096bc) unlocked Jira (317 paths).
+Remaining failures are in generated CLI command files and templates, not just types.
+The `$` and `/` characters leak through template rendering, not just type mapping.
+Next round of fixes needs to target `internal/generator/templates/*.tmpl`.
## Bugs Found
← 3f096bce fix(parser): sanitize schema names, cap title length, handle
·
back to Cli Printing Press
·
fix(generator): harden toCamel, flagName, types template for d8a93e0a →