Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
- name: Generate version file
run: bun run generate:version

- name: Examples up-to-date
run: |
bun run generate:examples
git diff --exit-code -- "examples/*.md"

- name: Lint
run: bun run lint

Expand Down
144 changes: 87 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,63 @@ Or run instantly:
npx fln . -o codebase.md
```

Works with **Claude**, **ChatGPT**, **Gemini**, **Grok**, **Cursor**, **Copilot**, and any AI tool.
Works with **Claude**, **ChatGPT**, **Gemini**, **Grok**, **Cursor**, **Copilot**, and *any* AI tool.

**`fln`** (short for *flatten*) is **language-agnostic** by design: TypeScript, Python, Java, Go, Rust, Bash, SQL, mixed monorepos — it treats everything as plain text, detects project metadata from common manifests (`package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `CMakeLists.txt`, `vcpkg.json`), respects `.gitignore`, and skips binaries by default.

## Why fln exists

If you use LLMs for real projects, you’ve hit these limits:

- **Context windows** — large projects don’t fit
- **Upload friction** — dozens of files, every session
- **Partial understanding** — AI sees fragments, not architecture
- **Manual prep** — repeating the same setup again and again
- **Context windows** — large projects don’t fit.
- **Upload friction** — selecting dozens of files for every session.
- **Partial understanding** — AI sees fragments, not the architecture.
- **Manual prep** — repeating the same setup context again and again.

**fln removes that overhead.**
**`fln` removes that overhead.**
It turns your project into a single, structured snapshot that LLMs can actually reason about.

## What fln enables

**→ Full-context refactoring**
Ask architectural questions that are impossible file-by-file:
> “Where is the real coupling here?”
> “What should be split into modules?”
> “What should be split into modules?”

**→ Instant onboarding**
One markdown file instead of “start by opening these 12 folders”.
One markdown file instead of “start by opening these 12 folders”. Perfect for reading code on a tablet or onboarding new developers without an IDE.

**→ Project-level code reviews**
Let AI detect patterns, inconsistencies, and risks across the entire codebase.

**→ Reliable documentation snapshots**
Generate auditable, deterministic snapshots for docs, reviews, or CI.
**→ Auditable Snapshots**
Create a single, clean artifact of your codebase state for security reviews, compliance audits, or legal records without granting full repo access.

**→ Dataset Preparation**
Generate clean, formatted data for RAG pipelines and fine-tuning custom models.

**→ LLM-friendly diffs**
Flatten → commit → flatten again. See how the *whole project* changed.
Flatten → commit → flatten again. See how the *whole project* changed structurally.

## Compatible with your AI workflow

- **Claude** — ideal for large architectural prompts (200K+ tokens)
- **Gemini** — push massive codebases into 1M token windows
- **ChatGPT** — single-shot analysis without attachments
- **Cursor / Windsurf** — reference the full project in prompts
- **GitHub Copilot** — better context → better suggestions
- **Local LLMs** — datasets for RAG and fine-tuning
- **Claude** — ideal for large architectural prompts (200K+ tokens).
- **Gemini** — push massive codebases into 1M token windows.
- **ChatGPT** — single-shot analysis without attachments.
- **Cursor / Windsurf** — reference the full project in prompts.
- **GitHub Copilot** — better context → better suggestions.
- **Local LLMs** — datasets for RAG and fine-tuning.

## Built for real projects

- ⚡ **Fast parallel scanning** — thousands of files in seconds
- 🎯 **Smart filtering** — respects `.gitignore`, excludes binaries, configurable size limits
- 📁 **Intentional file order** — entry points and configs first, not alphabetical noise
- 🔄 **Auto-detection** — skips files previously generated by fln
- 📐 **Deterministic output** — same input → same snapshot
- 🧠 **Project metadata detection** — name & version from ecosystem-native manifests
- 🛠️ **Developer-friendly** — Markdown for humans, JSON for tooling, dry-run mode for safety
- 🔒 **No surprises** — runs locally, no data leaves your machine
- ⚡ **Fast parallel scanning** — thousands of files in seconds.
- 🎯 **Smart filtering** — respects `.gitignore`, excludes binaries, configurable size limits.
- 📁 **Intentional file order** — entry points and configs first, not alphabetical noise.
- 🔄 **Auto-detection** — skips files previously generated by `fln`.
- 📐 **Deterministic output** — same input → same snapshot.
- 🧠 **Project metadata detection** — name & version from ecosystem-native manifests.
- 🛠️ **Developer-friendly** — `Markdown` for humans, `JSON` for tooling, `--dry-run` mode for safety.
- 🔒 **No surprises** — runs locally, no data leaves your machine.

Zero dependencies on external services. Zero tracking. Just a tool that does its job.

Expand Down Expand Up @@ -171,6 +176,56 @@ fln . --dry-run

</details>

## CI/CD & Automation

Integrate `fln` into your pipeline to keep your codebase “AI-ready” automatically.

### GitHub Actions: Auto-generate Snapshots

Generate a fresh `codebase.md` artifact on every push. Download it anytime to chat with LLMs about the *exact* state of your main branch or a specific PR without manual scanning.

Create `.github/workflows/codebase-snapshot.yaml`:

```yaml
name: Snapshot Codebase

on:
push:
branches: [ "main" ]
pull_request:

jobs:
snapshot:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6

- name: Generate Snapshot
# Generates codebase.md without installing fln globally
run: npx fln . -o codebase.md --no-ansi

- name: Upload Artifact
uses: actions/upload-artifact@v6
with:
name: codebase-snapshot
path: codebase.md
retention-days: 7
```

### Git Hooks: Pre-commit Context Guard

Prevent accidental “context bloat” (e.g., committing large datasets or wrong lockfiles) by failing commits if the flattened codebase exceeds a specific size. This ensures your project always fits within LLM context windows.

Add to your pre-commit hook (e.g., via `husky` or `lint-staged`):

```bash
# Fails the commit if the flattened codebase exceeds 5MB (configurable)
# --dry-run ensures no files are written to disk
npx fln . --dry-run --max-total-size 5mb
```

## JavaScript API

```typescript
Expand Down Expand Up @@ -249,38 +304,13 @@ All CLI options are available via `FlnOptions`.

## Preview

````md
<!-- 🥞 fln 1.0.0 -->

# Codebase Snapshot: my-app

Generated: 2026-02-03 09:41
Files: 42 | Directories: 8
Full real outputs are provided below. Each example is a compact project in [`examples/`](examples/). `fln` outputs the directory tree and file contents with **entry points and configs first** (intentional file order):

---

## Directory Tree
```text
├── src/
│ ├── cli/
│ │ └── main.ts
│ └── core/
│ └── scanTree.ts
```

---

## Source Files

### src/core/scanTree.ts
```ts
export function scanTree(…) {
// …
}
```
````
- [TypeScript](examples/ts-app.md)
- [Python](examples/python-app.md)
- [Java](examples/java-app.md)
- [Go](examples/go-app.md)
- [Rust](examples/rust-app.md)

## Support this project

Expand All @@ -293,7 +323,7 @@ If it saves you time or improves your AI workflow:
## Contributing

PRs and issues are welcome.
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines.
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup and guidelines.

## License

Expand Down
8 changes: 7 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { bun as default } from "@nesvet/eslint-config";// eslint-disable-line import/no-default-export
import { bun } from "@nesvet/eslint-config";


export default [// eslint-disable-line import/no-default-export
{ ignores: [ "examples/**" ] },
...bun
];
9 changes: 9 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Examples

Subfolders here are minimal sample projects in various languages, used to demonstrate `fln` output. Each subfolder has a corresponding generated snapshot `examples/<name>.md` next to it.

The `*.md` files in this directory are produced by a script. After changing any example’s code, regenerate them from the repository root:

```bash
bun run generate:examples
```
166 changes: 166 additions & 0 deletions examples/go-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<!-- 🥞 fln 1.0.0 -->

# Codebase Snapshot: go-app

Generated: 2026-01-01 00:00
Files: 7 | Directories: 8

---

## Directory Tree
```text
├── README.md
├── go.mod
├── cmd
│ └── app
│ └── main.go
└── internal
├── config
│ └── config.go
├── formatter
│ └── formatter.go
├── processor
│ └── processor.go
└── reader
└── reader.go
```

---

## Source Files

### README.md
```md
# go-app

Go app example.
```

### go.mod
```mod
module example.com/go-app

go 1.22
```

### cmd/app/main.go
```go
package main

import (
"fmt"

"example.com/go-app/internal/config"
"example.com/go-app/internal/formatter"
"example.com/go-app/internal/processor"
"example.com/go-app/internal/reader"
)

func main() {
appConfig := config.LoadConfig()
lines := reader.ReadLines(appConfig.InputPath)
report := processor.BuildReport(appConfig.ProjectName, lines, appConfig.MinLineLength)
fmt.Println(formatter.FormatReport(report))
}
```

### internal/config/config.go
```go
package config

type Config struct {
ProjectName string
InputPath string
MinLineLength int
}

func LoadConfig() Config {
return Config{
ProjectName: "go-app",
InputPath: "sample.txt",
MinLineLength: 3,
}
}
```

### internal/formatter/formatter.go
```go
package formatter

import (
"strconv"
"strings"

"example.com/go-app/internal/processor"
)

func FormatReport(report processor.Report) string {
lines := []string{
"Project: " + report.ProjectName,
"Lines: " + formatCount(report.LineCount),
"Filtered:",
}
for _, line := range report.FilteredLines {
lines = append(lines, "- "+line)
}

return strings.Join(lines, "\n")
}

func formatCount(value int) string {
return strconv.Itoa(value)
}
```

### internal/processor/processor.go
```go
package processor

type Report struct {
ProjectName string
LineCount int
FilteredLines []string
}

func BuildReport(projectName string, lines []string, minLineLength int) Report {
filteredLines := make([]string, 0, len(lines))
for _, line := range lines {
if len(line) >= minLineLength {
filteredLines = append(filteredLines, line)
}
}

return Report{
ProjectName: projectName,
LineCount: len(lines),
FilteredLines: filteredLines,
}
}
```

### internal/reader/reader.go
```go
package reader

import (
"os"
"strings"
)

func ReadLines(path string) []string {
content, err := os.ReadFile(path)
if err != nil {
return []string{"alpha", "beta", "gamma", "delta"}
}

lines := strings.Split(string(content), "\n")
result := make([]string, 0, len(lines))
for _, line := range lines {
if line != "" {
result = append(result, line)
}
}

return result
}
```
Loading