← back to Cli Printing Press
fix(cli): omit version field from SKILL.md frontmatter (#656)
e6aa0326871498719683f74b22389faf3abd70d5 · 2026-05-06 19:50:36 -0700 · Trevin Chow
The original Hermes alignment work emitted a top-level `version:` field
sourced from the Press's printing_press_version (with a fallback to
version.Version). That conflated two unrelated concepts: the version of
the generator vs the version of the printed CLI being described. Hermes
treats `version:` as describing the skill itself, so emitting the Press
version actively misleads consumers.
Hermes documents `version` as optional (alongside name/description plus
optional tags, author, platforms, and metadata.hermes.config). OpenClaw
also tolerates absence. Omitting is the honest move until per-CLI
release versions can be stamped at library CI time from goreleaser tags.
Removes:
- `version: "{{pressVersion}}"` line from skill.md.tmpl
- `pressVersion` template helper (and its `version` package import)
- `readManifestPressVersion` from plan_generate.go and its test
- `version: "X.Y.Z"` from both golden SKILL.md fixtures
Updates the Hermes-frontmatter test to assert the field is absent.
Updates the active plan doc to mark the version-related decisions as
superseded with rationale, preserving the audit trail.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M docs/plans/2026-05-06-002-feat-hermes-openclaw-frontmatter-alignment-plan.mdM internal/generator/generator.goM internal/generator/plan_generate.goM internal/generator/plan_generate_test.goM internal/generator/skill_test.goM internal/generator/templates/skill.md.tmplM testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.mdM testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
Diff
commit e6aa0326871498719683f74b22389faf3abd70d5
Author: Trevin Chow <trevin@trevinchow.com>
Date: Wed May 6 19:50:36 2026 -0700
fix(cli): omit version field from SKILL.md frontmatter (#656)
The original Hermes alignment work emitted a top-level `version:` field
sourced from the Press's printing_press_version (with a fallback to
version.Version). That conflated two unrelated concepts: the version of
the generator vs the version of the printed CLI being described. Hermes
treats `version:` as describing the skill itself, so emitting the Press
version actively misleads consumers.
Hermes documents `version` as optional (alongside name/description plus
optional tags, author, platforms, and metadata.hermes.config). OpenClaw
also tolerates absence. Omitting is the honest move until per-CLI
release versions can be stamped at library CI time from goreleaser tags.
Removes:
- `version: "{{pressVersion}}"` line from skill.md.tmpl
- `pressVersion` template helper (and its `version` package import)
- `readManifestPressVersion` from plan_generate.go and its test
- `version: "X.Y.Z"` from both golden SKILL.md fixtures
Updates the Hermes-frontmatter test to assert the field is absent.
Updates the active plan doc to mark the version-related decisions as
superseded with rationale, preserving the audit trail.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
...t-hermes-openclaw-frontmatter-alignment-plan.md | 10 ++++----
internal/generator/generator.go | 16 ------------
internal/generator/plan_generate.go | 6 -----
internal/generator/plan_generate_test.go | 30 ----------------------
internal/generator/skill_test.go | 13 +++++++---
internal/generator/templates/skill.md.tmpl | 1 -
.../printing-press-rich-auth/SKILL.md | 1 -
.../printing-press-golden/SKILL.md | 1 -
8 files changed, 15 insertions(+), 63 deletions(-)
diff --git a/docs/plans/2026-05-06-002-feat-hermes-openclaw-frontmatter-alignment-plan.md b/docs/plans/2026-05-06-002-feat-hermes-openclaw-frontmatter-alignment-plan.md
index 1b7be44d..dd967e8e 100644
--- a/docs/plans/2026-05-06-002-feat-hermes-openclaw-frontmatter-alignment-plan.md
+++ b/docs/plans/2026-05-06-002-feat-hermes-openclaw-frontmatter-alignment-plan.md
@@ -30,7 +30,7 @@ A separate experience report: a Hermes session loaded a printed-CLI skill and di
- R2. Add Hermes-recognized top-level fields to `skill.md.tmpl` frontmatter: `version`, `author`, `license`. (Note: `metadata.hermes.tags` is not added — most printed-CLI specs lack a populated catalog category and the field would emit `[other]` for the majority. Defer until catalog enrichment work makes it useful.)
- R3. Move the existing `## CLI Installation` section in `skill.md.tmpl` (currently around line 379) to immediately after the H1 title and rewrite it with direct imperative wording so any host's LLM-driven agent reads it as instruction: "**You must verify the CLI is installed before invoking any command.**" Failure mode explicit ("do not proceed with skill commands until verification succeeds"). Title becomes `## Prerequisites: Install the CLI` to signal precondition rather than reference. No duplication — the existing section is moved, not copied.
- R4. Add `## Install via Hermes` and `## Install via OpenClaw` sections to `readme.md.tmpl`. Hermes section emits both CLI form and chat form in code fences. OpenClaw section emits the operator-copyable instruction in a code fence.
-- R5. `version` sources via two-tier resolution: read `.printing-press.json`'s `printing_press_version` if the manifest exists at template-render time (regen / sweep paths); fall back to `version.Version` otherwise (fresh-print path, where the manifest hasn't been written yet — `.printing-press.json` is written during publish, after templates render). The value tracks regenerations of the CLI; on Press releases that don't touch a given CLI, the field stays stable.
+- R5. ~~`version` sources via two-tier resolution from the Press's `printing_press_version`.~~ **Superseded by `fix/skill-md-omit-version`**: the SKILL.md `version:` field is intentionally omitted. The Press version describes the generator, not the CLI; emitting it as `version:` actively misleads consumers about what changed. Hermes lists `version` as optional; OpenClaw isn't launched. CI-time stamping from goreleaser tags in `printing-press-library` is a possible future addition.
- R6. `author` reads from `git config user.name` at template-render time via a new `resolveOwnerNameForRender()` helper (NOT a reuse of `resolveOwnerForNew`, which sanitizes to slug form, prefers `github.user`, and falls back to `"USER"` — wrong shape for a display name). YAML-escaped via the existing `yamlDoubleQuoted` helper. No manifest decouple, no flag, no userconfig package, no authors lookup file.
- R7. `printing-press-library`'s `.github/workflows/generate-skills.yml` triggers on `library/**/SKILL.md` and `library/**/README.md` changes so future hand-edits auto-mirror to `cli-skills/`.
- R8. A one-time sweep tool in `printing-press-library/tools/` line-targets the same shape onto every existing `library/<cat>/<api>/SKILL.md` and `library/<cat>/<api>/README.md`. Idempotent, snapshot-restore safe, follows the `tools/migrate-skill-metadata/main.go` precedent. Sweep covers all 49 CLIs (including the 5 from issue #654 — Phase 3 reprints supersede the sweep's edits for those 5). Sweep derives `<category>` for any URL-templated string from the directory path (`filepath.Base(filepath.Dir(skillMdPath))`), NOT from `.printing-press.json`'s `category` field (which is `omitempty` and present in only ~22% of legacy manifests).
@@ -97,7 +97,7 @@ All file paths in the plan are repo-relative to the unit's labeled repo.
- **Coexistence**: Hermes top-level fields and OpenClaw nested block live in the same frontmatter. Hermes ignores unknown keys per its docs; one SKILL.md serves both hosts.
- **No env-var declarations in either format**: false-positive on harvested vars is asymmetrically worse than no declaration. The existing `readme.md.tmpl` already branches its install instructions by `auth.Type` (api_key emits "set the env var" with the canonical name; cookie/composed/oauth2 emit "run `auth login` (variant)"; bearer_token similar). That branching IS the conservative classification we want — applied at the install-instruction level instead of the metadata level. The plan must not break this existing logic, and does not add a new generic "Authentication" section that would risk re-introducing the harvested-vs-user-set classification problem at a different surface.
- **Prerequisites section is plain markdown, not host-specific metadata**: any LLM-driven agent reads it regardless of frontmatter format support. Direct imperative ("you must verify") and explicit failure mode ("do not proceed") signal it as instruction.
-- **`version` tracks Press version that produced the current SKILL.md**: two-tier resolution (manifest if present, else `version.Version`). Bumps on regen, stable on Press releases that don't touch a given CLI. Honest framing — it doesn't promise more than that.
+- **`version` field omitted from SKILL.md frontmatter** (superseded decision; original plan tracked the Press version, removed in `fix/skill-md-omit-version` follow-up). The Press version describes the generator, not the CLI being described, and the printed CLI's release version is independent and not known at template-render time. Hermes lists `version` as optional. Possible future enhancement: library CI stamps `version:` from per-CLI goreleaser tags.
- **`author` reads git config at render time via a new helper**: not a reuse of `resolveOwnerForNew`, which sanitizes to slug form. New helper reads raw `git config user.name`, no sanitization. Empty value falls back to the slug-shaped `Owner` with a loud stderr warning rather than hard-failing — the generator package is reused by tests, mcp-sync, and regen-merge where setting `OwnerName` is awkward, so a hard error was over-strict. The sweep tool's per-CLI authorship mapping overrides this code path entirely; the soft-fallback only fires for fresh prints by users who haven't set `git config user.name`.
- **Library publish path hardcoded `mvanhorn/printing-press-library`** in templates. Single constant; no abstraction layer.
- **Sweep is line-targeted, not template re-rendering**: follows `tools/migrate-skill-metadata/main.go` precedent. Frontmatter region replaced surgically; Prerequisites section inserted via anchor; README sections inserted via anchor. Body content untouched.
@@ -126,7 +126,7 @@ All file paths in the plan are repo-relative to the unit's labeled repo.
**Approach:**
- Remove the `requires.env` line, the entire `envVars:` block, and any `primaryEnv:` line (legacy synthesized CLIs may have it; the live template doesn't emit it but the canonical post-strip shape needs to be unambiguous about absence). Lines 9-44 of the current template are the relevant region.
-- Add top-level `version: "{{.PressVersion}}"`, `author: "{{yamlDoubleQuoted .OwnerName}}"`, `license: "Apache-2.0"` after the existing `description:` field. License is a literal string, no helper needed.
+- Add top-level `author: "{{yamlDoubleQuoted .OwnerName}}"`, `license: "Apache-2.0"` after the existing `description:` field. License is a literal string, no helper needed. (Original plan also added `version: "{{pressVersion}}"`; that decision was superseded — see Key Technical Decisions and `fix/skill-md-omit-version` follow-up.)
- Keep `metadata.openclaw.requires.bins` and `metadata.openclaw.install`.
**Test scenarios:**
@@ -240,11 +240,11 @@ Install the pp-{{.Name}} skill from https://github.com/mvanhorn/printing-press-l
---
-### U4. Plumbing: `pressVersion` helper and `OwnerName` field
+### U4. Plumbing: `OwnerName` field
**Repo:** `cli-printing-press`
-**Goal:** Register the template helper and field that U1 consumes. License is a literal `"Apache-2.0"` in the template, no helper needed (zero variability).
+**Goal:** Register the `OwnerName` field that U1 consumes. License is a literal `"Apache-2.0"` in the template, no helper needed (zero variability). (Original plan also added a `pressVersion` template helper; the `version:` field it served was removed in `fix/skill-md-omit-version` follow-up, taking the helper with it.)
**Requirements:** R5, R6
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index db4094ec..9d2148b6 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -21,7 +21,6 @@ import (
"github.com/mvanhorn/cli-printing-press/v3/internal/naming"
"github.com/mvanhorn/cli-printing-press/v3/internal/profiler"
"github.com/mvanhorn/cli-printing-press/v3/internal/spec"
- "github.com/mvanhorn/cli-printing-press/v3/internal/version"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
@@ -255,21 +254,6 @@ func New(s *spec.APISpec, outputDir string) *Generator {
}
return naming.CLI(s.Name)
},
- // pressVersion returns the Press version that produced the SKILL.md
- // being rendered. Two-tier: read .printing-press.json's
- // printing_press_version when the manifest exists (regen / sweep
- // path — preserves the version that originally produced the file
- // on Press releases that don't touch this CLI), else fall back to
- // the live version.Version (fresh-print path — manifest is written
- // during publish, after templates render). The value bumps on
- // regen of the CLI, stays stable on Press releases that leave it
- // alone.
- "pressVersion": func() string {
- if v := readManifestPressVersion(g.OutputDir); v != "" {
- return v
- }
- return version.Version
- },
"graphqlQueryField": graphqlQueryField,
"graphqlFieldSelection": func(typeName string, types map[string]spec.TypeDef) []string {
return graphqlFieldSelection(typeName, types)
diff --git a/internal/generator/plan_generate.go b/internal/generator/plan_generate.go
index 39fe35d7..da9976d3 100644
--- a/internal/generator/plan_generate.go
+++ b/internal/generator/plan_generate.go
@@ -400,12 +400,6 @@ func resolveOwnerNameForNew() string {
return strings.TrimSpace(string(out))
}
-// readManifestPressVersion returns the `printing_press_version` field
-// from the manifest.
-func readManifestPressVersion(outputDir string) string {
- return readManifestField(outputDir, "printing_press_version")
-}
-
// sanitizeOwner cleans up an owner string for use in Go module paths.
func sanitizeOwner(s string) string {
s = strings.ToLower(s)
diff --git a/internal/generator/plan_generate_test.go b/internal/generator/plan_generate_test.go
index 93a445a0..5ee1e42b 100644
--- a/internal/generator/plan_generate_test.go
+++ b/internal/generator/plan_generate_test.go
@@ -198,36 +198,6 @@ func TestReadManifestOwnerName(t *testing.T) {
})
}
-func TestReadManifestPressVersion(t *testing.T) {
- t.Parallel()
-
- t.Run("returns version when manifest present", func(t *testing.T) {
- dir := t.TempDir()
- require.NoError(t, os.WriteFile(
- filepath.Join(dir, ".printing-press.json"),
- []byte(`{"printing_press_version":"3.10.0"}`),
- 0o644,
- ))
- assert.Equal(t, "3.10.0", readManifestPressVersion(dir))
- })
-
- t.Run("returns empty on fresh-print path (no manifest yet)", func(t *testing.T) {
- // Manifest is written by the publish step, after templates render.
- // Fresh-print render must fall back to version.Version, not error.
- assert.Equal(t, "", readManifestPressVersion(t.TempDir()))
- })
-
- t.Run("returns empty when field missing", func(t *testing.T) {
- dir := t.TempDir()
- require.NoError(t, os.WriteFile(
- filepath.Join(dir, ".printing-press.json"),
- []byte(`{"owner":"x"}`),
- 0o644,
- ))
- assert.Equal(t, "", readManifestPressVersion(dir))
- })
-}
-
func TestResolveOwnerNameForExisting(t *testing.T) {
t.Parallel()
diff --git a/internal/generator/skill_test.go b/internal/generator/skill_test.go
index 58e7d732..97cd9869 100644
--- a/internal/generator/skill_test.go
+++ b/internal/generator/skill_test.go
@@ -485,7 +485,6 @@ func TestSkillFrontmatterEmitsHermesTopLevelFields(t *testing.T) {
var parsed struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
- Version string `yaml:"version"`
Author string `yaml:"author"`
License string `yaml:"license"`
}
@@ -496,8 +495,16 @@ func TestSkillFrontmatterEmitsHermesTopLevelFields(t *testing.T) {
"author must be the prose-shaped OwnerName, not the slug — yamlDoubleQuoted preserves spaces and casing")
assert.Equal(t, "Apache-2.0", parsed.License,
"license is a constant for printed CLIs (LICENSE.tmpl is Apache 2.0)")
- assert.NotEmpty(t, parsed.Version,
- "version must be populated from .printing-press.json or version.Version fallback")
+
+ // version field is intentionally omitted — Hermes lists `version`
+ // as optional (https://hermes-agent.nousresearch.com/docs/developer-guide/creating-skills),
+ // the printed CLI's release version is independent of the Press
+ // version that produced this SKILL.md, and emitting the Press
+ // version would actively mislead consumers about what changed.
+ // CI-time stamping from goreleaser tags is a possible future addition
+ // in printing-press-library; for now, no version field is honest.
+ assert.NotContains(t, body, "version:",
+ "version field should be omitted — see learning docs in docs/solutions/")
}
// TestSkillFrontmatterOmitsAllEnvVarDeclarations asserts the post-Hermes-
diff --git a/internal/generator/templates/skill.md.tmpl b/internal/generator/templates/skill.md.tmpl
index de540273..633771d7 100644
--- a/internal/generator/templates/skill.md.tmpl
+++ b/internal/generator/templates/skill.md.tmpl
@@ -1,7 +1,6 @@
---
name: pp-{{.Name}}
description: "{{if and .Narrative .Narrative.Headline}}{{yamlDoubleQuoted .Narrative.Headline}}{{else}}Printing Press CLI for {{yamlDoubleQuoted .ProseName}}. {{yamlDoubleQuoted (oneline .Description)}}{{end}}{{if and .Narrative .Narrative.TriggerPhrases}} Trigger phrases: {{range $i, $p := .Narrative.TriggerPhrases}}{{if $i}}, {{end}}`{{yamlDoubleQuoted $p}}`{{end}}.{{end}}"
-version: "{{pressVersion}}"
author: "{{yamlDoubleQuoted .OwnerName}}"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
diff --git a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
index 9f751ff2..f7d73a4f 100644
--- a/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
+++ b/testdata/golden/expected/generate-golden-api-rich-auth/printing-press-rich-auth/SKILL.md
@@ -1,7 +1,6 @@
---
name: pp-printing-press-rich
description: "Printing Press CLI for Printing Press Rich. Purpose-built fixture for rich auth env-var model coverage."
-version: "3.10.0"
author: "printing-press-golden"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
diff --git a/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md b/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
index cac2d518..e1d91b0b 100644
--- a/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
+++ b/testdata/golden/expected/generate-golden-api/printing-press-golden/SKILL.md
@@ -1,7 +1,6 @@
---
name: pp-printing-press-golden
description: "Printing Press CLI for Printing Press Golden. Purpose-built fixture for golden generation coverage."
-version: "3.10.0"
author: "printing-press-golden"
license: "Apache-2.0"
argument-hint: "<command> [args] | install cli|mcp"
← fd7fa6ea feat(cli): Hermes/OpenClaw frontmatter alignment for printed
·
back to Cli Printing Press
·
feat(cli): add public parameter names (#648) cf91eab3 →