Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
0.3.0 - 2026-09-18¶
Added¶
- The repository is a Claude Code plugin marketplace:
/plugin marketplace add fancysnake/glimpse-architecture, then/plugin install glimpse@glimpse SKILL.mdopens by naming the site as the authoritative form, so a review with the skill loaded knows where the reasoning lives
Changed¶
edges/is empty on a CLI, never absent. The import contracts are taken as a set on the first commit, and a contract can only name a module that exists, so the package is created with one empty__init__.py. Six pages said a CLI has noedges/at allSKILL.mdmoved toskills/glimpse/SKILL.md, where the plugin loader finds it. The raw-file curl URL moved with it- The site lives at https://glimpse.fancysnake.dev/, with repository and author links in the footer
0.2.0 - 2026-08-07¶
Added¶
SKILL.mdis now generated. Rule text shared with the docs site lives once inrules/;SKILL.src.mdand the docs pages both pull it in with the same--8<--include. Rebuild withmise run skill; CI fails on a stale file- Drift red flags consolidated into a single registry in Patterns & Red Flags, grouped by layer. The per-layer pages link to it instead of restating it
misetasks:skill,skill-check,lint,docs, andcheck- A Checks workflow running the skill, lint, and strict-docs gates on every
branch and pull request — previously nothing ran outside
main - Explanation of what each
edgesfile is for, aimed at readers who do not know Django - Hexagonal-architecture translation table in Why GLIMPSE
pactsplacement algorithm — contracts slice by noun, port, or wiring (pacts/db.py,pacts/services.py);pacts/core.pynamed a red flagTransactionProtocoldefinition (atomic()+savepoint()) with savepoint semantics and store-exception translation; implementation documented asinitsbinding glue- Per-port composition: CLI projects bootstrap via
[project.scripts]→inits, which constructs gates directly —initsis the only layer that may importgates - Connection and lifetime guidance in
inits(@cached_propertyper request,@lru_cachefactory per process) - Validation rule of thumb: gates validate format, mills validate meaning; permission threshold rule
- Error-flow pattern: coarse shared errors, caught at the gate call-site
- Repository-method design rule: params vary within a use case, a different scope is a different method
- Testing details: repo-root
tests/unit+tests/integrationlayout, mock-based mill tests, full-request gate tests with strict templates - Django guide: apps as markers with custom labels,
INSTALLED_APPS, migrations placement,ROOT_URLCONF,admin.pynext to models, framework-owned surfaces (request.user,django_login), noModelForm - Importlinter contracts for both directions of
edgesisolation - What is fixed, and what is guidance. Two things are absolute — which layer code belongs to, and which layer may import which. Everything else is recorded practice, to default to and to change when a real need appears. Absence of a rule is not a violation, and the site is not trying to become a catalogue with an entry for every decision. The same paragraph is in the skill, so a generated review stops treating silence as drift
- Projects page listing real GLIMPSE codebases at three stages — tingle (CLI, the day-one shape grown up), vekna (the layers repeated inside self-contained packages), Ludamus (production Django) — with what each is worth opening for. The home page links it instead of naming one project
- A test telling
specsandpactsapart, and shape constants listed amongpactscontents. A constant more than one layer must enforce is a fact about the shape of the data and belongs beside the contract it constrains; a constant only the rule can observe is a business invariant and stays inspecs. The previous justification — who may import the layer — was circular - Where a contract two nouns share lives: with the noun that needed it first,
earning its own module named after the thing it is (
pacts/money.py) once the sharing makes the case. The ban is on the name (common.py), not on the extraction - Pydantic named as optional: a DTO is a typed data shape with no behaviour, so
a dataclass,
NamedTuple, or attrs class serves as well — and with write shapes alreadyTypedDict,pactscan be pure standard library - The DTO construction rule spelled for non-ORM stores: attribute rows need
from_attributes=True, mapping rows (sqlite3.Row, a dict cursor) validate fromdict(row)with no config. A row that does not match the DTO is mapped in the repository, never by a method on the DTO - Where configuration comes from: it enters at
inits, which passes each value to the leaf that needs it. A framework's settings singleton is the exception and the reasonedgesexists — readingdjango.conf.settingsimports the framework, neveredges. A leaf may probe its own environment when the probe is injectable; what the environment decides,initsdecides. User input read at runtime — a config file, a command flag — is not configuration: it arrives through a port, shaped inpacts, validated in a mill
Changed¶
gatesno longer importsmills. A gate's only project import ispacts: it calls services through their protocols, andinitssupplies the implementations.gates,mills, andlinksare now three siblings that never see each other, meeting only ininits- Importlinter example rewritten as one
forbiddencontract per layer, named after the layer and listing every layer it may not reach, in GLIMPSE letter order.initscarriesallow_indirect_importsso its legalinits → mills → specschain does not trip thespecsrule independencecontracts added for the axis belowgates,links, andedges— ports do not import each other, and eachedgesfile is reached by the runtime on its own (needs import-linter 2.0 for wildcards)- Slicing vocabulary replaced: noun and verb instead of subdomain and
bounded context, and page as the
gatesaxis. Nouns are named after the thing they are, with no prescribed plurality; a verb cut must name a real activity - Gates mirror the interface, mills mirror the domain — the two trees are not
expected to match. Below port and adapter,
gatestakes whatever grouping the interface already has: command or command group for a CLI, page group / page or page / subpage for the web. The old noun axis did not survive contact with real URLs — plenty of pages belong to no single noun initsslicing is no longer prescribed. The layer is thin by construction — under a thousand lines even in the largest project on GLIMPSE — so it splits however is convenient. A module per registry class plus one that binds them (repositories.py,services.py,middleware.py,cli.py) is a suggestion, not a rule- Layer-promotion rationale restated as "on day one you have
mills.py" - "Framework-free" mills redefined by side effects, not package names — pure framework helpers allowed; enforcement level is a per-project choice
- DDD demoted to a slicing heuristic: no aggregates or value objects; data moves as DTOs and write TypedDicts, invariants live in service code
- Web request typing: gate-local
RootRequest(HttpRequest)typing-only subclass replacesRootRequestProtocolin pacts; onlyServicesProtocolstays in pacts - Service protocols documented as optional — needed for web-context typing and recommended for service-to-service dependencies
Services()takes no arguments and builds its own dependencies- Same-port adapters documented as usually coexisting (mills choose per operation), with deployment-time substitution as the rarer case
edgesdefined by two-way isolation and documented as optional for CLI projects;edges/main.pyremoved from layouts- Unit-of-Work rule scoped to ambient-ORM projects
- The typed web request lives in
gates/web/{adapter}/request.py, notentities.py—entityis a defined GLIMPSE term for a persistence-level concept, which the request is not - Class-based views are no longer presented as the recommended Django shape; that is a framework choice, not a GLIMPSE one
Fixed¶
specsno longer sends a constant a second layer needs toedges. It is either a contract (pacts) or configuration, which enters atinits— the layers overview was the last page still saying otherwisegatesis imported byinitson a CLI, which the gates page recorded as "depended on by nothing"- Patterns are no longer described as review-only: where one shows up as an import, the linter catches it
- The day-one tree on the layers overview no longer carries an
edges/directory the CLI it describes does not have - The status banner is a site-wide announcement bar, defined once in
overrides/main.html. It was a per-page admonition on ten pages and missing from the seven layer pages and both guides, so the pages it mattered on were the ones without it
Removed¶
- Repetition, roughly 300 lines of it.
SKILL.mdstated the file-layout patterns three times and the adapter rules twice — once inline, once through the sharedrules/fragments; "boundary vs core" was spelled out on three docs pages. Each rule now has one home, and the other places point at it - The pacts/mills symmetry rule. The two layers share the noun/verb axis
but promote independently —
mills/may be a package whilepacts.pyis still one file. Nothing enforced the rule and nothing depended on it; in practicemillssplits first. Three red flags went with it inits does not import gatesimportlinter contract (now an optional web-only stricter policy)- Entity-level mills red flag — a leftover concept
- Aggregate-invariant fix from dependency direction — the fix is a shared lower-level mill function
- DDD's strategic vocabulary — subdomains and bounded contexts are gone from the slicing rules entirely, replaced by nouns and verbs
- The
review.mdentry in the markdownlint ignore list — a local scratch file that was never in the repository
0.1.0 - 2026-07-09¶
Added¶
- Ludamus linked as a real-world example
- Why GLIMPSE page — what it adds over plain hexagonal architecture
- Request lifecycle page — one request traced through every layer
- Design principles and the acronym explanation on the home page
inits does not import gatesimportlinter contract
Changed¶
- Status banner: "Experimental" dropped in favour of a version number
initsslicing clarified: splits by what it wires (repositories.py,services.py), never by subdomaininitsdocumented as framework-aware binding code, likelinksandgatesedgesdocumented as never imported — inner layers referenced by configuration strings, files invoked by the runtime- Deduplicated repeated passages across layer and slicing pages
Removed¶
Storageand the repository identity-map pattern — repositories query the store directlycontext.di.uow.*legacy warnings
0.0.1 - 2026-04-17¶
Added¶
- Seven-layer architecture reference (pacts, specs, mills, links, gates, inits, edges)
- Slicing vocabulary and rules documentation
- File layout conventions
- Patterns and drift red flags
- Django implementation guide
- Import Linter configuration guide
- Claude skill reference and installation instructions