Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/autonomy/feat-9585-retire-feature-map-2ee74fe9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The routine catalog's `feature-map-upkeep` class is now `control-skill-upkeep`: its pass is `/app-control:map refresh`, and a cloud run installs the app-control plugin in place of testing.
14 changes: 14 additions & 0 deletions .changes/testing/feat-9585-retire-feature-map-ca2edf38.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
bump: major
---

### Removed

- `/testing:map-features`, `/testing:refresh-feature-map` and the plugin's `reference/feature-map.md`. An app's feature map is now its control skill, which `/app-control:map` writes (`create`), checks (`check`) and keeps current (`refresh`).
- The `feature_map_dir` key. `/testing:run-e2e` no longer resolves it, `/testing:setup apply` drops the line when it rewrites `docs/conventions/testing.yaml` and names the dropped value with the conversion command, and the schema no longer lists it. A leftover line still never stops a test scan.

### Changed

- `/testing:run-e2e` reads the app's control skill (`.claude/skills/control-<app>/`) as its feature map: the hub's Ready replaces the doctor, before the first drive and after a failed step; the hub's driving tool replaces the recorded driver; `Attended runs only.` stops an unattended run. The per-entry-point report line is unchanged. A legacy testing-format map is not read: the run warns and names `/app-control:map refresh <path>`.

**Migration.** Run `/app-control:map refresh <path>` on each legacy map, at `.claude/skills/feature-map/` or wherever `feature_map_dir` pointed; it converts the map in its upkeep pull request. Then delete any `feature_map_dir` line. The scheduled `feature-map-upkeep` routine is now `control-skill-upkeep`, running `/app-control:map refresh`; its pull requests use `control-skill-upkeep/control-<slug>` branches, and open `feature-map-upkeep/*` branches are superseded (refresh lists them and adopts none). The first app-control refresh re-does every map, since no clean pass is recorded for a control skill yet.
4 changes: 2 additions & 2 deletions docs/conventions/config-cascade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,8 @@ its conformance cell.
| `ai-slop` | no-floor | `.claude/ai-slop.json` | all three | later layer | per-key (lists replace) | conforms; per-key override, resolved by `/ai-slop:audit` (user-global, team, `.claude/ai-slop.local.json` overlay). Four list keys are additive-by-replacement rather than merged (`vocab_add` / `vocab_remove` tune the shipped word list, `phrase_add` / `phrase_remove` the shipped model-era phrase roster; the later layer's list wins per key). No policy-floor class: every key is a taste dial over prose style, and a personal overlay that silences a rule weakens nothing another surface depends on. Keys owned by `/ai-slop:setup`; `_comment` is an allowed free-text annotation, not drift |
| `docs-naming` | team-only | `.claude/docs-naming.json` | all three | later layer; team on named policy-floor keys | per-key | conforms, with the one sanctioned dual-read declared by retirement records `docs-naming-r001` and `docs-naming-r002` (the retired `.claude/docs-hygiene.json` and `.claude/docs-hygiene.local.json` are read as authority, with a WARN, only while the new name is absent); per-key override on `file_names.*`, with a policy-floor class on `tiers`, `generated`, `sweep_exclude`, `sweep_exclude_sites`, and the three `exempt_*` keys: a personal layer may ADD entries and never remove them, and `generated` is team-layer only. Those keys decide what `/docs-naming:realign-file-names` does to a tree (which files are frozen, which reference forms are rewritten, and which shell command runs after a move), so narrowing one from a single machine would weaken a team decision, while adding a scope root or an exemption weakens nothing and stays open. `rule`, `regex`, and `redirect_map` are nearest-wins. Keys owned by [`plugins/docs-naming/reference/config.md`](../../../plugins/docs-naming/reference/config.md), which also partitions them from plugin `userConfig` (this plugin declares none, so a layer naming one is an inert unknown key). Written by `/docs-naming:setup apply`, resolved by `plugins/docs-naming/scripts/resolve-config.sh` |
| `rendered-views` | no-floor | `.claude/rendered-views.md` | all three | later layer | per-key | conforms; per-key override on `medium`, no policy-floor class (taste dial, the `ai-slop` precedent). Keys owned by [`rendered-views`](../rendered-views/README.md), which also partitions them from plugin `userConfig` dials (never keys in this surface; a layer declaring one is reported as an inert unknown key). Resolved by `visualization:visualize` (wave-1 exemplar) |
| `testing` (`run-e2e`) | no-floor | `.claude/testing/e2e.md` for `recording` / `browser_mode`; `docs/conventions/testing.yaml`, `~/.claude/testing.yaml` and `.claude/testing.local.yaml` for `e2e_driver` / `reuse_running_instance` / `browser_driver`, over the plugin's `userConfig`; `docs/conventions/testing.yaml` and `.claude/testing.local.yaml` only for `feature_map_dir` (no user-global file, no `userConfig`) | all three | later layer | per-key | conforms; per-key override, keys owned by `/testing:run-e2e`. The three YAML keys are resolved by `resolve-config.sh e2e`, which reports the supplying layer and resolves an unknown or refused value to the default without stopping |
| `testing` (`write`) | no-floor | `docs/conventions/testing.yaml` and `.claude/testing.local.yaml` for `tdd_refactor_step` (`on` \| `off`, default `on`) and `first_test` (`user-flow` \| `unit`, default `user-flow`); no user-global file, no `userConfig` | team + local overlay | later layer | per-key | declared deviation: repository settings, so the user-global layer is skipped with a warning, as for `feature_map_dir`. Keys owned by `/testing:write`; resolved by `resolve-config.sh write`, which reports the supplying layer and resolves an unknown value to the default without stopping |
| `testing` (`run-e2e`) | no-floor | `.claude/testing/e2e.md` for `recording` / `browser_mode`; `docs/conventions/testing.yaml`, `~/.claude/testing.yaml` and `.claude/testing.local.yaml` for `e2e_driver` / `reuse_running_instance` / `browser_driver`, over the plugin's `userConfig` | all three | later layer | per-key | conforms; per-key override, keys owned by `/testing:run-e2e`. The YAML keys are resolved by `resolve-config.sh e2e`, which reports the supplying layer and resolves an unknown or refused value to the default without stopping |
| `testing` (`write`) | no-floor | `docs/conventions/testing.yaml` and `.claude/testing.local.yaml` for `tdd_refactor_step` (`on` \| `off`, default `on`) and `first_test` (`user-flow` \| `unit`, default `user-flow`); no user-global file, no `userConfig` | team + local overlay | later layer | per-key | declared deviation: repository settings, so the user-global layer is skipped with a warning. Keys owned by `/testing:write`; resolved by `resolve-config.sh write`, which reports the supplying layer and resolves an unknown value to the default without stopping |
| `testing` (`audit`, `test-weaken`) | no-floor | team: `docs/conventions/testing.yaml` (schema `plugins/testing/schemas/testing.schema.json`), else the `yaml config` block in `docs/conventions/testing.md` (read for one more release), else `.claude/testing.yaml`; user-global `~/.claude/testing.yaml`; overlay `.claude/testing.local.yaml` | all three | later layer; the first present of `testing.yaml`, the docs block and `.claude/testing.yaml` in the team layer | per-key (scalars override, lists concatenate) | conforms to [ADR 0064](../../adr/0064-home-plugin-customization-in-docs-conventions-yaml.md), except the declared stop on an unusable layer, which replaces rule 5's soft degrade and its inert unknown keys (see Declared): one warning names each shadowed team file. Keys and layer merge owned by `plugins/testing/scripts/resolve-config.sh`; written by `/testing:setup apply`, read by `/testing:audit` and the `test-weaken` hook, which skip the `run-e2e` and `write` keys the same file carries |
| `testing` (`check-visual-parity`) | lowest-value | team: `docs/conventions/testing.yaml` read from origin's default branch (the working tree only when the repository has no `origin`); user-global `~/.claude/testing.yaml`; overlay `.claude/testing.local.yaml`; per user: the plugin's `pixel_tolerance` `userConfig` option | all three, plus the user option | lowest value (policy floor) | per-key | declared deviation from [ADR 0064](../../adr/0064-home-plugin-customization-in-docs-conventions-yaml.md) Decision 5's floor wording, as the `implementation` row: the `pixel_tolerance` map keeps a tighten-only `userConfig` option. The team value, else the default 0, is the ceiling; a personal layer or the option can only lower it, and a raise is reported as ignored. An invalid layer is named with its file or option, key and value and dropped (Decision 7). Resolved by `visual-compare.sh config` in `/testing:check-visual-parity`, which reports the supplying layer; the test scan skips the map and `/testing:setup apply` keeps it |
| `plugin-quality` | no-floor | convention doc at the consumer's convention home, `<home>/plugin-quality/README.md` (the pointer line binds `<home>`) | team, via pointer line | team via pointer line | convention doc | migrated (expression-doctrine pilot, ADR 0018): conformance is retirement record `plugin-quality-r001` (dual-read window while the retired `.claude/plugin-quality.md` persists: WARN-visible, the file reads as authority until cleaned); overlay layer retired by `plugin-quality-r002`, user-global layer retired prose-only (machine scope, outside the manifest); keys owned by the plugin's `reference/config.md` |
Expand Down
2 changes: 1 addition & 1 deletion docs/conventions/invocation-mode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ must be a deliberate human choice" either. This item records grades only; it cha
| `playbooks:repo-sweep` | none | Each mutating step already asks for approval: `plan` "open[s] the selection page" before it creates the branch and draft PR; `next` runs "audit, review findings with the user, fix, one commit, tick"; `review` will "audit and file each problem after approval". The sweep opens a draft PR, never merges, and its triggers ("sweep this repo", "next sweep step") are utterances. Not setup, not maintainer-only. | **FLIP → `false`** (approval-gated mutation) |
| `repo-fleet-hygiene:sync` | none | The mutation is approval-gated twice over: "Bare invocation prints a dry-run plan. Mutation requires --apply and one confirmation." (description); "`--yes` skips the script's prompt, not the user. Pass it only after you have shown the user the dry-run plan ... and the user has explicitly said to go ahead." Dirty work is parked, not deleted, and "The script does not reset a branch." Not setup, not maintainer-only. The 2026-08-17 KEEP of `repo-fleet-hygiene:apply` is an owner-decision sibling and is not revisited here. | **FLIP → `false`** (approval-gated mutation) |
| `session-flow:tidy-work` | none | Removal and moves are approval-gated: "Does not delete or move anything without `--apply` and a confirmation naming the paths."; "Ask one confirmation covering exactly the listed paths. A refusal, or silence, ends the run with nothing changed."; the `--apply` call "is not pre-approved, so it goes through the normal permission flow." The "Nothing runs unless the user invokes this skill" line says it is opt-in, which is not a class. Not setup, not maintainer-only. | **FLIP → `false`** (approval-gated mutation) |
| `testing:map-features` | none | "This skill writes files into the repository and commits nothing. It writes a new map; it never overwrites one." Where a `SKILL.md` already exists it "stop[s] and write[s] nothing." Additive and uncommitted, and it proves the map by invoking `/testing:run-e2e`, which is `false`, so the invariant does not block that call; a flipped `map-features` could itself be chained to, though `refresh-feature-map` and `run-e2e` only name it for the human to type. Its description's "User-invoked only." states the current flag, not a class. Not setup, not maintainer-only, and its triggers ("map the features of this app", "write a feature map") are utterances. | **FLIP → `false`** |
| `testing:map-features` | none | "This skill writes files into the repository and commits nothing. It writes a new map; it never overwrites one." Where a `SKILL.md` already exists it "stop[s] and write[s] nothing." Additive and uncommitted, and it proves the map by invoking `/testing:run-e2e`, which is `false`, so the invariant does not block that call; a flipped `map-features` could itself be chained to, though `refresh-feature-map` and `run-e2e` only name it for the human to type. Its description's "User-invoked only." states the current flag, not a class. Not setup, not maintainer-only, and its triggers ("map the features of this app", "write a feature map") are utterances. | **FLIP → `false`**; retired, replaced by `/app-control:map` |

Result: one KEEP (`discipline:be-ambitious`, class (i)) and five FLIPs. Fleet-wide, the non-setup
`true` population after the 2026-08-21 grade (9) plus these six is 15 skills, of which this section
Expand Down
2 changes: 0 additions & 2 deletions docs/skill-cheat-sheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@ owned by [docs/catalog-taxonomy.md](catalog-taxonomy.md).
| [`/testing:check-visual-parity`](../plugins/testing/skills/check-visual-parity/SKILL.md) | `testing` | Baseline screenshots before a change, pixel counts against them after |
| [`/testing:cleanup`](../plugins/testing/skills/cleanup/SKILL.md) | `testing` | Rewrite, quarantine or delete low-value tests behind a mutation gate, or delete cannot-fail ones |
| [`/testing:diagnose`](../plugins/testing/skills/diagnose/SKILL.md) | `testing` | Root-cause failing tests, never retry blindly |
| [`/testing:map-features`](../plugins/testing/skills/map-features/SKILL.md) | `testing` | Write a feature map that /testing:run-e2e drives entry point by entry point |
| [`/testing:plan`](../plugins/testing/skills/plan/SKILL.md) | `testing` | Classify changes by required test type and coverage gaps |
| [`/testing:refresh-feature-map`](../plugins/testing/skills/refresh-feature-map/SKILL.md) | `testing` | Keep an existing feature map accurate, one PR per map at most |
| [`/testing:run-e2e`](../plugins/testing/skills/run-e2e/SKILL.md) | `testing` | Start the app, drive real flows, capture evidence |
| [`/testing:test-value`](../plugins/testing/skills/test-value/SKILL.md) | `testing` | Where expected values come from and which tests earn their keep |
| [`/testing:write`](../plugins/testing/skills/write/SKILL.md) | `testing` | Write and place tests with TDD cadence across ecosystems |
Expand Down
Loading
Loading