fractal-tree-file-structure
npx skills add https://github.com/kachkaev/reusable-stuff --skill fractal-tree-file-structure
Agent 安装分布
Skill 文档
Fractal tree file structure
This project follows a fractal tree approach to file organization, where the structure of any part mirrors the whole. This self-similar organization allows confident navigation without needing to understand the entire codebase.
Core principles
-
Recursive structure: Every directory follows the same organizational patterns, creating predictable navigation at any depth.
Developers should not need to learn the entire codebase structure to contribute meaningfully to any section. -
No circular dependencies: Imports must form a directed acyclic graph. Circular import chains turn the fractal tree into a generic graph, breaking the tree’s integrity and causing runtime issues.
-
Organic growth: Start with a single file; extract to subdirectories only when complexity demands it. No boilerplate structure upfront. Group resources by functional purpose, never by file “shape” (no project-wide
components/,hooks/, orutils/folders). -
Encapsulation: Resources in a subdirectory are internal to the parent file unless explicitly re-exported. A
shapes/directory is “owned” byshapes.ts. Direct imports from nested levels are prohibitedâeach sub-tree exports resources that can only be imported on the next level up. -
Contextual sharing: Common logic lives at the closest common ancestor (“fork” in the tree). The
shared/directory exists at thesrc/level because multiple entrypoints need it. Place shared logic as deep in the tree as possible while still serving all dependents. -
Present-state focus: Structure reflects current reality, not anticipated future needs. Refactor freely as usage patterns evolve. This eliminates over-engineering and enables formal linting enforcement.
Practical rules
Naming
All files and folders use kebab-case for cross-platform compatibility with case-sensitive filesystems.
Enforced via unicorn/filename-case.
No index files
Avoid index.ts files that enable implicit folder imports.
They cause path ambiguity where ./foo could resolve to both foo.ts and foo/index.ts, and they hurt ESM compatibility.
Files as mini-libraries
Each file acts as a self-contained “mini-library” with cohesive exports serving a common semantic purpose. If a file contains only one export, name the file after that export. Avoid default exports unless externally required.
Outgrown files become sub-trees
When a file grows unwieldy, extract logic into a sibling subdirectory bearing the original filename:
my-app.ts â my-app.ts (keeps public API)
â my-app/
âââ config.ts
âââ lifecycle.ts
âââ lifecycle/
â âââ something.ts
â âââ something-else.ts
âââ helpers.ts
Only my-app.ts imports from the my-app/ directory, and only lifecycle.ts imports from the lifecycle/ directory â each file owns its namespace.
If my-app.ts becomes unused, delete it together with its internal folder safely.
Relative paths within workspaces
All imports within a workspace use relative paths.
Avoid mixing path alias systems (e.g. @/foo) with relative imports, as this creates inconsistency.
(This project uses @/ aliases for the src/ root as a convention.)
shared/ folder convention
Shared resources between sub-trees go into path/to/common-parent/shared/.
Think of shared/ folders as lightweight node_modules/.
Contents of parent-level shared/ folders remain accessible, but sub-tree shared/ folders are internal to that sub-tree.
Multiple entry points
Projects may have several entry points (pages, API handlers, scripts, tests).
Keep their names distinct from mini-libraries using suffixes: do-something.script.ts, xyz.test.tsx.
Entry points access shared resources but remain outside core logic.
Colocate unit tests
Place unit tests beside the files they cover: foo.ts pairs with foo.test.ts.
Integration and end-to-end tests live in separate directories outside the source tree root.
Exceptions are permitted
Partial adoption works. Gradually migrate from leaves toward the root. Imperfect implementation still provides benefits by clarifying dependencies in sections of larger codebases.
Scoped directories with @ prefix
Directories prefixed with @ group related utilities under a namespace, similar to npm scoped packages:
src/shared/
âââ @foo/
| âââ a.ts
| âââ b.ts
âââ @bar/
| âââ m.ts
| âââ n.ts
âââ x.ts
âââ y.ts
This prevents naming collisions and clearly signals “this is a utility namespace, not a feature.”
Import rules
As a consequence of encapsulation, imports should only target “public” resources:
// â Correct: import from the mini-library entry point
import { something } from "../../../shared/foo.ts";
import { other } from "../../../shared/@scope/bar.ts";
// â Incorrect: import from internal files (owned by their parent)
import { internal } from "../../../shared/foo/helpers.ts";
import { deep } from "../../../shared/@scope/bar/internal.ts";
// â Incorrect: import from a scope directly (like npm, scopes aren't packages)
import { wrong } from "../../../shared/@scope";
Organic growth example
A real project evolves step by step. Starting with a single file:
example.ts
Extract when necessary:
example.ts
example/
âââ do-x.ts
âââ do-x.test.ts
Add shared logic between extracted modules:
example.ts
example/
âââ shared/
â âââ do-common-thing.ts
âââ do-x.ts
âââ do-y.ts
When a second entry point (example-2.ts) needs something previously nested, promote it to the closest common ancestor:
shared/
âââ bar.ts
example.ts
example/
âââ shared/
â âââ do-common-thing.ts
âââ do-x.ts
âââ do-y.ts
example-2.ts
Each step reflects actual code relationships without predicting future needs.
Anti-patterns
- Do not group files by type/shape rather than function (
components/,hooks/,utils/) - Do not use
index.tsfiles enabling implicit folder resolution and path synonyms - Avoid default exports unless required by third-party
- Do not over-engineer structures for hypothetical future needs
- Do not prematurely split files before maintenance issues emerge (they may not)
- Do not import from a sub-tree’s internal files (bypassing encapsulation)