@fringetools/cli 0.1.0 → 0.1.2

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 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 skill. Safe to re-run; refuses to touch an invalid `settings.json`. |
30
+ | `fringe init [path]` | Add the Claude Code hook and install the five 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
- **The skill** (`.claude/skills/fringe/SKILL.md`, source: [skill/SKILL.md](skill/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.
104
+ **Skills** — `fringe init` installs five into `.claude/skills/` (sources in [skill/](skill/), one folder each); the Claude Code plugin ships the same folders, namespaced `/fringe:…`: `fringe` (below), `fringe-review` ("review this page": ranked, plain-English findings with fixes), `fringe-audit` (whole-repo plan: top rules and files, a fix order, offers `--update-baseline`), `fringe-debt` (lists `fringe-ignore` suppressions and the baseline) and `fringe-help` (quick reference). Invoke as `/fringe-review` etc. 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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-AOUZJDQL.js";
4
+ } from "./chunk-E3SJZ4OQ.js";
5
5
 
6
6
  // src/bin.ts
7
7
  process.exitCode = await runCli(process.argv.slice(2));
@@ -4,7 +4,7 @@ import { readFileSync as readFileSync8 } from "fs";
4
4
  import { resolve } from "path";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = "0.1.0";
7
+ var VERSION = "0.1.2";
8
8
 
9
9
  // src/check.ts
10
10
  import { readFileSync as readFileSync5 } from "fs";
@@ -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>`;
@@ -1186,11 +1186,11 @@ function startBoard(root, port) {
1186
1186
  }
1187
1187
 
1188
1188
  // src/init.ts
1189
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2, copyFileSync } from "fs";
1189
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, writeFileSync as writeFileSync2, copyFileSync } from "fs";
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 SKILL = fileURLToPath(new URL("../skill/SKILL.md", import.meta.url));
1193
+ var SKILL_DIR = fileURLToPath(new URL("../skill/", import.meta.url));
1194
1194
  function initProject(root) {
1195
1195
  const done = [];
1196
1196
  const settingsPath = join7(root, ".claude", "settings.json");
@@ -1210,12 +1210,14 @@ function initProject(root) {
1210
1210
  writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1211
1211
  done.push(`hook added to ${settingsPath}`);
1212
1212
  }
1213
- const skillPath = join7(root, ".claude", "skills", "fringe", "SKILL.md");
1214
- if (existsSync2(skillPath)) done.push("skill already present");
1215
- else {
1216
- mkdirSync2(dirname(skillPath), { recursive: true });
1217
- copyFileSync(SKILL, skillPath);
1218
- done.push(`skill installed at ${skillPath}`);
1213
+ for (const name of readdirSync3(SKILL_DIR)) {
1214
+ const skillPath = join7(root, ".claude", "skills", name, "SKILL.md");
1215
+ if (existsSync2(skillPath)) done.push(`skill ${name} already present`);
1216
+ else {
1217
+ mkdirSync2(dirname(skillPath), { recursive: true });
1218
+ copyFileSync(join7(SKILL_DIR, name, "SKILL.md"), skillPath);
1219
+ done.push(`skill ${name} installed`);
1220
+ }
1219
1221
  }
1220
1222
  return done;
1221
1223
  }
@@ -1235,6 +1237,13 @@ function readManifest(path2, warn) {
1235
1237
  try {
1236
1238
  const parsed = JSON.parse(raw);
1237
1239
  if (!parsed || !Array.isArray(parsed.flows)) throw new Error("missing flows");
1240
+ for (const f of parsed.flows) {
1241
+ if (f.action === "cut" && weakEvidence(f.evidence)) {
1242
+ f.unsupported = true;
1243
+ warn(`fringe: warning: cut '${f.what}' has no real evidence \u2014 recorded as unsupported
1244
+ `);
1245
+ }
1246
+ }
1238
1247
  return parsed;
1239
1248
  } catch {
1240
1249
  warn(`fringe: warning: manifest at ${path2} is not valid '{ flows: [...] }' JSON \u2014 continuing without
@@ -1242,6 +1251,7 @@ function readManifest(path2, warn) {
1242
1251
  return void 0;
1243
1252
  }
1244
1253
  }
1254
+ var weakEvidence = (e) => typeof e !== "string" || e.trim().length < 12 || /^(n\/?a|none|tbd|obvious|unnecessary|clutter|unused)\.?$/i.test(e.trim());
1245
1255
  var HELP = `fringe check [--json] [--hook] [--record] [--manifest <path>] [path]
1246
1256
  fringe check --claude-hook [path] for Claude Code hooks (feeds errors back to the agent)
1247
1257
  fringe check --update-baseline [path] accept today's findings; only new ones gate
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runCli
3
- } from "./chunk-AOUZJDQL.js";
3
+ } from "./chunk-E3SJZ4OQ.js";
4
4
  export {
5
5
  runCli
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fringetools/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.
@@ -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.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: fringe-debt
3
+ description: Use when the user asks what Fringe findings have been silenced or deferred ("fringe debt", "what did we suppress", "what's in the baseline", /fringe-debt). One-shot report of suppressions and the baseline; changes nothing.
4
+ ---
5
+
6
+ # Fringe debt
7
+
8
+ Fringe has two ways to defer a finding: an inline `fringe-ignore` comment and the `fringe.baseline.json` file. This lists both so a deferral can't quietly become permanent.
9
+
10
+ Command: use `fringe` if it is on the PATH, otherwise `npx -y @fringetools/cli` in its place.
11
+
12
+ 1. **Inline suppressions.** `git grep --no-index --exclude-standard -nE 'fringe-ignore +FR-[0-9]+'`. One row per hit: `<file>:<line>, <rule> — <why, if the comment or nearby code says>`. Skip hits that are only prose mentioning the convention.
13
+ 2. **Baseline.** If `fringe.baseline.json` exists, count entries per rule (each entry is `rule|file|message`) and list the files with the most. If it is missing, say there is no baseline.
14
+ 3. **Today's picture.** Run `fringe check --json .` and report the current counts by severity next to the baseline total, so the user sees whether the debt is shrinking.
15
+ 4. Close with the single most valuable thing to pay down first (usually the rule with the most baselined entries that has a mechanical fix) and the command to shrink the baseline afterwards: `fringe check --update-baseline`.
16
+
17
+ Change nothing. Do not run `--update-baseline` unless the user asks.
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: fringe-help
3
+ description: Use when the user asks what Fringe can do or how to use it ("fringe help", "what commands does fringe have", /fringe-help). Quick reference; changes nothing.
4
+ ---
5
+
6
+ # Fringe help
7
+
8
+ Fringe checks agent-written frontends for accessibility, state, dark-pattern and "AI look" problems, gates the hard failures, and keeps a local history.
9
+
10
+ **Skills**
11
+
12
+ | Skill | Use it to |
13
+ |---|---|
14
+ | `fringe` | The working rules: cut what users don't need, fix every hook finding, polish, record |
15
+ | `/fringe-review` | Review a page or component: ranked findings with plain-English fixes |
16
+ | `/fringe-audit` | Audit the whole repo: top rules and files, a fix order, offer a baseline |
17
+ | `/fringe-debt` | List silenced findings (`fringe-ignore`, baseline) |
18
+ | `/fringe-help` | This reference |
19
+
20
+ **Commands** (`fringe`, or `npx -y @fringetools/cli`)
21
+
22
+ | Command | What it does |
23
+ |---|---|
24
+ | `check [dir]` | Scan and print findings and the health score; exit 1 on any error |
25
+ | `check --json` / `--hook` / `--claude-hook` | Machine output; CI gate; agent feedback form |
26
+ | `check --update-baseline` | Accept today's findings; only new ones gate |
27
+ | `check --record [--manifest f]` | Append the run to `.fringe/ledger.jsonl` |
28
+ | `init` | Add the hook and install the skills |
29
+ | `open` | View the ledger at 127.0.0.1:4173 |
30
+
31
+ **Escape hatch:** `// fringe-ignore FR-203` on or above the line, for a real false positive only; every run reports `N suppressed`.
32
+
33
+ Full reference: https://github.com/skc-learn/fringe/blob/main/packages/cli/README.md
@@ -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.