Skip to content

Migrations

What changed in each version that an existing project may need to know about or act on — not limited to changes to the context/ entry format. Also covers structural/placement conventions (e.g. how context/index.md is ordered) and config defaults added to AGENTS.md/AGENTS.local.md. Not every release needs an entry here — only ones with something an existing project should check. See setup.md for how and when this file gets consulted; note that "consulted" doesn't always mean "asks the user to act" — a purely informational entry (e.g. a config field silently backfilled to a default) is recorded here for completeness but needs no prompt.

Entries below assume 0.2.0 as the starting point — nothing before it tracked a context-schema at all, and 0.2.0 itself introduced no context/ entry format change.

0.16.0 — a Codex plugin install route, and the two wizards as two messages (informational, no action required)

What changed: the repository is installable as a Codex plugin (.codex-plugin/plugin.json plus a one-plugin marketplace), a third install route beside the skill directory and the Claude Code plugin. And the first-setup wizards are stated as two messages: the project list ends the turn, the personal list is the next message after the project answer, under batch as under sequential.

Existing projects and developers: nothing changes. A project set up by any route keeps its .keep-the-why and context/ untouched; the wizards don't run again. A Codex user who installed by path may switch to the plugin route at any time — same skill, same files — but nothing requires it.

0.15.0 — wizard defaults are the fully integrated values (informational, no action required)

What changed: three wizard defaults, for new setups only. confirmation-flow proposes batch instead of sequential, so a first setup is one list per wizard with the defaults filled in and one answer, not one question per message. local-lint proposes auto instead of ask: the personal wizard names the install in its question, and the answer — "defaults" included — is the go-ahead, so a default setup installs keep-the-why-lint from PyPI in the same turn. The project wizard's activation question defaults to the project asks instead of only when a developer asks: the "Keep the Why" section goes into the entry-point file, plus the project-scoped hook where autostart.md has a verified example for the current platform. Everything else keeps its default — capture-confirmation stays confirm-when-unsure, pending-confirmation-check stays no, no personal-defaults block unless asked for. The reasoning: the one-word "defaults" answer should be a complete, fully integrated setup, and whoever wants less picks less. "Project init wizard" and "Personal preferences wizard" in setup.md.

Existing projects and developers: nothing changes. A wizard default is what a new setup gets; a file that already exists keeps its values, and a line that is absent keeps its absent-field rule — confirmation-flow absent is still asked once, local-lint absent is still ask (a skill update never installs a package on an existing machine on its own), no capture-confirmation backfill changed. A project set up before 0.15.0 that wants the new defaults changes the lines by hand or asks the agent to; the activation section and hook are added the same way as before (autostart.md).

0.14.0 — local-lint: the linter as the agent's own check (informational, no action required)

What changed: a personal setting, local-lint: auto | ask | no (default ask), in ~/.keep-the-why/<id>.md and offerable through a project's personal-defaults. With auto or ask, the skill runs keep-the-why-lint after every write to the context location or .keep-the-why, and with --setup after a settings change — the two home files a CI run never sees. The linter's version must be at least the skill's: auto installs or updates it from PyPI without asking, ask asks first. Findings in files written this session are fixed and the run repeated; findings elsewhere are reported and left. context-schema is never lowered to satisfy an older linter. The personal wizard asks it as its fifth question; "Local linting" in setup.md has the whole rule. Purely additive: no entry-format change, nothing an existing project or developer must do.

Existing developers: the personal wizard doesn't run again; a personal file without the line means ask, the documented default. Nothing happens until the next write to context/; then, with no linter at the skill's version on the machine, the agent asks once whether to install it — a yes installs and runs it, a no is recorded as local-lint: no in the personal file so the question doesn't return. Nothing is ever installed without that yes. To skip the question, add - local-lint: auto or - local-lint: no to ~/.keep-the-why/<id>.md by hand or ask the agent to. A project that wants to suggest it for new developers adds the same line to its personal-defaults block. keep-the-why-lint accepts the field from 0.14.0.0 on; --setup exists from the same version.

0.13.0 — pending-confirmation Status, two optional settings, and an index.md letter skeleton (one mechanical step)

What changed: a new Status value, pending-confirmation, for an entry written during an unattended session — one declared so by the task or by session: unattended in ~/.keep-the-why/config (or, per project, in the personal file, which wins), never inferred — at a point where the project's capture-confirmation setting would normally require asking permission first. Rather than inventing confidence to skip the ask, or dropping the information because there was no one to ask, the entry gets written with Status: pending-confirmation standing in for whatever Status it would otherwise carry. See Core rule 5 and step 5 in SKILL.md, references/repository-structure.md, and "Unattended sessions" under the confirmation model in references/setup.md. Two settings come with it, both optional, both defaulting to the old behavior: session: attended | unattended in ~/.keep-the-why/config, overridable per project by the same line in the personal file (default attended), and pending-confirmation-check: on-start | no in the personal file (default no) — when on, a session starts by listing entries that still wait for a first confirmation, silently when there are none. The check also runs on request at any time.

New value (see references/repository-structure.md):

  • Status: pending-confirmation — never got a first human confirmation because no one was present to give one during an unattended session. Distinct from needs-review: that flags an entry that was current until a Revisit when trigger fired, whatever its Evidence; this flags a claim that never got a first confirmation at all. Resolving it replaces the flag with active, superseded, or open.

Migrating an existing project: informational, not a backfill pass — this doesn't touch any existing entry's recorded Status, and neither setting has to be written anywhere. An existing project only sees the value going forward, the first time a declared-unattended session actually hits a would-need-to-ask point. keep-the-why-lint accepts the value from context-schema 0.13.0 on (E113 below it) and knows pending-confirmation-check as a personal-defaults field.

Example:

**Status:** pending-confirmation
**Evidence:** inferred

Also new — references/specification.md: the normative definition of the config files, the context directory, the index and the entry format in one place; repository-structure.md keeps the worked examples and routing. Nothing to do — it documents what already holds.

Also changed — context/index.md gets a fixed letter skeleton: thirty-six level-2 headings, ## 0 through ## 9 then ## A through ## Z, always all of them, and every topic file listed under the heading of its filename's first character, sorted within the section — see references/specification.md and #194. The 0.10.0 sort order stays; the headings add a separator line between any two letters, so two pull requests adding differently-named topic files can no longer collide, however small the index.

Migrating an existing project (index): rebuild context/index.md into the skeleton fully, once — keep the title and any intro line, then the thirty-six headings in order with each existing entry moved under its letter. Mechanical, no per-entry judgment; do it now rather than next time touched, same reasoning as the 0.10.0 resort: the protection only exists once every entry sits under its heading. keep-the-why-lint reports a missing or misordered heading as E205 and an entry under the wrong heading as E206 from context-schema 0.13.0 on.

Example — after:

# Context index

## 0## 9

## A

- [architecture.md](architecture.md) — …

## B## Z

0.13.0 — id is a file name; configured paths stay inside the project (one mechanical check)

What changed: the three filesystem locations .keep-the-why can name are each confined to one directory, and the skill now says so instead of leaving it to the linter. id is letters, digits, ., _, - only — it names ~/.keep-the-why/<id>.md, and anything else could make that file land elsewhere. The uuid form's <folder-name> is slugified like the repo name (My Cool ProjectMy-Cool-Project). context and pinned-path are relative and resolve inside the project; a pinned SKILL.md is followed only if it says name: keep-the-why at the pinned version. A value outside its boundary is not read, written or followed — the skill names it and asks; keep-the-why-lint reports E009/E010. Details: setup.md, "Project config" and "Pinned versions"; trust-model.md, "Paths named by configuration".

Existing projects: check the id line once. Every id the skill generated from a git remote already fits; one generated from a folder name containing a space or another character outside the alphabet does not (123e4567-…---My Cool Project) — rewrite it in .keep-the-why and rename the matching ~/.keep-the-why/<id>.md on every machine that has one, same content. Mechanical, do it now rather than next time touched — the linter fails on it. Nothing to do for context or pinned-path unless they leave the project, which a working setup never had.

0.12.0 — start paths, ephemeral environments, action refs (informational, no action required)

What changed: three additions an existing project can adopt, none of which changes .keep-the-why, the personal file, or the context/ entry format.

  • Start paths. references/autostart.md now defines three ways the skill gets loaded at session start — every session machine-wide (a developer-level hook), the project asks (a project-scoped hook and/or a "Keep the Why" section in the project's entry-point file, AGENTS.md or its equivalent), or only when a developer asks — with per-agent sections stating what is verified how (Claude Code, Codex CLI, opencode, Cline by eval; Hermes live). The project init wizard asks about this as its last question for new projects.
  • Ephemeral environments. references/setup.md, "Ephemeral environments": how a devcontainer, Codespace or CI agent answers the personal wizard once, in the image — by baking the personal file, or by baking ~/.keep-the-why/config with personal-defaults-policy: auto-accept and letting the project's personal-defaults block supply the values. Both mechanisms existed since 0.10.0; the recipe is new.
  • Action refs. The GitHub Action now installs the linter its own ref belongs to: @lint-latest keeps rolling, @lint-v<version> and @<commit-sha> pin action and linter together. Older tags keep their old action.yml, so nothing already pinned changes behavior.

Existing projects: all optional. A project that wants the skill loaded without anyone asking picks a start path from autostart.md and, for the entry-point route, pastes the section into its AGENTS.md (or equivalent) — the wizard's step 2 wording, by hand or by asking the agent. A project whose developers work in throwaway environments bakes one of the two files. A project on @lint-latest does nothing; one that pinned the action for reproducibility now gets the reproducibility it pinned for on its next bump.

0.12.0 — init: declined retired (informational; one optional deletion)

What changed: a setup request that is called off no longer writes init: declined to a new .keep-the-why — it writes nothing at all. The flag dated from the time an organic activation could propose setup and needed a "don't ask again" marker; since 0.10.0 setup only ever starts from an explicit request, so there was nothing left for the flag to suppress, while the file it created made every .keep-the-why-gated autostart hook fire on a project that had just said no.

Existing projects: a .keep-the-why whose config block carries init: declined (and nothing but the id) is a leftover of a setup that never happened — delete the file. The skill has no special handling for the value anymore: it's an unrecognized init value, so the setup check names the valid option (complete) and asks, rather than guessing; keep-the-why-lint reports it as an invalid value. Both point at the same one-line fix.

0.11.0 — CI linting available (informational, no action required)

What changed: the project init wizard now offers to wire keep-the-why-lint — a structural linter for .keep-the-why and the context directory, gated by the project's context-schema — into the project's CI (GitHub Actions or GitLab CI, detected from the repository) and, where pre-commit is already in use, into pre-commit. Purely additive: no config field, no entry-format change, nothing an existing project must do.

Existing projects: optional. To add it, apply the same detection rules and snippets as the wizard would — references/ci-linting.md — by hand or by asking the agent. The workflow file's presence is the only state there is.

Already wired with @latest? The first published snippet referenced uses: oliver-zehentleitner/keep-the-why@latest; that tag follows skill releases and didn't carry action.yml until the next one, so the job fails at setup ("Can't find 'action.yml'"). Switch the ref to @lint-latest (moves with every linter publish) or @lint-v<version> — a one-word mechanical fix, do it now rather than next time touched.

0.11.0 — Core rules renumbered (15 → 11)

What changed: SKILL.md's 15 core rules were merged down to 11 — no rule's logic changed, but most rule numbers did. Merges: old 1+14 → 1 (never invent / clarify ambiguity), old 3+4+5 → 3 (adapt to what exists), old 9+10 → 7 (privacy / don't commit unasked). Full old → new map:

1 → 1    4 → 3    7 → 5    10 → 7    13 → 10
2 → 2    5 → 3    8 → 6    11 → 8    14 → 1
3 → 3    6 → 4    9 → 7    12 → 9    15 → 11

Migrating an existing project: informational for most — nothing in .keep-the-why or the context/ entry format changed. But a project whose own files quote rule numbers (a context/ entry citing "rule 13's proportionality gate", a CONTRIBUTING.md pointing at "rule 10") now points at the wrong rule. One mechanical pass, once: grep the project for rule <number> references to this skill's core rules and remap them per the table — cheap, complete, and a stale number actively misleads, so don't defer it to "next touched". CHANGELOG.md-style historical records keep the numbering that was current at the time; only living documents (context entries, docs, contributor guides) get remapped.

0.10.0 — Project/personal config moves into dedicated .keep-the-why files

What changed: the project config block (<!-- keep-the-why:config -->) moves out of the entry-point file (AGENTS.md, or whatever a project already uses) into a dedicated .keep-the-why file at the project root. The personal config block (<!-- keep-the-why:local -->) moves out of AGENTS.local.md into a dedicated, non-project file at ~/.keep-the-why/<id>.md, keyed by a new id field the project file now carries. See "Why dedicated files, not entry-point blocks" in context/config-format.md for the reasoning, and references/setup.md for the full format and detection logic. Three optional additions ship alongside the relocation, none of which existing projects are required to adopt: a personal-defaults block a project can offer new developers (plus a machine-wide ~/.keep-the-why/config policy governing whether that's asked about or auto-applied), pinned-version/pinned-path fields for pinning to a vendored skill copy, and context/AGENTS.md + context/CLAUDE.md guard files. Not a context/ entry-format change — existing entries are untouched — but it needs real action from an existing project, not a silent backfill.

Migrating an existing project (do this now, not on next touch — the whole point only holds once it's actually done): steps 1, 3 and 4 are a mechanical relocation of state the project already opted into — perform them directly in the session that finds the legacy block, no "shall I migrate?" question first; step 2 is the only question in this list.

  1. Generate the project's id (see "Project config" in setup.md) and create .keep-the-why, with the header line every .keep-the-why gets (see setup.md), carrying over every existing field from the old AGENTS.md block verbatim (context, init, context-schema, capture-confirmation, source-reference), plus the new id field. Verbatim includes context-schema: the relocation step itself copies the old value (say, 0.9.2) — it is not a schema migration. The normal behind-schema comparison (setup.md, "Context schema and migrations") then runs against the new file exactly as it would have against the old block, and advances the field the way it always does: right away when nothing between the two versions applies to this project, otherwise only after the applicable migration has actually been done or explicitly deferred by the user.
  2. Ask whether the project wants to add a personal-defaults block for future developers — same question the project init wizard now asks, framed the same way.
  3. Remove the <!-- keep-the-why:config --> block from the entry-point file, along with any prose that specifically pointed at it or at AGENTS.local.md for this skill's own state — don't replace it with a general "this project uses Keep the Why" mention; that's the project's own editorial call (a README section, the badge), not this skill's to add. Do leave one line noting the project was migrated and that any Keep the Why skill installation reading this file needs to be at metadata.version 0.10.0 or later. This is the one exception to "don't write pointers into the entry-point file": an older installed skill won't know to look for .keep-the-why at all, but it's still an LLM reading the whole file, not a program doing a literal marker match — a plain-English note left where it's already looking is something it can actually notice and act on, unlike a change to detection logic it was never taught.
  4. If context/ doesn't already have AGENTS.md and CLAUDE.md guard files (see "Guarding context/ itself" in setup.md), add them now, in the same pass.
  5. Separately, per developer, the next time each one activates the skill in this checkout (this doesn't happen all at once for everyone the moment the project-level part above lands, and that's fine — it's driven by local file presence, not by anything shared or git-tracked): if this checkout still has a <!-- keep-the-why:local --> block in AGENTS.local.md, carry its values and last: timestamps over verbatim into ~/.keep-the-why/<id>.md, then remove the block from AGENTS.local.md — a pure relocation of that developer's own already-stated preferences, no questions needed. A developer who never had a personal block before this migration isn't affected by this step at all; they go through ordinary first-activation handling (personal wizard, or the project's personal-defaults if it offers one) exactly as if the project had always used .keep-the-why.

Known limitation: an installed skill older than 0.10.0 has no way to know .keep-the-why exists — it looks for the config block in the entry-point file, doesn't find it (step 3 removed it), and could mistake an already-migrated project for one that was never set up. Not fixable retroactively (an old skill can't be taught a convention that didn't exist when it was released) — the one-line note step 3 leaves behind exists specifically to make this visible before it causes confusion, and updating the skill before opening an already-migrated project avoids it entirely.

Example — before (AGENTS.md):

<!-- keep-the-why:config -->
- context: `context/`
- init: complete
- context-schema: 0.9.2
- capture-confirmation: confirm-when-unsure
- source-reference: never
<!-- /keep-the-why:config -->

Example — after (.keep-the-why, new file; AGENTS.md keeps only the version note):

This is machine-readable project state for the Keep the Why skill
(https://keepthewhy.com). See context/index.md, or this project's own
README, for what Keep the Why actually is.

<!-- keep-the-why:config -->
- id: acme---widget-service
- context: `context/`
- init: complete
- context-schema: 0.9.2
- capture-confirmation: confirm-when-unsure
- source-reference: never
<!-- /keep-the-why:config -->

context-schema is shown at 0.9.2 here on purpose: the relocation carries every field over verbatim (step 1) and doesn't stand in for the schema migrations between 0.9.2 and the installed version — those go through the normal "behind → check migrations.md → discuss now / defer / stop asking" flow (setup.md, "Context schema and migrations") in the same session, and context-schema advances when that check completes (immediately if nothing applies).

Keep the Why's config for this project migrated to .keep-the-why on
2026-08-31 — requires skill version 0.10.0 or later to read it.

0.10.0 — context/index.md entries sorted alphabetically

What changed: new entries in context/index.md are inserted in alphabetical order by filename instead of appended at the end — see context/entry-format.md and #194. Not a context/ entry-format change, but it does need action in an existing project: the merge-conflict reduction this convention exists for only works once the whole list is actually sorted.

Migrating an existing project:

  1. Resort context/index.md fully, once, alphabetically by filename. Unlike a per-entry field backfill, this is mechanical (no per-entry judgment) and cheap even for a large index — do it now rather than waiting for entries to be touched individually.
  2. Insert any new entries in sorted position from that point on.

Example — before:

- [release-and-distribution.md](release-and-distribution.md) — ...
- [config-format.md](config-format.md) — ...
- [entry-format.md](entry-format.md) — ...

Example — after:

- [config-format.md](config-format.md) — ...
- [entry-format.md](entry-format.md) — ...
- [release-and-distribution.md](release-and-distribution.md) — ...

0.9.0 — Type accepts multiple values

What changed: an entry that genuinely documents more than one kind of thing now gets one **Type:** line per applicable value, instead of being forced to pick a single one. Supersedes point 2 of the 0.7.0 migration below — that guidance said to pick whichever value a future search is more likely to be about when an entry straddles two; the current guidance is to add a line for each value that genuinely applies instead. undefined stays exclusive — it never combines with the other four, since it means none of them fit.

Changed guidance (see references/repository-structure.md and context/entry-format.md):

  • Type: one line per value that genuinely applies (decision | workaround | incident | constraint) — most entries still get exactly one; undefined — <reason> stays a single, exclusive line used only when none of the four fit.

Migrating an existing entry:

  1. Not a backfill pass. An entry that already picked one value under the old "pick whichever" guidance doesn't need a dedicated pass to recover the value it left out — same "next time touched" rule as the 0.7.0 and 0.8.0 migrations below.
  2. When you do touch such an entry, add a second **Type:** line if a second value genuinely applies now — don't add one just because the field technically allows it if the original single value still covers the entry fully.
  3. Project-wide, once: check context/README.md's "Reading the entries" Type line — it already describes Type generically ("what kind of thing it is: decision, workaround, incident, or constraint") without claiming single-valued, so no wording change is required there. Nothing to do for this step.

Example — before:

**Type:** workaround
**Status:** active
**Evidence:** confirmed

Example — after (only once a second value genuinely applies):

**Type:** workaround
**Type:** incident
**Status:** active
**Evidence:** confirmed

0.8.0 — undefined Type value added

What changed: entries where none of the four Type values (decision | workaround | incident | constraint) cleanly fit now record **Type:** undefined — <reason> instead of leaving the field blank. Supersedes point 3 of the 0.7.0 migration below — that guidance said to leave Type out when nothing fits; the current guidance is to mark it undefined with a reason instead, so misfit cases stay filterable rather than indistinguishable from entries that never considered Type at all.

New value (see references/repository-structure.md):

  • Type: undefined — used only after actively confirming none of the four values fit; always followed by — <short reason>.

Migrating an existing entry:

  1. Not a backfill pass. Entries that currently skip Type because nothing fit don't need a dedicated pass — same "next time touched" rule as the 0.7.0 migration below.
  2. When you do touch one and confirm none of the four values fit, add **Type:** undefined — <short reason> rather than leaving the field blank.
  3. Project-wide, once: if context/README.md's "Reading the entries" Type line doesn't mention undefined, update it to match references/setup.md's current template.

Example — before:

**Status:** active
**Evidence:** confirmed

Example — after:

**Type:** undefined — documents a naming convention, not a decision/workaround/incident/constraint
**Status:** active
**Evidence:** confirmed

0.7.0 — Type field added

What changed: entries can now carry a Type header field (decision | workaround | incident | constraint), placed before Status. It categorizes what kind of thing an entry is, independent of Status/Evidence, so a tool or agent can filter — "every incident," "every workaround" — without loading full topic files to find out.

New field (see references/repository-structure.md):

  • Type: decision | workaround | incident | constraint — optional, filled in when one value clearly fits.

Migrating an existing entry:

  1. Not a backfill pass. Add Type to an entry the next time it's touched anyway, same as any other maintenance edit — matches "Retrofitting an existing project" in repository-structure.md.
  2. If an entry genuinely straddles two values (a workaround adopted because of an incident), pick whichever a future search is more likely to be about. Don't split the entry or leave Type blank just because more than one value would fit.
  3. If nothing fits cleanly, leave it out rather than forcing a wrong-feeling value — Type is there to help filtering, not to gate whether an entry counts. (Superseded by the undefined value above — for a project migrating straight to the current version, apply that guidance instead of this step.)
  4. Project-wide, once: if context/README.md's "Reading the entries" section doesn't mention Type at all, add it — same wording as references/setup.md's current template.

Example — before:

**Status:** active
**Evidence:** confirmed

Example — after:

**Type:** workaround
**Status:** active
**Evidence:** confirmed

0.3.0 — Evidence split from Status

What changed: context/ entries previously classified evidence as one of confirmed, inferred, unknown, or superseded — treating "superseded" as if it were a fourth evidence level. It isn't: whether a decision is still current (Status) and how well it's evidenced (Evidence) are independent questions. A superseded decision can have been thoroughly confirmed when it was still active. Also added: an optional Source/Verification pair for confirmed entries whose claim is worth tracing or could be checked against other evidence.

New fields (see SKILL.md rules 2 and 5):

  • Status: active | superseded | open | needs-review
  • Evidence: confirmed | inferred | unknown (unchanged values, now its own field)
  • Source and Verification (corroborated | uncorroborated | contradicted) — optional, only add where there's a real answer, per the proportionality principle. A contradicted verification must explain what contradicts it.

Migrating an existing entry:

  1. If it currently has a single Confirmed / Inferred / Unknown marker with no mention of being superseded → that value becomes Evidence. Add Status: active.
  2. If it currently says Superseded (with or without a separate confirmed/inferred/unknown marker) → Status: superseded. If an evidence value was recorded alongside it, keep it as Evidence. If not, set Evidence: unknown and flag the entry for review — don't guess what the original evidence level was.
  3. Don't add Source/Verification retroactively just because the fields now exist — only add them where there's a genuine answer (rule 10's proportionality gate applies here too).
  4. Superseded annotations already in prose (e.g. > Superseded 2026-03: see below) don't need to be rewritten — that's still how supersession gets recorded; Status: superseded is the structured counterpart for anything that also carries an Evidence/Status header.

Example — before:

**Status:** active
**Confirmed** (2026-03-14, via maintainer interview)

Example — after:

**Status:** active
**Evidence:** confirmed
**Source:** maintainer interview, 2026-03-14

(Verification omitted here — nothing to corroborate or contradict this against; adding it would be filler, not signal.)