A change is one unit of significant technical work, identified by its GitHub tracking number. Its documents describe what will be built, why, how it will be validated, and how it affects the plugin: goals, non-goals, the proposed design, data/storage impact, security, accessibility, internationalization, backward compatibility, testing and rollout — agreed before implementation starts.
Change documents make a big change reviewable as a whole, instead of arriving as a large pull request that reviewers must reverse-engineer.
These documents were previously called Software Design Documents (SDD) and
numbered SDD-NNNN. That name is retired for two reasons: the global counter it
used was unsafe on parallel branches, and "SDD" is increasingly used across the
wider ecosystem to mean Spec-Driven Development, which is a different thing.
The main repository retired it in
exelearning/exelearning#2232;
this plugin follows.
Use "change document", "design document", or simply the concrete filename
(design.md, proposal.md) instead. Existing documents are preserved;
../migration-map.md maps each retired identifier to its
current path.
| Artifact | Answers | Lifetime |
|---|---|---|
| Change document | What will be built and how it will be implemented | May become historical once implemented |
| ADR | Which durable decision was made and why | Long-lived, append-only |
A change is a design; an ADR is a decision. A single
change often contains several durable decisions (a storage choice, a
compatibility guarantee, a security boundary). Those belong in ADRs so they
outlive the feature work — the change then links to them via related_adrs
instead of burying them in prose.
A change document is not a substitute for an ADR: when a design introduces a long-lived technical decision, capture that decision in an ADR (existing or newly proposed) and link it from the ADRs required or referenced table.
Every significant proposal may start with a change directory. Every durable architectural decision inside it should either link to an existing ADR or propose a new one — but do not create one ADR per section.
One directory per change:
docs/architecture/changes/<tracking-number>-<change-slug>/
docs/architecture/changes/68-stale-content-url-redirects/
docs/architecture/changes/86-unify-release-packaging/
-
A GitHub tracking number is required. It is the change's issue when it has one, and its pull request when it does not — GitHub draws both from a single repository-wide sequence, so they never collide.
In this repository the tracking number is always a pull request number. Issues are disabled on
exelearning/wp-exelearning. A cross-repository issue (for example in the main eXeLearning repository) comes from a different sequence and cannot identify a change here; link it underexternal_refs. -
<change-slug>is lowercase kebab-case. -
One tracking number may own more than one change directory, for the same reason it may own more than one ADR: a single pull request sometimes delivers two independent designs.
88-testable-editor-bundle-pathsand88-editor-bootstrap-view-returns-htmlboth shipped in #88 and are separate designs, not two halves of one. Do not merge unrelated designs into one directory to force a one-to-one mapping — the slug keeps them distinguishable, and the index sorts by number and then by slug. -
Every document in the directory carries
tracking_issue, and it must match the directory prefix. CI enforces this.
| File | Responsibility |
|---|---|
proposal.md |
Motivation, problem, scope, goals, non-goals |
spec.md |
Observable behavior, requirements, scenarios, acceptance criteria |
design.md |
Technical implementation design |
research.md |
Evidence, experiments, alternatives, source analysis |
tasks.md |
Implementation plan and progress |
Create only the files that carry real content. Empty placeholders are not
required and should not be added to complete the set. A small change may be a
single proposal.md; a large one may use all five.
Do not duplicate the same content across proposal.md, spec.md and
design.md. Each answers a different question.
Mutable change-level metadata (title, status, implementation_prs,
related_adrs) lives in exactly one file: the first of proposal.md,
spec.md, design.md, research.md, tasks.md that exists in the directory.
Other documents may repeat tracking_issue, title, status and date, but
must not declare implementation_prs — that would create a second source of
truth, and CI rejects it.
tracking_issue: 68
title: "Stale content URL redirects to the latest ELPX extraction"
status: accepted
date: 2026-07-10
implementation_prs: [68]
related_adrs: [ADR-68-01]
external_refs:
- https://github.com/exelearning/exelearning/issues/2150implementation_prs is traceability metadata: it lists every PR of this
repository that implements the change. The change's identity is the single,
stable value in tracking_issue, not the growing list in implementation_prs.
external_refs holds full URLs to issues and pull requests in other
repositories — the main eXeLearning repository, the Moodle plugin, and so on.
Their numbers come from other sequences and must never be written as bare
numbers.
The frontmatter records tools and links, not the names of people — use PR links for attribution.
template.mdis the canonical starting point.- There is no committed index. Run
make architecture-recordsto print one.
| Status | Meaning |
|---|---|
draft |
Being written; not yet ready for review. |
in-review |
Under review; open for feedback. |
accepted |
Design agreed; implementation may start. |
implemented |
The design has shipped. Kept as a historical record. |
superseded |
Replaced by a newer change (see superseded_by). |
abandoned |
Dropped before implementation. Kept for the record. |
Status lives in the frontmatter only. Do not add a ## Status section.
A change can be edited freely while it is draft or in-review. Once
implemented, avoid rewriting it except for typo/link fixes. If the design
changes substantially, create a new change directory under a new tracking number
and mark the previous one superseded. Implemented changes are preserved as
historical design records — do not delete them.
Write one for work that needs a design gate before implementation:
- significant new features;
- major refactors or rewrites of a subsystem;
- cross-cutting changes (ELPX upload/extraction, the embedded editor install flow, the content proxy, the style registry, Media Library integration);
- security-sensitive changes (upload validation, path-traversal protection, checksums, capabilities, nonces, the content-proxy security model);
- data or storage changes (attachment post meta, the
wp-content/uploads/exelearning/layout, uploaded style storage); - REST API changes, or changes to shortcode/block attribute contracts;
- build or distribution changes (packaging, the editor build/install flow);
- proposals with multiple implementation phases.
A change document is worth writing, even when not strictly required, when a change is small in code but wide in impact — for example a new hook contract other plugins will depend on, or a change that alters how existing ELPX packages are stored or served.
Skip it for:
- bug fixes and small enhancements;
- localized changes with an obvious implementation;
- translation-only or test-only changes;
- work already fully covered by an existing, current change document.
A durable decision that needs no full design can go straight to an ADR.
Every change document must address, or explicitly mark as not-applicable:
- Testing strategy — which PHPUnit suites and, where relevant, Playwright specs will cover the change; how coverage is verified.
- Backward compatibility — impact on existing ELPX packages, stored meta, shortcodes, blocks and hooks; whether a migration is needed.
- Migration/rollout — order of merges, feature gating, and rollback.
- Security, privacy, accessibility and internationalization — the cross-cutting concerns a WordPress plugin must not skip.
Tools such as OpenSpec and GitHub Spec Kit organize each change as a self-contained folder carrying its proposal, specs, design and tasks. This layout is deliberately modelled on that pattern, and the per-change directory names follow the same kebab-case convention.
This plugin does not adopt OpenSpec or any external spec tool as a dependency. They are referenced as prior art; adopting one formally would be its own architecture decision, and would need its own ADR.
As with ADRs, technical claims should cite a verifiable source: a repository path
plus commit, official WordPress documentation, a benchmark, a reproducible
experiment, or a linked PR or ADR. Put evidence in research.md when there is
enough of it to warrant its own document; otherwise keep it inline.
If an AI tool helped draft or research a document, disclose it in the frontmatter:
ai_assistance:
tool: "Claude Code" # tool / interface used
model: "claude-opus-5" # model, when relevantIf no AI tool was involved, set both fields to none.
- From a PR: mention the change directory, e.g.
changes/68-stale-content-url-redirects. - From an ADR: list the directory name under
related.changes. - From docs or code: link the concrete file,
[design](changes/68-stale-content-url-redirects/design.md).
Retired identifiers must not appear in new content. CI fails on them; use
../migration-map.md to find the current path.
| Command | What it does |
|---|---|
make architecture-check |
Validates identifiers, metadata and cross-references. Non-zero on failure. |
make architecture-records |
Prints the ADR and change indexes, derived from frontmatter. |
Both are thin wrappers around node bin/architecture-records.mts, also available
as composer architecture-check / composer architecture-records.
- Identify the change's GitHub tracking number — the pull request that carries it. Do not open an issue just to get a number; you cannot here anyway.
- Create
docs/architecture/changes/<number>-<change-slug>/. - Copy the relevant sections of
template.mdinto the documents you actually need. Start atstatus: draft. - Capture durable decisions as ADRs named
ADR-<number>-<NN>-<decision-slug>.md, and list them inrelated_adrsand in the ADRs required or referenced table. - Run
make architecture-checkto validate.make architecture-recordsprints the current index if you want to read it. - Open (or reference) the PR and move to
in-review. - On approval, set
acceptedand implement. When it ships, setimplementedand record the PRs inimplementation_prs.
- The change has a tracking number (its pull request), and the directory uses it.
- Every document's
tracking_issuematches the directory. - Only documents with real content exist; no empty placeholders.
- Content is not duplicated across
proposal.md,spec.mdanddesign.md. - Goals and non-goals are explicit.
- Data/storage, security, privacy, accessibility, i18n, compatibility and testing are addressed (or marked not-applicable).
- Durable decisions are captured as ADRs and listed in
related_adrs. - Every technical claim cites a verifiable source.
-
statusreflects reality, and appears only in the frontmatter. -
ai_assistanceis filled in (values ornone). - Cross-repository links are in
external_refsas full URLs. -
make architecture-checkpasses.