# Fractal File Structuring

> 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).

- Skill: `hashintel/fractal-file-structuring` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hashintel/fractal-file-structuring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hashintel/fractal-file-structuring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: hashintel (https://skillmd.com/u/hashintel)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/hashintel/fractal-file-structuring

---


# 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.

```text
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.

```text
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.

```text
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.

```typescript
// Avoid: reaches into another file's private subtree
import { SimulateView } from "../editor-view/panels/simulate-view";

// Prefer (1): import from a public mini-library (if it is conceptually part of editor-view)
import { EditorView } from "../editor-view";

// Prefer (2): move shared code to a shared folder (if it is NOT conceptually part of 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.

```text
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.

```text
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.

```text
foo.ts
foo.test.ts
```

If a private extracted file needs direct tests, place those tests next to that extracted file.

```text
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:

1. Identify the semantic concept the file represents.
2. Name the file or folder in kebab-case.
3. If extracting from an existing file, put private implementation files under a same-named folder.
4. If multiple current branches need the resource, put it in the nearest `shared/` folder.
5. Avoid `index` files and implicit folder imports.
6. Use relative imports within the workspace.
7. 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.

