Skip to content

Commit 3b98e80

Browse files
lesnik512claude
andauthored
chore: adopt planning-convention v1.0.0 (#15)
Apply the canonical two-axis planning convention from lesnik512/planning-convention (v1.0.0, fresh adoption). - Copy index.py + _templates/* verbatim into planning/ - Add planning/README.md (Quick path + Conventions prose) - Scaffold planning/{changes,decisions,releases}/, deferred.md, architecture/README.md (promotion rule) - Wire `index` + `check-planning` recipes into Justfile; run check-planning in lint-ci - Point CLAUDE.md at planning/README.md and add the architecture promotion note - Record applied version in planning/.convention-version Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e934337 commit 3b98e80

15 files changed

Lines changed: 543 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ See `Justfile` for recipes (`just --list`). Notes that aren't obvious from the r
1010
- `just test` runs `uv run --no-sync pytest` with coverage always enabled; it
1111
forwards args, e.g. `just test tests/test_end_of_file_fixer.py::test_name`.
1212

13+
## Planning
14+
15+
This repo follows the two-axis planning convention — see
16+
[`planning/README.md`](planning/README.md). Start with its **Quick path** to
17+
pick a lane (Full / Lightweight / Tiny) before making a change.
18+
19+
When a change alters capability behavior, update the matching
20+
`architecture/<capability>.md` in the same PR.
21+
1322
## Architecture
1423

1524
Single-purpose CLI tool: ensures text files end with exactly one newline.
@@ -26,3 +35,11 @@ Files skipped: binary files, empty files, `.git`/`.cache`/`.uv-cache` directorie
2635
- **ruff** for formatting and linting (line length 120)
2736
- **ty** for type checking (use `ty: ignore` for suppressions, not `# type: ignore`)
2837
- **pytest** with `pytest-cov` for testing; fixtures in `tests/fixtures/`
38+
39+
## Conventions
40+
41+
- No `print()` in library/CLI source — it's a code smell here. Use
42+
`sys.stdout.write(...)` / `sys.stderr.write(...)` with an explicit `\n` in the
43+
format string. When fixing output bugs, amend the existing `write` call (add
44+
`\n`, change destination) rather than swapping in `print()`. `print()` is fine
45+
in tests, scratch scripts, and REPL examples.

Justfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ lint-ci:
1313
uv run ruff format --check
1414
uv run ruff check --no-fix
1515
uv run ty check
16+
just check-planning
17+
18+
index:
19+
uv run python planning/index.py
20+
21+
check-planning:
22+
uv run python planning/index.py --check
1623

1724
test *args:
1825
uv run --no-sync pytest {{ args }}

architecture/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Architecture
2+
3+
The living truth home: what `eof-fixer` does **now**. One file per capability,
4+
living prose, no frontmatter — dated by git.
5+
6+
**Promotion rule:** when a change alters a capability's behavior, hand-edit the
7+
matching `architecture/<capability>.md` in the **same PR** that ships the code.
8+
The edit rides in the implementing diff and is reviewed with it — never applied
9+
as a separate post-merge step. The change bundle in `planning/changes/` stays as
10+
the *why*; these files are the *what is true now*.

planning/.convention-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.0.0

planning/README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Planning
2+
3+
The portable two-axis planning convention: `architecture/` (repo root) holds the
4+
living truth about what `eof-fixer` does **now**; `planning/changes/` records how
5+
it got there. The prose below is sourced from the canonical convention
6+
([`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention));
7+
the applied version is in [`.convention-version`](.convention-version). To update
8+
it, re-run that repo's `APPLY.md` flow.
9+
10+
## Quick path (start here)
11+
12+
> The fast lane for making a change. The full reference is in
13+
> [Conventions](#conventions) below — read it only when this isn't enough.
14+
15+
**1. Choose a lane — first matching rule wins:**
16+
17+
1. Any of: needs design judgment · new file/module · public-API change ·
18+
cross-cutting or multi-file · non-trivial test design → **Full**
19+
(`design.md` + `plan.md`)
20+
2. Purely mechanical: typo · dep bump · linter/formatter/CI tweak ·
21+
mechanical rename · single-line config → **Tiny** (no bundle, conventional
22+
commit)
23+
3. Small-but-real, none of the above: ≲30 LOC net · ≤2 files · no new file ·
24+
no public-API change · one straightforward test → **Lightweight**
25+
(`change.md`)
26+
27+
Ambiguous between two? Take the heavier. A `change.md` that outgrows its lane
28+
splits into `design.md` + `plan.md`.
29+
30+
**2. Create the bundle** (Full / Lightweight only):
31+
`planning/changes/YYYY-MM-DD.NN-<slug>/`, where `.NN` is a zero-padded
32+
intra-day counter. Copy the matching template from
33+
[`_templates/`](_templates/).
34+
35+
**3. Ship in the implementing PR:** hand-edit the affected
36+
`architecture/<capability>.md`, finalize the bundle's `summary:` to the
37+
realized result, and run `just check-planning` before pushing.
38+
39+
## Conventions
40+
41+
> This is the portable convention, sourced from the canonical repo
42+
> [`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention)
43+
> (applied version in [`.convention-version`](.convention-version)). To update
44+
> it, run that repo's `APPLY.md` flow. The generated change index (`just index`)
45+
> and the `## Other` pointers below are repo-local.
46+
47+
### Two axes, never mixed
48+
49+
- **`architecture/` (repo root) — the present.** One file per capability,
50+
living prose, updated in the same PR that ships the change. The truth home.
51+
- **`planning/changes/` — the past-and-pending.** One folder per change,
52+
kept in place after ship.
53+
54+
A change **promotes** its conclusions into the affected
55+
`architecture/<capability>.md` by hand **in the implementing PR, alongside the
56+
code** — the edit rides in the same diff and is reviewed with it, never applied
57+
as a separate post-merge step. That hand-edit is what keeps `architecture/`
58+
true; the bundle stays in `changes/` as the *why*.
59+
60+
### Change bundles
61+
62+
A change is a folder `changes/YYYY-MM-DD.NN-<slug>/`:
63+
64+
- `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter
65+
(`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably.
66+
- `<slug>` — kebab-case description, not a story ID.
67+
68+
`summary` is written when the change is created (the intent one-liner) and
69+
**finalized at ship** to state the realized result — set in the implementing
70+
PR, alongside the code and the `architecture/` promotion. No post-merge
71+
bookkeeping, no folder move. `date` and `slug` are never written — they are
72+
read from the bundle's directory name.
73+
74+
### Three lanes
75+
76+
| Lane | Artifacts | Use when |
77+
|------|-----------|----------|
78+
| **Full** | `design.md` + `plan.md` | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design |
79+
| **Lightweight** | `change.md` | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test |
80+
| **Tiny** | none — conventional commit | typo, dep bump, linter/formatter/CI tweak, mechanical rename, single-line config |
81+
82+
Heavier lane wins on ambiguity. A `change.md` that outgrows its lane splits
83+
into `design.md` + `plan.md`.
84+
85+
### Artifacts at a glance
86+
87+
- **`design.md`** — the spec: the *thinking* (why, design, trade-offs, scope).
88+
- **`plan.md`** — the plan: the *sequencing* (the executor's task checklist).
89+
- **`change.md`** — both, condensed, for the lightweight lane.
90+
- **`releases/<semver>.md`** — per-release user-facing notes.
91+
- **`audits/<date>-<slug>.md`** — findings from a code/docs/bug-hunt sweep;
92+
spawns fix changes.
93+
- **`retros/<date>-<slug>.md`** — what we learned after a body of work.
94+
- **`deferred.md`** — real-but-unscheduled items, each with a revisit trigger.
95+
- **`decisions/<YYYY-MM-DD>-<slug>.md`** — one file per design decision taken
96+
(especially options *rejected*), each with a revisit trigger; listed by
97+
`just index`.
98+
99+
Templates live in [`_templates/`](_templates/).
100+
101+
### Frontmatter
102+
103+
`date` and `slug` are **derived from the directory / file name** — never
104+
repeated in frontmatter. So:
105+
106+
- `design.md` / `change.md`: `summary` (single line) only.
107+
- `plan.md`: **no frontmatter** — its identity is the bundle directory.
108+
- `decisions/*.md`: `status` (accepted|superseded), `summary`, and optional
109+
`supersedes` / `superseded_by`.
110+
- Files in `architecture/` carry **no** frontmatter — living prose, dated by git.
111+
112+
**`summary`** is one line: written at creation as the intent, then **finalized
113+
at ship** to state the realized result — what shipped and its effect. It is the
114+
only field the index renders.
115+
116+
## Other
117+
118+
- `just index` — print the generated change/decision index (a query over the
119+
files, never committed).
120+
- `just check-planning` — validate bundle/decision names, bundle shape, and
121+
required frontmatter. Runs in `just lint-ci`.
122+
- [`deferred.md`](deferred.md) — real-but-unscheduled items.
123+
- [`architecture/`](../architecture/) — the living truth home.

planning/_templates/change.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result.
3+
---
4+
5+
# Change: One-line capitalized title
6+
7+
**Lane:** lightweight — ≲30 LOC net, ≤2 files, no new file, no public-API
8+
change, a single straightforward test. If it outgrows this, split into
9+
`design.md` + `plan.md`.
10+
11+
## Goal
12+
13+
One or two sentences: what changes and why.
14+
15+
## Approach
16+
17+
The shape of the change in brief — enough that a reviewer sees the design
18+
without a full spec. Link the truth home (`architecture/<capability>.md`) if a
19+
capability contract moves.
20+
21+
## Files
22+
23+
- `path/to/file.py` — what changes
24+
- `tests/test_x.py` — test added / updated
25+
26+
## Verification
27+
28+
- [ ] Failing test first — command + expected error.
29+
- [ ] Apply the change.
30+
- [ ] Test passes — command.
31+
- [ ] `just test` — full suite green.
32+
- [ ] `just lint` — clean.

planning/_templates/decision.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
status: accepted # accepted | superseded
3+
summary: One line — shown in `just index`.
4+
supersedes: null
5+
superseded_by: null
6+
---
7+
8+
# One-line capitalized title
9+
10+
**Decision:** What was decided, in a sentence.
11+
12+
## Context
13+
14+
Why this came up; the options that were on the table.
15+
16+
## Decision & rationale
17+
18+
The call and why — including why the alternatives were rejected. Enough that a
19+
future explorer doesn't re-litigate it.
20+
21+
## Revisit trigger
22+
23+
The concrete signal that should reopen this decision.

planning/_templates/design.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result.
3+
---
4+
5+
# Design: One-line capitalized title
6+
7+
## Summary
8+
9+
One paragraph. What changes, at the level a reader needs to decide if this
10+
spec is worth reading in full.
11+
12+
## Motivation
13+
14+
Why now. What is broken or missing. Concrete observations / numbers, not
15+
abstract complaints. Link to memory entries or earlier specs when relevant.
16+
17+
## Non-goals
18+
19+
What is deliberately out of scope and (when nontrivial) why. Each item is
20+
a sentence; one line each.
21+
22+
## Design
23+
24+
### 1. <First piece>
25+
26+
What changes, in enough detail that a reader who has not seen the codebase
27+
can follow. Code samples / diagrams welcome.
28+
29+
### 2. <Second piece>
30+
31+
...
32+
33+
## Operations
34+
35+
Out-of-repo steps (DNS, infra, external account changes). Omit if none.
36+
37+
## Out of scope
38+
39+
Already covered above under Non-goals if appropriate. Repeat-list of
40+
explicitly-excluded follow-ups belongs here when the list is long.
41+
42+
## Testing
43+
44+
How we know it landed correctly. New pytest? Smoke check on live URL?
45+
Lint pass? Be specific.
46+
47+
## Risk
48+
49+
What could go wrong, ranked by likelihood × impact. Mitigations.

planning/_templates/plan.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# <slug> — implementation plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use
4+
> superpowers:subagent-driven-development (recommended) or
5+
> superpowers:executing-plans to implement this plan task-by-task. Steps
6+
> use checkbox (`- [ ]`) syntax for tracking.
7+
8+
**Goal:** One sentence — what shipping this plan achieves. No design
9+
rationale; link to the spec for that.
10+
11+
**Spec:** [`design.md`](./design.md)
12+
13+
**Branch:** `feat/my-change` (or `fix/`, `chore/`, etc.)
14+
15+
**Commit strategy:** Per-task commits / single commit / squash on merge.
16+
Whichever fits.
17+
18+
---
19+
20+
### Task 1: <imperative description>
21+
22+
**Files:**
23+
- Modify: `path/to/file.py`
24+
- Create: `path/to/new.py`
25+
26+
One sentence on what this task accomplishes. No deeper reasoning — that's
27+
in the spec.
28+
29+
- [ ] **Step 1: <action>**
30+
31+
Run / edit / verify command. Expected output.
32+
33+
- [ ] **Step 2: <action>**
34+
35+
...
36+
37+
- [ ] **Step 3: Commit**
38+
39+
```bash
40+
git add path/to/file.py
41+
git commit -m "<type>: <subject>"
42+
```
43+
44+
---
45+
46+
### Task 2: ...

planning/_templates/release.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# <project> <version> — <one-line headline>
2+
3+
<One or two sentences: what this release is and its headline change. Say up
4+
front if there are no behavior/API changes.>
5+
6+
<!-- Keep only the sections that apply; reorder/rename freely. A tiny release
7+
may be just the title + one section. Versioning is tag-driven — the
8+
release tag sets the version (`just publish` runs `uv version`), so there
9+
is no pyproject bump. -->
10+
11+
## Feature
12+
13+
- **<name>.** What it adds and how to use it.
14+
15+
## Fix
16+
17+
- **<name>.** What was broken, now fixed (reference the issue/regression).
18+
19+
## Internal refactors
20+
21+
- **<name>.** What changed under the hood, stated as no behavior change.
22+
23+
## Packaging
24+
25+
- Metadata / build / dependency changes visible to installers.
26+
27+
## Why
28+
29+
Context a reader needs for the headline change. Omit for small releases.
30+
31+
## Downstream
32+
33+
What dependents must do — e.g. bump their version floor — or "No action
34+
needed" when there is no API change. Omit if the project has no downstreams.
35+
36+
## Internals
37+
38+
- Coverage / tooling notes.

0 commit comments

Comments
 (0)