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
24 changes: 12 additions & 12 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`.

## Headless analysis

Expand Down Expand Up @@ -74,10 +74,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 @@ -89,10 +89,10 @@ PR #482 @yourhandle
--------------------------
LOC +312
functions +4
dead code −1
circular deps −1
blast radius 23 → 18 ▼
health B+ → A- ▲
dead code ظêْ1
circular deps ظêْ1
blast radius 23 ظ†ْ 18 ظû╝
health B+ ظ†ْ A- ظû▓
--------------------------
thank you for your merge
```
Expand All @@ -101,7 +101,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 10–30 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 @@ -30,7 +30,7 @@ inputs:
required: false
default: 'compact'
panels:
description: 'Only used when style=detailed. Comma-separated: grade, scale, languages, fragility, hidden-costs, patterns.'
description: 'Only used when style=detailed. Comma-separated: grade, scale, languages, composition, top-folders, fragility, hidden-costs. Empty string shows all of those panels.'
required: false
default: 'grade,scale'
show-grade:
Expand Down
10 changes: 5 additions & 5 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 @@ -83,12 +83,12 @@ async function run() {
const outAbs = path.resolve(repoRoot, inputs.output);
fs.mkdirSync(path.dirname(outAbs), { recursive: true });
fs.writeFileSync(outAbs, svg);
log('wrote card → ' + path.relative(repoRoot, outAbs));
log('wrote card ظ†ْ ' + path.relative(repoRoot, outAbs));

// Append snapshot to state.
const updatedState = appendRun(state, snapshot, inputs.sparklineWindow);
writeState(stateAbs, updatedState);
log('wrote state → ' + path.relative(repoRoot, stateAbs));
log('wrote state ظ†ْ ' + path.relative(repoRoot, stateAbs));

// Receipt comment (opt-in, only on merged PRs).
const isMergedPR =
Expand Down Expand Up @@ -117,7 +117,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 @@ -136,7 +136,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
30 changes: 30 additions & 0 deletions card/lib/grade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

// Shared grade ranking for SVG cards and PR receipts.
// Order: F < D- < D < D+ < C- < C < C+ < B- < B < B+ < A- < A < A+

const LETTER_RANK = { F: 0, D: 1, C: 2, B: 3, A: 4 };

function gradeRank(grade) {
if (!grade || typeof grade !== 'string') return -1;
const letter = grade[0].toUpperCase();
const base = LETTER_RANK[letter];
if (base == null) return -1;
let modifier = 0;
if (grade.length > 1) {
if (grade[1] === '+') modifier = 0.3;
else if (grade[1] === '-') modifier = -0.3;
}
return base + modifier;
}

function compareGrades(curr, prev) {
const ci = gradeRank(curr);
const pi = gradeRank(prev);
if (ci < 0 || pi < 0) return 0;
if (ci > pi) return 1;
if (ci < pi) return -1;
return 0;
}

module.exports = { gradeRank, compareGrades };
10 changes: 5 additions & 5 deletions card/render/receipt-md.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

'use strict';

const { compareGrades } = require('../lib/grade.js');

function diff(curr, prev) {
if (prev == null || curr == null) return null;
if (typeof curr !== 'number' || typeof prev !== 'number') return null;
Expand All @@ -25,11 +27,9 @@ function dirArrow(n, lowerIsBetter) {

function gradeArrow(curr, prev) {
if (!curr || !prev || curr === prev) return '';
const order = ['F', 'D', 'C', 'B', 'A'];
const c = order.indexOf(curr[0]);
const p = order.indexOf(prev[0]);
if (c > p) return ' ▲';
if (c < p) return ' ▼';
const cmp = compareGrades(curr, prev);
if (cmp > 0) return ' ▲';
if (cmp < 0) return ' ▼';
return '';
}

Expand Down
10 changes: 4 additions & 6 deletions card/render/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

const { getTheme } = require('./theme.js');
const { sparkline } = require('./sparkline.js');
const { compareGrades } = require('../lib/grade.js');

const ALL_STYLES = ['compact', 'row', 'minimal', 'hero', 'detailed'];

Expand Down Expand Up @@ -36,12 +37,9 @@ function gradeColor(theme, grade) {

function gradeArrow(curr, prev, theme) {
if (!prev || !curr || curr === prev) return '';
const order = ['F', 'D', 'C', 'B', 'A'];
const ci = order.indexOf(curr[0]);
const pi = order.indexOf(prev[0]);
if (ci < 0 || pi < 0) return '';
if (ci > pi) return '<tspan dx="6" font-size="14" fill="' + theme.green + '">▲</tspan>';
if (ci < pi) return '<tspan dx="6" font-size="14" fill="' + theme.red + '">▼</tspan>';
const cmp = compareGrades(curr, prev);
if (cmp > 0) return '<tspan dx="6" font-size="14" fill="' + theme.green + '">▲</tspan>';
if (cmp < 0) return '<tspan dx="6" font-size="14" fill="' + theme.red + '">▼</tspan>';
return '';
}

Expand Down
Loading