Skip to content
Open
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
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

Ever opened a new codebase and felt completely lost? **CodeFlow** turns any GitHub repository or local codebase into an interactive architecture map in seconds.

- **No installation required** runs entirely in your browser
- **No data collection** your code never leaves your machine
- **No accounts** just paste a URL or select local files and go
- **Works offline** analyze local files without internet
- **No installation required** - runs entirely in your browser
- **No data collection** - your code never leaves your machine
- **No accounts** - just paste a URL or select local files and go
- **Works offline** - analyze local files without internet

```
Paste URL / Select Files -> See Architecture -> Make Better Decisions
Expand All @@ -38,7 +38,7 @@ Paste URL / Select Files -> See Architecture -> Make Better Decisions
See how your files connect at a glance. Click any node to highlight its dependencies. Drag, zoom, and explore.

### Blast Radius Analysis
*"If I change this file, what breaks?"* CodeFlow answers this instantly. Select any file and see exactly how many files would be affected by changes.
*"If I change this file, what breaks?"* - CodeFlow answers this instantly. Select any file and see exactly how many files would be affected by changes.

### Code Ownership
Know who owns what. See the top contributors for any file based on git history. Perfect for code reviews and knowing who to ask.
Expand Down Expand Up @@ -72,7 +72,7 @@ Color files by commit frequency to see which parts of your codebase are most act
Paste a PR URL to see exactly which files it affects and calculate the blast radius of proposed changes.

### CodeFlow Card (GitHub Action)
Health grade, scale, fragility, and hidden costs as a self-updating SVG on your README recomputed every merge, with optional thermal-receipt PR comments. See [card/](./card/).
Health grade, scale, fragility, and hidden costs as a self-updating SVG on your README - recomputed every merge, with optional thermal-receipt PR comments. See [card/](./card/).

### Markdown & Wiki-Link Graph
Point CodeFlow at an Obsidian vault or any markdown directory to see notes as a connected graph. Both `[[wiki-links]]` and `[text](./relative.md)` links become edges; each note is a `note`-layer node (distinct color) with a `dependencies[]` array in the JSON export.
Expand All @@ -93,7 +93,7 @@ Analyze code directly from your computer without uploading to GitHub:
A GitHub Action that drops an auto-updating SVG card on your README, recomputed on every merge by the same analyzer as the web app. Five styles, accent presets, opt-in PR receipts, and a privacy mode for public repos. The card adapts to the viewer's light/dark theme automatically.

<p align="center">
<img src="./card/examples/compact.svg" alt="CodeFlow card compact style" width="100%" />
<img src="./card/examples/compact.svg" alt="CodeFlow card - compact style" width="100%" />
</p>

See [card/](./card/) for setup, or jump to the [style gallery](#card-style-gallery) below.
Expand Down Expand Up @@ -350,7 +350,7 @@ node --test tests/
> CodeFlow runs entirely in your browser. It calls the GitHub API directly from your browser and processes everything client-side.

**Q: Is my code safe?**
> Yes. Your code is fetched directly from GitHub to your browser. Nothing is sent to any server we control. Check the source it's one file!
> Yes. Your code is fetched directly from GitHub to your browser. Nothing is sent to any server we control. Check the source - it's one file!

**Q: Can I use it offline?**
> Yes. With the local file analysis feature, you can analyze code from your computer without any internet connection. Click the "Open Folder" button and select your files. All processing happens entirely in your browser.
Expand All @@ -367,40 +367,40 @@ node --test tests/

All examples below are real cards rendered by the [CodeFlow Card Action](./card/) against this very repo. Pick one and drop it on your README.

### `style: compact` default
### `style: compact` - default

<img src="./card/examples/compact.svg" alt="compact" width="100%" />

### `style: compact` with `show-grade: false, show-score: false`

For public READMEs where you'd rather show data than a letter grade. The card stays informational files, functions, LOC, languages, tests without the judgmental bits.
For public READMEs where you'd rather show data than a letter grade. The card stays informational - files, functions, LOC, languages, tests - without the judgmental bits.

<img src="./card/examples/compact-private.svg" alt="compact private" width="100%" />

### `accent` any preset or CSS color
### `accent` - any preset or CSS color

The accent recolors the sparklines, links, and pin. Presets: `purple` (default), `teal`, `cyan`, `green`, `pink`, `blue`, `amber`, `red`. Or pass any CSS color (e.g. `#ff6b6b`).

<img src="./card/examples/compact-teal.svg" alt="compact teal" width="100%" />
<img src="./card/examples/compact-pink.svg" alt="compact pink" width="100%" />

### `style: row` status-bar strip
### `style: row` - status-bar strip

<img src="./card/examples/row.svg" alt="row" width="100%" />

### `style: minimal` single text line
### `style: minimal` - single text line

<img src="./card/examples/minimal.svg" alt="minimal" width="100%" />

### `style: hero` splashy gradient
### `style: hero` - splashy gradient

<img src="./card/examples/hero.svg" alt="hero" width="100%" />

`hero` with `show-grade: false`:

<img src="./card/examples/hero-private.svg" alt="hero private" width="100%" />

### `style: detailed` information-rich
### `style: detailed` - information-rich

Everything: grade, scale, language breakdown, composition (connections, tests, folders, function stats, patterns), top folders, fragility, hidden costs.

Expand All @@ -420,7 +420,7 @@ If you find CodeFlow useful, please star the repo.

## License

MIT License use it however you want.
MIT License - use it however you want.

---

Expand Down
16 changes: 8 additions & 8 deletions card/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# CodeFlow Card

A GitHub Action that drops a slick auto-updating SVG card on your README health grade, scale, fragility, hidden costs recomputed every merge by [codeflow](https://github.com/braedonsaunders/codeflow).
A GitHub Action that drops a slick auto-updating SVG card on your README - health grade, scale, fragility, hidden costs - recomputed every merge by [codeflow](https://github.com/braedonsaunders/codeflow).

The card uses the **same analyzer** as the codeflow web app. There's no separate parser, no version drift the Action reads codeflow's `index.html` and runs its analyzer in a Node `vm`.
The card uses the **same analyzer** as the codeflow web app. There's no separate parser, no version drift - the Action reads codeflow's `index.html` and runs its analyzer in a Node `vm`.

## Quick start

Expand Down Expand Up @@ -58,10 +58,10 @@ The Action commits the rendered SVG to `.github/codeflow-card.svg` (overwriting

## What's on the card

- **Health** letter grade (A+ F) with delta arrow vs the last run, plus the underlying score.
- **Scale** files / functions / LOC / languages, each with a 30-run sparkline (after the second run).
- **Fragility** top 3 highest-blast-radius files. The numbers nobody usually shows.
- **Hidden costs** circular deps, dead code %, average coupling. Lower-is-better arrows.
- **Health** - letter grade (A+ -> F) with delta arrow vs the last run, plus the underlying score.
- **Scale** - files / functions / LOC / languages, each with a 30-run sparkline (after the second run).
- **Fragility** - top 3 highest-blast-radius files. The numbers nobody usually shows.
- **Hidden costs** - circular deps, dead code %, average coupling. Lower-is-better arrows.

## PR receipts (opt-in)

Expand All @@ -85,7 +85,7 @@ The comment is sticky (updates in place via `<!-- codeflow-card:receipt -->` mar

## Notes

- **First run**: with no history yet, sparklines and deltas don't render the panels degrade gracefully.
- **First run**: with no history yet, sparklines and deltas don't render - the panels degrade gracefully.
- **Permissions**: the workflow needs `contents: write` to commit the SVG, and `pull-requests: write` if `receipts: true`.
- **CI cost**: analysis runs in pure Node (no Docker, no external APIs); typical run is 1030 seconds depending on repo size.
- **CI cost**: analysis runs in pure Node (no Docker, no external APIs); typical run is 10-30 seconds depending on repo size.
- **Privacy**: nothing leaves the runner. Same guarantee as the codeflow web app.
2 changes: 1 addition & 1 deletion card/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inputs:
required: false
default: '.github/codeflow-card.json'
theme:
description: 'Card theme: dark | light | auto. `auto` (default) produces a single SVG that adapts to the viewer''s system theme via `prefers-color-scheme` one card looks native on light and dark READMEs.'
description: 'Card theme: dark | light | auto. `auto` (default) produces a single SVG that adapts to the viewer''s system theme via `prefers-color-scheme` - one card looks native on light and dark READMEs.'
required: false
default: 'auto'
accent:
Expand Down
6 changes: 3 additions & 3 deletions card/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// CodeFlow Card GitHub Action entry point.
// CodeFlow Card - GitHub Action entry point.
// Reads codeflow's analyzer out of index.html, runs it on the consuming repo,
// renders an SVG card, optionally posts a PR receipt comment, then commits the
// updated card + history file back to the repo.
Expand Down Expand Up @@ -132,7 +132,7 @@ async function run() {
log('receipt post failed: ' + (e.message || e));
}
} else if (inputs.receipts) {
log('receipts enabled, but not a merged PR skipping');
log('receipts enabled, but not a merged PR - skipping');
}

// Commit changes back to the repo (skip when running outside a checkout).
Expand All @@ -151,7 +151,7 @@ async function run() {
log('git commit failed: ' + (e.message || e));
}
} else {
log('local mode skipping git commit');
log('local mode - skipping git commit');
}
}

Expand Down
2 changes: 1 addition & 1 deletion card/lib/analyzer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Extract the codeflow analyzer block from index.html and run it in a Node vm
// context. Mirrors what tests/codeflow-golden.test.mjs does the analyzer is
// context. Mirrors what tests/codeflow-golden.test.mjs does - the analyzer is
// the single source of truth, lives in one file, never drifts.

'use strict';
Expand Down
4 changes: 2 additions & 2 deletions card/lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function readState(statePath) {
const parsed = JSON.parse(raw);
if (parsed && Array.isArray(parsed.runs)) return parsed;
} catch {
// missing or corrupt start fresh
// missing or corrupt - start fresh
}
return { version: 1, runs: [] };
}
Expand Down Expand Up @@ -87,7 +87,7 @@ function snapshotFromAnalysis(data, helpers, ctx) {
.slice(0, 5);
}

// Function size stats derive from `code` if `lines` isn't populated.
// Function size stats - derive from `code` if `lines` isn't populated.
const fnLines = Array.isArray(data && data.functions)
? data.functions
.map((fn) => fn.lines || (fn.code ? fn.code.split('\n').length : 0))
Expand Down
4 changes: 2 additions & 2 deletions card/render/receipt-md.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ function diff(curr, prev) {
}

function fmtSigned(n) {
if (n == null) return '';
if (n == null) return '-';
return (n > 0 ? '+' : '') + n;
}

function dirArrow(n, lowerIsBetter) {
if (n == null || n === 0) return '';
const better = lowerIsBetter ? n < 0 : n > 0;
const sym = n < 0 ? '▼' : '▲';
return better ? ' :small_blue_diamond: ' + sym : ' :warning: ' + sym;
return ' ' + sym;
}

function gradeArrow(curr, prev) {
Expand Down
4 changes: 2 additions & 2 deletions card/render/receipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function diff(curr, prev) {
}

function fmtSigned(n, opts) {
if (n == null) return '';
if (n == null) return '-';
const sign = n > 0 ? '+' : '';
const v = (opts && opts.suffix) ? sign + n + opts.suffix : sign + n;
return v;
Expand All @@ -36,7 +36,7 @@ function row(label, value, color, theme) {

function renderReceipt(opts) {
// Receipts are PR-comment images that don't go through svgWrap, so they
// can't host a <style> block collapse `auto` down to `dark`.
// can't host a <style> block - collapse `auto` down to `dark`.
const themeName = opts.theme === 'auto' ? 'dark' : (opts.theme || 'dark');
const theme = getTheme(themeName);
const snap = opts.snapshot;
Expand Down
14 changes: 7 additions & 7 deletions card/render/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function escapeXml(str) {
}

function fmtNum(n) {
if (n == null) return '';
if (n == null) return '-';
if (typeof n !== 'number') return String(n);
if (n >= 1_000_000) return Math.round(n / 100_000) / 10 + 'M';
if (n >= 1_000) return Math.round(n / 100) / 10 + 'K';
Expand Down Expand Up @@ -102,7 +102,7 @@ function pinFooter(theme, x, y, showPin) {
}

// ============================================================================
// Style: compact (default) grade left, 4 scale stats right, single row
// Style: compact (default) - grade left, 4 scale stats right, single row
// ============================================================================

function renderCompact(opts) {
Expand Down Expand Up @@ -181,7 +181,7 @@ function renderCompact(opts) {
}

// ============================================================================
// Style: row single horizontal status-bar
// Style: row - single horizontal status-bar
// ============================================================================

function renderRow(opts) {
Expand Down Expand Up @@ -233,7 +233,7 @@ function renderRow(opts) {
}

// ============================================================================
// Style: minimal single text line, monospace
// Style: minimal - single text line, monospace
// ============================================================================

function renderMinimal(opts) {
Expand Down Expand Up @@ -277,7 +277,7 @@ function renderMinimal(opts) {
}

// ============================================================================
// Style: hero bigger, splashier, gradient
// Style: hero - bigger, splashier, gradient
// ============================================================================

function renderHero(opts) {
Expand Down Expand Up @@ -362,7 +362,7 @@ function renderHero(opts) {
}

// ============================================================================
// Style: detailed original 4-panel renderer
// Style: detailed - original 4-panel renderer
// ============================================================================

function panelGradeDetailed(snap, prev, theme, x, y, width, opts) {
Expand Down Expand Up @@ -434,7 +434,7 @@ function panelComposition(snap, theme, x, y, width) {
{ label: 'TEST FILES', value: snap.testFiles || 0 },
{ label: 'FOLDERS', value: snap.folders || 0 },
{ label: 'AVG FN LINES', value: snap.avgFnLines || 0 },
{ label: 'LONGEST FN', value: snap.longestFn ? snap.longestFn + 'L' : '' },
{ label: 'LONGEST FN', value: snap.longestFn ? snap.longestFn + 'L' : '-' },
{ label: 'PATTERNS', value: snap.patterns || 0 },
];
const h = 88;
Expand Down
10 changes: 5 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CodeFlow Open Source Architecture Intelligence</title>
<title>CodeFlow - Open Source Architecture Intelligence</title>
<meta name="description" content="Visualize any GitHub repository's architecture in seconds. See dependencies, blast radius, code ownership, security issues, and design patterns. No installation required.">
<meta name="keywords" content="code visualization, architecture, github, dependency graph, blast radius, code analysis, open source">
<meta name="author" content="CodeFlow">
<meta name="robots" content="index, follow">
<meta property="og:title" content="CodeFlow Visualize Your Codebase Architecture">
<meta property="og:title" content="CodeFlow - Visualize Your Codebase Architecture">
<meta property="og:description" content="Turn any GitHub repo into an interactive architecture map in seconds. Zero setup, privacy-first, runs in your browser.">
<meta property="og:type" content="website">
<meta property="og:image" content="codeflow-social.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="CodeFlow Visualize Your Codebase Architecture">
<meta name="twitter:title" content="CodeFlow - Visualize Your Codebase Architecture">
<meta name="twitter:description" content="Turn any GitHub repo into an interactive architecture map in seconds. Zero setup, privacy-first.">
<meta name="twitter:image" content="codeflow-social.png">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300ff9d' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M13 2 4 14h6l-1 8 9-12h-6l1-8z'/></svg>">
Expand Down Expand Up @@ -5068,7 +5068,7 @@
tooltip.html(renderTooltipHtml(d.data.name,[
{label:'Lines',value:d.data.value},
{label:'Functions',value:d.data.fns||0},
{label:'Layer',value:d.data.layer||''},
{label:'Layer',value:d.data.layer||'-'},
{label:'Folder',value:d.data.folder||'root'}
]))
.style('display','block').style('left',(e.offsetX+15)+'px').style('top',(e.offsetY+15)+'px');
Expand Down Expand Up @@ -5200,7 +5200,7 @@
tooltip.html(renderTooltipHtml(d.data.name,[
{label:'Lines',value:d.data.lines||0},
{label:'Functions',value:d.data.fns||0},
{label:'Layer',value:d.data.layer||''}
{label:'Layer',value:d.data.layer||'-'}
]))
.style('display','block').style('left',(e.offsetX+15)+'px').style('top',(e.offsetY+15)+'px');
d3.select(this).select('circle').transition().duration(150).attr('r',12).attr('stroke','var(--acc)').attr('stroke-width',3);
Expand Down
8 changes: 4 additions & 4 deletions tests/fixtures/vault/note-with-external.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Note With External Links

External: [Google](https://google.com) ignore.
External: [Google](https://google.com) - ignore.

Mail: [email](mailto:a@b.com) ignore.
Mail: [email](mailto:a@b.com) - ignore.

Anchor: [section](#heading) ignore.
Anchor: [section](#heading) - ignore.

Image (not a link): ![pic](./img.png) ignore.
Image (not a link): ![pic](./img.png) - ignore.

Real: [click](./target-note.md).
2 changes: 1 addition & 1 deletion tests/md-extractors.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Source of truth for markdown link extraction. Mirror into index.html's
// `Parser.extractMarkdownLinks` and `Parser.resolveMarkdownLink`. If you edit
// one, edit the other the copy lives inside a single-file static app.
// one, edit the other - the copy lives inside a single-file static app.

export function extractMarkdownLinks(content) {
if (!content) return [];
Expand Down
2 changes: 1 addition & 1 deletion tests/md-extractors.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ test('extractMarkdownLinks ignores links inside fenced code blocks and inline co

test('extractMarkdownLinks ignores external http(s), mailto, anchor-only, and image links', () => {
const links = extractMarkdownLinks(read('note-with-external.md'));
// Only the real [click](./target-note.md) not Google, not mailto, not #heading, not ![pic]
// Only the real [click](./target-note.md) - not Google, not mailto, not #heading, not ![pic]
assert.equal(links.length, 1);
assert.equal(links[0].kind, 'mdlink');
assert.equal(links[0].target, './target-note.md');
Expand Down
Loading