@fringetools/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bin.js +1 -1
- package/dist/{chunk-AOUZJDQL.js → chunk-W23H5MXS.js} +19 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +2 -0
- package/skill/audit.md +19 -0
- package/skill/review.md +18 -0
package/README.md
CHANGED
|
@@ -17,8 +17,6 @@ That is the whole setup. `init` adds a `PostToolUse` hook to `.claude/settings.j
|
|
|
17
17
|
|
|
18
18
|
Prefer a permanent command? `npm install -D @fringetools/cli`, then `npx fringe check`. Existing messy repo? Run `npx @fringetools/cli check --update-baseline` once so only *new* problems gate (see [Baseline](#baseline)).
|
|
19
19
|
|
|
20
|
-
> **Until the first npm release:** build from source — `pnpm install && pnpm --filter @fringetools/cli build`, then use `node packages/cli/dist/bin.js` in place of `npx @fringetools/cli`.
|
|
21
|
-
|
|
22
20
|
## Commands
|
|
23
21
|
|
|
24
22
|
| Command | What it does |
|
|
@@ -29,7 +27,7 @@ Prefer a permanent command? `npm install -D @fringetools/cli`, then `npx fringe
|
|
|
29
27
|
| `fringe check --hook` | One-line `fringe/hook.v1` JSON for CI / pre-commit. Exits `1` on errors. Claude Code does **not** feed this back — use `--claude-hook` for agents. Wins over `--json`. |
|
|
30
28
|
| `fringe check --update-baseline` | Accept today's findings into `fringe.baseline.json` (commit it). Exit `0`. |
|
|
31
29
|
| `fringe check --record [--manifest <path>]` | Also append this run to `.fringe/ledger.jsonl`, optionally merging an agent flow manifest. |
|
|
32
|
-
| `fringe init [path]` | Add the Claude Code hook and install the
|
|
30
|
+
| `fringe init [path]` | Add the Claude Code hook and install the three skills. Safe to re-run; refuses to touch an invalid `settings.json`. |
|
|
33
31
|
| `fringe open [path] [--port <n>]` | Serve the ledger as a plain HTML page on `127.0.0.1:4173` (loopback only). Ctrl+C to stop. |
|
|
34
32
|
| `fringe --help` · `--version` | Usage / version. |
|
|
35
33
|
|
|
@@ -103,7 +101,9 @@ After each edit the hook runs; with errors it returns, for example:
|
|
|
103
101
|
|
|
104
102
|
This was verified end to end with real agent runs: the agent receives the findings and fixes them (see [docs/architecture.md](../../docs/architecture.md)). Why `--claude-hook` and not `--hook`: Claude Code only passes a hook's output to the agent as `decision`/`reason` JSON on exit 0 (or stderr on exit 2); the CI contract `--hook` exits 1 and is ignored.
|
|
105
103
|
|
|
106
|
-
**
|
|
104
|
+
**Skills** — `fringe init` installs three into `.claude/skills/` (sources in [skill/](skill/)): `fringe` (below), `fringe-review` ("review this page": runs the checker and reports ranked, plain-English findings with fixes; invoke as `/fringe-review`) and `fringe-audit` (whole-repo plan: top rules and files, a suggested fix order, offers `--update-baseline`; `/fringe-audit`). Reviews run `fringe check`, so Claude Code will ask you to approve that command once (or allow `Bash(fringe:*)` / `Bash(npx:*)`).
|
|
105
|
+
|
|
106
|
+
**The main skill** (`.claude/skills/fringe/SKILL.md`) teaches the agent the ladder: *Flow* (cut what users don't need, with evidence in `.fringe/manifest.json`) → *Edges* (fix every hook finding, never silence a rule to get green) → *Polish* (tokens, scales, no AI look), then record the run. It is plain Markdown, so any agent platform that reads `AGENTS.md`/skill files can use it as-is.
|
|
107
107
|
|
|
108
108
|
For CI or pre-commit use `fringe check --hook` (or just the exit code):
|
|
109
109
|
|
package/dist/bin.js
CHANGED
|
@@ -1166,7 +1166,7 @@ import { createServer } from "http";
|
|
|
1166
1166
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
1167
1167
|
function renderBoard(records, corrupt = 0) {
|
|
1168
1168
|
const rows = [...records].reverse().map((r) => {
|
|
1169
|
-
const flows = (r.manifest?.flows ?? []).map((f) => `<li>${esc(f.action)}: ${esc(f.what)} <small>(${esc(f.evidence)}) \u2014 judged</small></li>`).join("");
|
|
1169
|
+
const flows = (r.manifest?.flows ?? []).map((f) => `<li>${esc(f.action)}: ${esc(f.what)} <small>(${esc(f.evidence)}) \u2014 judged${f.unsupported ? ", <b>no evidence</b>" : ""}</small></li>`).join("");
|
|
1170
1170
|
return `<tr><td>${esc(r.ts)}</td><td>${r.score}</td><td>${r.counts.error}</td><td>${r.counts.review}</td><td>${r.counts.signal}</td><td>${r.files}</td><td>${r.suppressed ?? 0}</td><td>${flows ? `<ul>${flows}</ul>` : ""}</td></tr>`;
|
|
1171
1171
|
}).join("");
|
|
1172
1172
|
const body = records.length === 0 ? "<p>No runs yet. Run <code>fringe check --record</code> and reload.</p>" : `<table><tr><th>When</th><th>Health</th><th>Errors</th><th>Review</th><th>Signals</th><th>Files</th><th>Suppressed</th><th>Flow cuts</th></tr>${rows}</table>`;
|
|
@@ -1190,7 +1190,8 @@ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as rea
|
|
|
1190
1190
|
import { dirname, join as join7 } from "path";
|
|
1191
1191
|
import { fileURLToPath } from "url";
|
|
1192
1192
|
var HOOK = "npx -y @fringetools/cli check --claude-hook";
|
|
1193
|
-
var
|
|
1193
|
+
var SKILLS = { fringe: "SKILL.md", "fringe-review": "review.md", "fringe-audit": "audit.md" };
|
|
1194
|
+
var skillSrc = (file) => fileURLToPath(new URL(`../skill/${file}`, import.meta.url));
|
|
1194
1195
|
function initProject(root) {
|
|
1195
1196
|
const done = [];
|
|
1196
1197
|
const settingsPath = join7(root, ".claude", "settings.json");
|
|
@@ -1210,12 +1211,14 @@ function initProject(root) {
|
|
|
1210
1211
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1211
1212
|
done.push(`hook added to ${settingsPath}`);
|
|
1212
1213
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1214
|
+
for (const [name, file] of Object.entries(SKILLS)) {
|
|
1215
|
+
const skillPath = join7(root, ".claude", "skills", name, "SKILL.md");
|
|
1216
|
+
if (existsSync2(skillPath)) done.push(`skill ${name} already present`);
|
|
1217
|
+
else {
|
|
1218
|
+
mkdirSync2(dirname(skillPath), { recursive: true });
|
|
1219
|
+
copyFileSync(skillSrc(file), skillPath);
|
|
1220
|
+
done.push(`skill ${name} installed`);
|
|
1221
|
+
}
|
|
1219
1222
|
}
|
|
1220
1223
|
return done;
|
|
1221
1224
|
}
|
|
@@ -1235,6 +1238,13 @@ function readManifest(path2, warn) {
|
|
|
1235
1238
|
try {
|
|
1236
1239
|
const parsed = JSON.parse(raw);
|
|
1237
1240
|
if (!parsed || !Array.isArray(parsed.flows)) throw new Error("missing flows");
|
|
1241
|
+
for (const f of parsed.flows) {
|
|
1242
|
+
if (f.action === "cut" && weakEvidence(f.evidence)) {
|
|
1243
|
+
f.unsupported = true;
|
|
1244
|
+
warn(`fringe: warning: cut '${f.what}' has no real evidence \u2014 recorded as unsupported
|
|
1245
|
+
`);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1238
1248
|
return parsed;
|
|
1239
1249
|
} catch {
|
|
1240
1250
|
warn(`fringe: warning: manifest at ${path2} is not valid '{ flows: [...] }' JSON \u2014 continuing without
|
|
@@ -1242,6 +1252,7 @@ function readManifest(path2, warn) {
|
|
|
1242
1252
|
return void 0;
|
|
1243
1253
|
}
|
|
1244
1254
|
}
|
|
1255
|
+
var weakEvidence = (e) => typeof e !== "string" || e.trim().length < 12 || /^(n\/?a|none|tbd|obvious|unnecessary|clutter|unused)\.?$/i.test(e.trim());
|
|
1245
1256
|
var HELP = `fringe check [--json] [--hook] [--record] [--manifest <path>] [path]
|
|
1246
1257
|
fringe check --claude-hook [path] for Claude Code hooks (feeds errors back to the agent)
|
|
1247
1258
|
fringe check --update-baseline [path] accept today's findings; only new ones gate
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -5,6 +5,8 @@ description: Use when writing or changing frontend code (JSX/TSX, HTML, CSS, Vue
|
|
|
5
5
|
|
|
6
6
|
# Fringe
|
|
7
7
|
|
|
8
|
+
Command: use `fringe` if it is on the PATH, otherwise `npx -y @fringetools/cli` in its place (e.g. `npx -y @fringetools/cli check --json .`).
|
|
9
|
+
|
|
8
10
|
Ponytail trims your code. Fringe handles your edges. Work the ladder in order.
|
|
9
11
|
|
|
10
12
|
1. **Flow (judged).** Before building, ask what the user can skip. Cut steps, screens or options nobody needs. Write each decision with its evidence to `.fringe/manifest.json`: `{"flows":[{"action":"cut"|"kept","what":"...","evidence":"..."}]}`. No evidence, no cut.
|
package/skill/audit.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fringe-audit
|
|
3
|
+
description: Use when the user wants a whole-repo UX/accessibility audit, a plan for an existing frontend with many problems, or to adopt Fringe on a messy repo ("audit this codebase", "where do we start", "adopt fringe"). Produces a prioritized plan and sets a baseline.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fringe audit
|
|
7
|
+
|
|
8
|
+
Command: use `fringe` if it is on the PATH, otherwise `npx -y @fringetools/cli` in its place (e.g. `npx -y @fringetools/cli check --json .`).
|
|
9
|
+
|
|
10
|
+
1. Run `fringe check --json .` and note the totals. Do not print every finding.
|
|
11
|
+
2. Summarize as a plan:
|
|
12
|
+
- Totals by severity and the health score, plus files scanned and skipped.
|
|
13
|
+
- **Top rules** by count, and **top files** by error count (the 5 worst of each).
|
|
14
|
+
- For each top rule: what it means in one sentence, a typical fix, and whether it is likely mechanical (add `aria-label`, add `alt`) or needs design judgment.
|
|
15
|
+
- A suggested order: mechanical `error` fixes first, then `error`s needing judgment, then `review`, then `signal`.
|
|
16
|
+
3. Offer the baseline: "`fringe check --update-baseline` records today's findings so only new problems gate; commit `fringe.baseline.json`." Run it only if the user agrees.
|
|
17
|
+
4. Ask before changing code. If the user says go, fix one rule at a time, re-run `fringe check` after each, and re-baseline at the end so the file shrinks.
|
|
18
|
+
|
|
19
|
+
Spot-check a few reported findings by reading the code before presenting them as certain; Fringe is static and can be wrong. Say which you checked.
|
package/skill/review.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fringe-review
|
|
3
|
+
description: Use when the user asks to review, check or critique a UI, page or component for accessibility and UX edges ("review this page", "what's wrong with this form", "is this accessible"). Runs Fringe and reports ranked findings with fixes, in plain English.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fringe review
|
|
7
|
+
|
|
8
|
+
Command: use `fringe` if it is on the PATH, otherwise `npx -y @fringetools/cli` in its place (e.g. `npx -y @fringetools/cli check --json .`).
|
|
9
|
+
|
|
10
|
+
1. Run `fringe check --json .` from the project root. Fringe takes directories, not single files, so if the user named one file or page, run on the folder and keep only the findings whose `loc.file` matches it.
|
|
11
|
+
2. Report in plain English, no jargon left unexplained:
|
|
12
|
+
- **Must fix** — `error` findings. Group by rule. For each: where (`file:line`), what is wrong in one sentence, the exact fix, and who is hurt (for example "a screen reader says only 'button'"). Cite the `wcag` criterion when present.
|
|
13
|
+
- **Worth a look** — `review` findings. These are heuristics: say they need your judgment, and give your call on each.
|
|
14
|
+
- **Polish** — `signal` findings, only the top few. Never present them as bugs.
|
|
15
|
+
3. End with the health score as measured, the count by severity, and one line on what was not checked (Fringe is static: it cannot judge contrast on images, real focus order, or whether copy is good).
|
|
16
|
+
4. Change no code unless asked. If asked to fix, fix `error` items first and re-run to confirm.
|
|
17
|
+
|
|
18
|
+
Do not invent findings the tool did not report, and do not call the score an estimate: it is measured.
|