STRUCTURE.
START HERE.
@domain-first/project-structure
Clear architecture costs files, contracts and wiring, so under a deadline code lands wherever it is quickest to write. The scaffolder makes the right place the shortest path.
Architecture loses
to the deadline.
In most TypeScript codebases you choose between convenience and architecture. Clear boundaries mean more files, more contracts and more wiring β and every one of them is written by hand.
The structure lives in people's heads. Where does a query go? What is a port? The answer is a wiki page or a colleague.
Every feature starts with copy-paste. Copy a neighboring folder, rename, fix imports, delete leftovers, add the factory, add the export.
Shortcuts accumulate. Under pressure the logic goes where it is quickest to write. Boundaries blur one import at a time.
Review is spent on placement. βWhy is this in services?β instead of βis the rule right?β And a coding agent improvises a new layout every session.
The clean path
is the shortest one.
The conventions become executable. The CLI asks which bounded context, layer and role a new piece of code has, and generates it in its place β with contracts, implementations, test skeletons and wiring that respect the boundaries. Doing it right takes a few prompts; doing it wrong takes extra effort.
The generated files belong to your project. You write the business rules.
Scaffold. Teach.
Check.
One package, three binaries.
npx df-psScaffold- First run
Asks your settings β writes
domain-first.project-structure.config.jsonβ exits. - Every next run
Opens the menu β one operation β files in their place.
npx df-skillsTeach- Run
Copies the agent skills β
.claude/skillsor.agents/skills. - After an upgrade
Run again β the new versions replace the old ones.
npx df-lintCheck- Run
Checks imports and wiring against the rules β
file:linereport. - In CI
Anything found β exit code 1 β the build fails.
Boundaries
stay checked.
The scaffolder puts new code in its place. After that the code is edited by people and agents, and an import across a boundary is one autocomplete away. df-lint catches it before review does: it fits CI and pre-commit hooks.
context-isolationA bounded context importing another one.
shared-layerThe Shared Layer importing a context beyond its errors.
layersImports against the layers, e.g. domain importing infrastructure.
dependenciesA module importing or wiring a kind its dependency rules don't allow.
reactPages, widgets, UI-kit and hooks importing what they shouldn't.
clocknew Date() or Date.now() in domain or application β use the clock port.
The rules
are a default.
Teams that work differently change them in the config. df-ps offers dependencies and df-lint checks them by the same rules. Kinds and checks you don't mention keep their defaults.
rules.dependenciesWhat each kind of module may depend on: a list replaces the defaults, add and remove change them.
rules.lintWhich checks run: set a check to false to turn it off.
{
"rules": {
"dependencies": {
"use-cases": { "add": ["use-cases"] },
"orchestration-queries": { "remove": ["repositories"] },
"application-services": ["application-ports"]
},
"lint": { "clock": false }
}
}Defaults for every kind: Dependency rules β
Add the scaffolder.
npm install --save-dev @domain-first/project-structurenpx df-psThe first run asks for your settings, writes the config and exits. Every next run opens the menu and performs one operation. In a monorepo, run it inside the package you want to scaffold.
Building blocks βBounded contexts, the shared layer and the five layers.
Package README βEvery generator and integration.