fix(init): validate --lang and --title and quote the title per file #278

Merged
dominik.polakovics merged 2 commits from afk/271 into main 2026-10-13 23:10:55 +02:00

Closes #271

What changed

  • Validation before any write. initcmd.Run checks --lang with pagepath.ValidLangTag and checks --title for control characters before it scans or plans anything. A refusal is a *initcmd.UsageError, and harness init maps it to exit 2 (harness init: --lang: "Bad Code" is not a valid language code: …). Nothing is written: an absent target stays absent and an empty one stays empty. A non-empty target still exits 1.

  • Each file gets the value in its own format. The scaffold templates no longer put quotes around the markers:

    • harness.yaml uses @@TITLE_YAML@@, @@LANG_YAML@@ and @@LANG_LABEL_YAML@@, written as single-quoted YAML scalars.
    • hugo.toml uses @@TITLE_TOML@@ and @@LANG_TOML@@, written as TOML strings.
    • AGENTS.md keeps the raw @@TITLE@@.

    Substitution runs in one strings.Replacer pass. Output for the default title and language is byte-identical to before, and a test pins it.

  • One owner for each rule. checkCode, checkLabel, tomlQuote and yamlQuote moved from internal/langcmd to a new internal/configvalue package as CheckLangCode, CheckOneLine, TOML and YAML. Both langcmd and initcmd call them. lang add messages are unchanged.

  • Docs. The init bullet in docs/harness.md now covers the validation, the exit-2 refusals and the per-file quoting. docs/agents/docs-map.md lists the new package. The skills (setup-site, migrate-site, redo-theme) mention init only as a step that has already run, so they need no change. README.md and scaffold/site/AGENTS.md (apart from its template marker) are untouched.

Verification

  • New tests in internal/initcmd:
    • Refusals for bad --lang values and for titles containing \n, \t and \x07, each checking that the target is untouched.
    • Round-trips of ', ", \, '' and non-ASCII titles through siteconfig.Load, Hugo's TOML decoder and the AGENTS.md heading.
    • The default output, byte for byte.
  • New tests in internal/configvalue: table tests for both renderers and both checks.
  • New test in cmd/harness: exit 2 and the stderr message for each refused flag value.
  • go vet ./... is clean and go test ./... passes. The subagent also ran the suite with Hugo 0.167.0 extended on PATH, so the hugo-build tests ran instead of skipping.
  • Manual run of the issue's four commands:
    • The bad --lang values and the multi-line title exit 2 and leave no directory.
    • --title "Bob's Site" exits 0. harness check reports ok: no problems found, harness.yaml has title: 'Bob''s Site' and hugo.toml has title = "Bob's Site".
  • Not done: harness serve on the new site.

Note

The title refusal reads harness init: --title: title "…" contains a line break or control character. The shared message format is <what> %q contains …, and the UsageError adds the flag prefix in front of it.

🤖 Generated with Claude Code

Closes #271 ## What changed - **Validation before any write.** `initcmd.Run` checks `--lang` with `pagepath.ValidLangTag` and checks `--title` for control characters before it scans or plans anything. A refusal is a `*initcmd.UsageError`, and `harness init` maps it to **exit 2** (`harness init: --lang: "Bad Code" is not a valid language code: …`). Nothing is written: an absent target stays absent and an empty one stays empty. A non-empty target still exits 1. - **Each file gets the value in its own format.** The scaffold templates no longer put quotes around the markers: - `harness.yaml` uses `@@TITLE_YAML@@`, `@@LANG_YAML@@` and `@@LANG_LABEL_YAML@@`, written as single-quoted YAML scalars. - `hugo.toml` uses `@@TITLE_TOML@@` and `@@LANG_TOML@@`, written as TOML strings. - `AGENTS.md` keeps the raw `@@TITLE@@`. Substitution runs in one `strings.Replacer` pass. Output for the default title and language is byte-identical to before, and a test pins it. - **One owner for each rule.** `checkCode`, `checkLabel`, `tomlQuote` and `yamlQuote` moved from `internal/langcmd` to a new `internal/configvalue` package as `CheckLangCode`, `CheckOneLine`, `TOML` and `YAML`. Both `langcmd` and `initcmd` call them. `lang add` messages are unchanged. - **Docs.** The `init` bullet in `docs/harness.md` now covers the validation, the exit-2 refusals and the per-file quoting. `docs/agents/docs-map.md` lists the new package. The skills (`setup-site`, `migrate-site`, `redo-theme`) mention `init` only as a step that has already run, so they need no change. `README.md` and `scaffold/site/AGENTS.md` (apart from its template marker) are untouched. ## Verification - New tests in `internal/initcmd`: - Refusals for bad `--lang` values and for titles containing `\n`, `\t` and `\x07`, each checking that the target is untouched. - Round-trips of `'`, `"`, `\`, `''` and non-ASCII titles through `siteconfig.Load`, Hugo's TOML decoder and the `AGENTS.md` heading. - The default output, byte for byte. - New tests in `internal/configvalue`: table tests for both renderers and both checks. - New test in `cmd/harness`: exit 2 and the stderr message for each refused flag value. - `go vet ./...` is clean and `go test ./...` passes. The subagent also ran the suite with Hugo 0.167.0 extended on PATH, so the hugo-build tests ran instead of skipping. - Manual run of the issue's four commands: - The bad `--lang` values and the multi-line title exit 2 and leave no directory. - `--title "Bob's Site"` exits 0. `harness check` reports `ok: no problems found`, `harness.yaml` has `title: 'Bob''s Site'` and `hugo.toml` has `title = "Bob's Site"`. - Not done: `harness serve` on the new site. ## Note The title refusal reads `harness init: --title: title "…" contains a line break or control character`. The shared message format is `<what> %q contains …`, and the `UsageError` adds the flag prefix in front of it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(init): validate --lang and --title and quote the title per file
All checks were successful
ci / build-and-test (pull_request) Successful in 3m13s
ci / release (pull_request) Has been skipped
22ffa978d4
harness init substituted --title and --lang into harness.yaml, hugo.toml
and AGENTS.md as raw text inside quoted template literals, so a bad
language tag or a title with a quote or line break produced a site that
did not load while init exited 0.

- init now refuses a --lang that fails pagepath.ValidLangTag and a
  --title holding a control character with a *initcmd.UsageError,
  before touching the target; the CLI maps it to exit 2.
- The templates no longer carry quotes: per-format markers render the
  title, code and label as a YAML scalar in harness.yaml, a TOML string
  in hugo.toml and raw text in the AGENTS.md heading. Default output is
  byte-identical.
- checkCode, checkLabel, tomlQuote and yamlQuote move from langcmd to a
  new internal/configvalue package that both init and lang add call.
- docs/harness.md documents the refusals and the quoting.

Closes #271

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

Verdict: CONCERNS (non-blocking). Approved, not merged.

Signal relied on: labctl pr checks 278 reported success. ci / build-and-test installs Hugo 0.167.0 extended and runs go vet ./..., go test ./... and go build, so the hugo-build tests ran as well. Nothing was re-run locally.

Conventions:

  • Conventional Commits title: met.
  • Closes #271: present.
  • Scope: matches the issue #271 brief. Validation runs before scan/plan, the *UsageError maps to exit 2, the rules moved to internal/configvalue and langcmd calls them, the templates no longer carry quotes, and the default output is pinned byte for byte.
  • No conflict with main.

Concern: docs/features.md:32 is now stale. It still says "@@TITLE@@, @@LANG@@, @@LANG_LABEL@@ and @@HUGO_VERSION@@ are substituted in exactly five files". This PR removes @@LANG@@ and @@LANG_LABEL@@, and the templates now use @@TITLE_YAML@@, @@TITLE_TOML@@, @@LANG_YAML@@, @@LANG_TOML@@ and @@LANG_LABEL_YAML@@ (internal/initcmd/init.go:51-59, scaffold/site/harness.yaml, scaffold/site/hugo.toml). The line should list the current markers, or say that each _YAML/_TOML marker receives a complete rendered scalar. Per CLAUDE.md "Every fact has one document", keeping that prose true is part of the change. No drift test catches it.

Note (not a concern): the issue's grep criterion still finds func yamlQuote in internal/content/apply_yaml.go:315. That function predates this PR and is a different rule: it renders double-quoted front-matter scalars, not single-quoted config scalars.

[autoland] verdict: pass **Verdict: CONCERNS (non-blocking). Approved, not merged.** Signal relied on: `labctl pr checks 278` reported `success`. `ci / build-and-test` installs Hugo 0.167.0 extended and runs `go vet ./...`, `go test ./...` and `go build`, so the hugo-build tests ran as well. Nothing was re-run locally. Conventions: - Conventional Commits title: met. - `Closes #271`: present. - Scope: matches the issue #271 brief. Validation runs before scan/plan, the `*UsageError` maps to exit 2, the rules moved to `internal/configvalue` and `langcmd` calls them, the templates no longer carry quotes, and the default output is pinned byte for byte. - No conflict with main. **Concern: `docs/features.md:32` is now stale.** It still says "`@@TITLE@@`, `@@LANG@@`, `@@LANG_LABEL@@` and `@@HUGO_VERSION@@` are substituted in exactly five files". This PR removes `@@LANG@@` and `@@LANG_LABEL@@`, and the templates now use `@@TITLE_YAML@@`, `@@TITLE_TOML@@`, `@@LANG_YAML@@`, `@@LANG_TOML@@` and `@@LANG_LABEL_YAML@@` (`internal/initcmd/init.go:51-59`, `scaffold/site/harness.yaml`, `scaffold/site/hugo.toml`). The line should list the current markers, or say that each `_YAML`/`_TOML` marker receives a complete rendered scalar. Per CLAUDE.md "Every fact has one document", keeping that prose true is part of the change. No drift test catches it. Note (not a concern): the issue's grep criterion still finds `func yamlQuote` in `internal/content/apply_yaml.go:315`. That function predates this PR and is a different rule: it renders double-quoted front-matter scalars, not single-quoted config scalars.
Author
Owner

Addressed the concern in 1c7164b: the init paragraph in docs/features.md now lists the current markers (@@TITLE_YAML@@, @@LANG_YAML@@, @@LANG_LABEL_YAML@@ for harness.yaml; @@TITLE_TOML@@, @@LANG_TOML@@ for hugo.toml; bare @@TITLE@@ for AGENTS.md; @@HUGO_VERSION@@) and says each _YAML/_TOML marker receives a complete rendered scalar. go test ./docs/ ./internal/initcmd/ is green.

Addressed the concern in 1c7164b: the init paragraph in `docs/features.md` now lists the current markers (`@@TITLE_YAML@@`, `@@LANG_YAML@@`, `@@LANG_LABEL_YAML@@` for `harness.yaml`; `@@TITLE_TOML@@`, `@@LANG_TOML@@` for `hugo.toml`; bare `@@TITLE@@` for `AGENTS.md`; `@@HUGO_VERSION@@`) and says each `_YAML`/`_TOML` marker receives a complete rendered scalar. `go test ./docs/ ./internal/initcmd/` is green.
docs(features): name the per-format init markers
All checks were successful
ci / build-and-test (pull_request) Successful in 3m5s
ci / release (pull_request) Has been skipped
1c7164b879
The init paragraph still listed @@LANG@@ and @@LANG_LABEL@@, which the
per-file quoting change replaced with _YAML and _TOML markers that each
receive a complete rendered scalar. List the current markers and the file
each lands in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!278
No description provided.