feat(features): standard feature bundles — categories installed by harness init, not removable #162

Merged
dominik.polakovics merged 2 commits from afk/157 into main 2026-10-07 13:22:54 +02:00

Closes #157

What

  • Standard bundles. feature.yaml accepts standard: true (still strictly decoded). categories sets it; news stays optional.

  • harness init installs every standard bundle after the starter site, in name order, through addcmd.Run: files, hugo.toml edits, allowances and receipt. Its output gains an Installed standard features: section. A failed install exits 1 naming the feature, with no rollback. The install lives in cmd/harness (initSite), not initcmd, because addcmd's tests import initcmd.

  • harness features puts "standard": <bool> on every --json entry, with and without --site, and formatVersion stays 1. The text form appends [standard].

  • harness remove on an installed standard feature returns removecmd.StandardFeatureError before anything is read or written, with or without --purge. It exits 1 with status error and the message feature "categories" is a standard feature and cannot be removed. A site without the receipt still gets not-installed (exit 0). A feature this harness doesn't ship is never standard.

  • Remove JSON shape. Early failures, the refusal among them, now emit the full removeOutput shape with empty arrays (this replaces removeFailureOutput). So usage.categoriesRemovedFrom is present, and [], in every harness remove --json object. The change only adds fields.

  • Deleted the code that only removing categories used, with its tests:

    • the shipped-pages rule;
    • the category rewrites (categoryChanges, CategoriesRemovedFrom in ContentUsage/PurgeReport);
    • the revert halves of disableKinds/taxonomies/outputs, with revertContext and taxonomiesAfterRevert.

    The apply halves, step IDs and receipt format stay. RevertConfig reports a recorded edit whose step has no revert as kept. pageops.Delete and the editor's category handling are untouched.

  • harness add news no longer tells the user to run harness add categories.

  • Docs: README and scaffold/site/AGENTS.md describe standard features, categories on every new site, the refusal, and the standard field. They no longer describe removing categories.

Tests

  • New tests:
    • a starter-site file at a path a standard bundle ships fails a test;
    • every standard bundle installs cleanly on a fresh starter site;
    • the remove refusal leaves the tree byte-identical, with and without purge;
    • the full init → features → add → remove lifecycle in cmd/harness;
    • an init'd site builds with Hugo and renders /categories/.
  • make test passes (npm test: 1860 tests; go test ./...), and the Hugo tests ran against Hugo 0.163.3 extended. go vet is clean. gofmt flags only internal/theme/icon_test.go, which was already unformatted before this branch.
  • Manual smoke of the issue's "How to verify" steps 1–6 passes. Both remove attempts exit 1, and the tree is unchanged afterwards. add news + remove news restores the post-init tree exactly, and hugo writes public/categories/index.html.

Notes

  • harness init's "Created:" list now also includes top-level entries the standard install adds (layouts/).
  • The receipt header text is unchanged. It still reads "written by harness add categories … harness remove categories is driven by it", even when init wrote it, because the header is part of the receipt bytes.
  • No test covers the init failure path: on an empty target only an I/O error can trigger it.
  • deckle-hosted is out of scope, as the issue says. Until it reads standard, its new-site dialog, Features page and real-binary tests break in the ways the issue lists.

🤖 Generated with Claude Code

Closes #157 ## What - **Standard bundles.** `feature.yaml` accepts `standard: true` (still strictly decoded). `categories` sets it; `news` stays optional. - **`harness init`** installs every standard bundle after the starter site, in name order, through `addcmd.Run`: files, `hugo.toml` edits, allowances and receipt. Its output gains an `Installed standard features:` section. A failed install exits 1 naming the feature, with no rollback. The install lives in `cmd/harness` (`initSite`), not `initcmd`, because addcmd's tests import initcmd. - **`harness features`** puts `"standard": <bool>` on every `--json` entry, with and without `--site`, and `formatVersion` stays 1. The text form appends `[standard]`. - **`harness remove`** on an installed standard feature returns `removecmd.StandardFeatureError` before anything is read or written, with or without `--purge`. It exits 1 with status `error` and the message `feature "categories" is a standard feature and cannot be removed`. A site without the receipt still gets `not-installed` (exit 0). A feature this harness doesn't ship is never standard. - **Remove JSON shape.** Early failures, the refusal among them, now emit the full `removeOutput` shape with empty arrays (this replaces `removeFailureOutput`). So `usage.categoriesRemovedFrom` is present, and `[]`, in every `harness remove --json` object. The change only adds fields. - **Deleted** the code that only removing categories used, with its tests: - the shipped-pages rule; - the category rewrites (`categoryChanges`, `CategoriesRemovedFrom` in `ContentUsage`/`PurgeReport`); - the revert halves of `disableKinds`/`taxonomies`/`outputs`, with `revertContext` and `taxonomiesAfterRevert`. The apply halves, step IDs and receipt format stay. `RevertConfig` reports a recorded edit whose step has no revert as `kept`. `pageops.Delete` and the editor's category handling are untouched. - **`harness add news`** no longer tells the user to run `harness add categories`. - **Docs:** README and `scaffold/site/AGENTS.md` describe standard features, categories on every new site, the refusal, and the `standard` field. They no longer describe removing categories. ## Tests - New tests: - a starter-site file at a path a standard bundle ships fails a test; - every standard bundle installs cleanly on a fresh starter site; - the remove refusal leaves the tree byte-identical, with and without purge; - the full init → features → add → remove lifecycle in `cmd/harness`; - an init'd site builds with Hugo and renders `/categories/`. - `make test` passes (`npm test`: 1860 tests; `go test ./...`), and the Hugo tests ran against Hugo 0.163.3 extended. `go vet` is clean. `gofmt` flags only `internal/theme/icon_test.go`, which was already unformatted before this branch. - Manual smoke of the issue's "How to verify" steps 1–6 passes. Both remove attempts exit 1, and the tree is unchanged afterwards. `add news` + `remove news` restores the post-init tree exactly, and `hugo` writes `public/categories/index.html`. ## Notes - `harness init`'s "Created:" list now also includes top-level entries the standard install adds (`layouts/`). - The receipt header text is unchanged. It still reads "written by `harness add categories` … `harness remove categories` is driven by it", even when init wrote it, because the header is part of the receipt bytes. - No test covers the init failure path: on an empty target only an I/O error can trigger it. - deckle-hosted is out of scope, as the issue says. Until it reads `standard`, its new-site dialog, Features page and real-binary tests break in the ways the issue lists. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Drop the shipped-pages rule and the category rewrites from
internal/removecmd, and the revert halves of the categories bundle's
disableKinds, taxonomies and outputs steps with revertContext and
taxonomiesAfterRevert. A recorded edit whose step has no revert is kept.
usage.categoriesRemovedFrom stays in harness remove --json, always [].

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(features): standard feature bundles installed by harness init
All checks were successful
ci / build-and-test (pull_request) Successful in 10m38s
3e040ffad4
A bundle manifest may set standard: true; categories is the first.
harness init installs every standard bundle after the starter site,
exactly as harness add does, and names them. harness features marks
them ([standard], and a standard boolean on every --json entry within
formatVersion 1). harness remove refuses an installed standard feature
with status error and changes nothing; every remove --json object now
has the full shape, so usage.categoriesRemovedFrom is always present.
harness add news no longer points at harness add categories.

README and the scaffolded AGENTS.md describe standard features and
categories as installed on every new site.

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

[autoland] verdict: pass

This was generated by AI while landing a PR.

PASS — validated against #157.

  • Signal: Forgejo Actions ci / build-and-test green on the head (10m38s), not re-run. The head contains all of main, so there is no conflict.
  • Conventions: Conventional Commits title; Closes #157 present.
  • Diff vs. issue: every acceptance criterion is implemented and tested.
    • standard: true is strictly decoded.
    • initSite installs standard bundles in name order through addcmd.Run.
    • standard appears on every features --json entry.
    • removecmd.Run refuses an installed standard feature after reading the receipt and before any other read or write, so a site without the receipt still gets not-installed.
    • A remove that fails early now emits the full removeOutput shape, with categoriesRemovedFrom: [].
    • The shipped-pages rule, the category rewrites and the three categories config reverts are deleted. RevertConfig reports an edit with no revert as kept.
    • The news hint no longer mentions harness add categories.
    • README and scaffold/site/AGENTS.md are updated as CLAUDE.md requires.
  • Unchanged: pageops.Delete and DeletePlan, which the editor's server still uses. Old news receipts with taxonomy edits are still kept as unknown steps, the same as on main.
  • Note (accepted in #157, not a concern): deckle-hosted is out of scope. Until it reads standard, its new-site Categories checkbox, its Remove action for Categories, and its real-binary tests break as the issue lists.
[autoland] verdict: pass > *This was generated by AI while landing a PR.* **PASS** — validated against #157. - **Signal:** Forgejo Actions `ci / build-and-test` green on the head (10m38s), not re-run. The head contains all of `main`, so there is no conflict. - **Conventions:** Conventional Commits title; `Closes #157` present. - **Diff vs. issue:** every acceptance criterion is implemented and tested. - `standard: true` is strictly decoded. - `initSite` installs standard bundles in name order through `addcmd.Run`. - `standard` appears on every `features --json` entry. - `removecmd.Run` refuses an installed standard feature after reading the receipt and before any other read or write, so a site without the receipt still gets `not-installed`. - A remove that fails early now emits the full `removeOutput` shape, with `categoriesRemovedFrom: []`. - The shipped-pages rule, the category rewrites and the three categories config reverts are deleted. `RevertConfig` reports an edit with no revert as kept. - The news hint no longer mentions `harness add categories`. - README and `scaffold/site/AGENTS.md` are updated as CLAUDE.md requires. - **Unchanged:** `pageops.Delete` and `DeletePlan`, which the editor's server still uses. Old news receipts with taxonomy edits are still kept as unknown steps, the same as on `main`. - **Note (accepted in #157, not a concern):** deckle-hosted is out of scope. Until it reads `standard`, its new-site Categories checkbox, its Remove action for Categories, and its real-binary tests break as the issue lists.
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!162
No description provided.