| name | fractal-file-structuring |
| description | Use when creating, moving, splitting, or organizing TypeScript files and folders. Applies fractal tree file-structuring rules which reduce the cognitive overhead of choosing where to put files and ultimately navigating a codebase (once the structure is established and understood). |
| license | MIT |
| metadata | {"triggers":{"type":"domain","enforcement":"suggest","priority":"high","keywords":["TypeScript","JavaScript","file structure","folder structure","create file","create folder","split file","shared folder"],"intent-patterns":["\\b(create|add|move|split|organize|refactor)\\b.*?\\b(file|folder|directory|module|component|hook|type|helper)\\b","\\b(file|folder|directory)\\b.*?\\b(structure|layout|organization|placement)\\b"]}} |
Fractal File Structuring
TypeScript and JavaScript files should be organised in a fractal tree structure. Use this skill when deciding where to create, move, split, or organize files and folders in a TypeScript or JavaScript workspace.
This guidance is based on HASH's file-structuring approach: https://hash.dev/blog/file-structuring
Scope
Apply this skill to TypeScript and JavaScript source files, including modules, components, hooks, helpers, types, tests, scripts, and entry points.
Core Rules
Use kebab-case names
Use kebab-case for all TypeScript and JavaScript file and folder names.
create-worker-factory.ts
playback-settings-menu.tsx
button.tsx
Avoid PascalCase, camelCase, and mixed-case file names, even for React components.
Do not create index files
Do not add index.ts, index.tsx, index.js, or index.jsx files for folder imports. Prefer explicit file entry points with meaningful names.
If a subtree needs a public entry point, name that file after the concept it exposes (e.g. schema.ts)
Treat each file as a mini-library
A file should expose one or more named exports with a shared semantic purpose. The file name should summarize that purpose (e.g. users.ts)
If a file contains only one main export, prefer naming the file after that export in kebab-case (e.g. create-user.ts)
Avoid default exports unless a framework or external API requires them.
Split outgrown files into private subtrees
When a file becomes too large or contains implementation details worth extracting, create a same-named folder next to it and move private pieces there.
editor-view.tsx # public mini-library: the component other files import
editor-view/
panels.tsx # private entry point imported by editor-view.tsx
panels/
simulate-view.tsx # private to panels.tsx
calculate-timeline-range.ts # private helper used only by editor-view.tsx
create-panel-state.ts # private helper used only by editor-view.tsx
Only editor-view.tsx should import from direct child mini-libraries such as editor-view/panels.tsx and editor-view/calculate-timeline-range.ts. Only editor-view/panels.tsx should import from editor-view/panels/*.tsx. Other files should import from editor-view.tsx, not from its private subtree. This keeps editor-view.tsx as the API boundary and makes editor-view/ read as its implementation.
If editor-view/calculate-timeline-range.ts grows and needs its own private implementation files, create editor-view/calculate-timeline-range/. Only editor-view/calculate-timeline-range.ts should import from that deeper subtree.
editor-view/
calculate-timeline-range.ts
calculate-timeline-range/
clamp-time.ts # private to calculate-timeline-range.ts
get-visible-duration.ts # private to calculate-timeline-range.ts
Keep private subtrees private
Do not import directly from another file's implementation folder.
import { SimulateView } from "../editor-view/panels/simulate-view";
import { EditorView } from "../editor-view";
import { Button } from "../shared/button";
If a resource must be available outside the subtree, re-export it from the subtree root only when it is part of that root's public concept. If it is independently useful to sibling branches, move it to an appropriate shared/ folder instead.
Put shared resources at the closest fork
When multiple sibling branches need the same helper, type, component, constant, or hook, place it in the nearest applicable shared/ folder.
editor-view.tsx
editor-view/
shared/
duration-label.tsx # used by both panels.tsx and bottom-section.tsx
playback-time.ts # shared formatting/parsing logic for this subtree
panels.tsx # imports from panels/
panels/
simulate-view.tsx # private to panels.tsx
bottom-section.tsx # imports from bottom-section/
bottom-section/
bottom-bar.tsx # private to bottom-section.tsx
Place shared files as deep as possible while still covering all current consumers. Do not move something to a high-level shared folder just because it might be reused later.
Here editor-view.tsx imports ./editor-view/panels and ./editor-view/bottom-section. panels.tsx may import ./panels/simulate-view and ./shared/duration-label; bottom-section.tsx may import ./bottom-section/bottom-bar and ./shared/duration-label. Nothing else should import from panels/ or bottom-section/ directly.
Shared files are mini-libraries too. A shared file can have its own private same-named subtree, and those internals should remain private to that shared file.
editor-view/
shared/
playback-time.ts # public to editor-view/* branches
playback-time/
parse-playback-time.ts # private to playback-time.ts
format-playback-time.ts # private to playback-time.ts
If later only bottom-bar.tsx uses duration-label.tsx, move it beside bottom-bar.tsx or under bottom-bar/. The folder structure should describe current consumers, not preserve old sharing.
Use relative imports within a workspace
For imports inside the same workspace, use relative paths. Do not introduce workspace-local aliases just to shorten paths.
Imports from other workspaces should use the package name.
Co-locate unit tests
Place unit tests next to the file they cover.
foo.ts
foo.test.ts
If a private extracted file needs direct tests, place those tests next to that extracted file.
editor-view.tsx
editor-view.test.tsx
editor-view/
calculate-timeline-range.ts
calculate-timeline-range.test.ts
Prefer testing through the public mini-library when that gives enough coverage. Add direct tests for private extracted files when the logic is complex enough that tests through the owner would be indirect or brittle.
Match the current shape
Organize files for the code's current relationships, not speculative future reuse. Moving files later is expected and cheaper than adding premature structure now.
Decision Checklist
Before creating a TypeScript or JavaScript file or folder:
- Identify the semantic concept the file represents.
- Name the file or folder in kebab-case.
- If extracting from an existing file, put private implementation files under a same-named folder.
- If multiple current branches need the resource, put it in the nearest
shared/ folder.
- Avoid
index files and implicit folder imports.
- Use relative imports within the workspace.
- Co-locate tests with the file under test.
When Unsure
Choose the location that communicates the file's current consumers and API boundary most clearly:
- Private implementation detail: place it under the owning file's same-named folder, and import it only from that owner.
- Named mini-library: create a normal named file when the concept has its own purpose and exports a small API for nearby consumers.
- Shared mini-library: place the named file in the closest
shared/ folder when multiple branches need that API.
- Subtree entry point: expose the public API from a named root file, and keep any deeper implementation files private to that root.
Do not add broad components, hooks, utils, types, or services folders unless absolutely necessary. If they exist, these folders MUST only be imported from by files called components.ts, hooks.ts, etc.