fractal-tree-file-structure

📁 kachkaev/reusable-stuff 📅 11 days ago
10
总安装量
9
周安装量
#31002
全站排名
安装命令
npx skills add https://github.com/kachkaev/reusable-stuff --skill fractal-tree-file-structure

Agent 安装分布

opencode 9
gemini-cli 9
antigravity 9
claude-code 9
github-copilot 9
codex 9

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/, or utils/ folders).

  • Encapsulation: Resources in a subdirectory are internal to the parent file unless explicitly re-exported. A shapes/ directory is “owned” by shapes.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 the src/ 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.ts files 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)