taste-lint 0.0.6 → 0.0.7
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 +8 -12
- package/dist/cli.js +74 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +71 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/data/rules/craft/craft-near-duplicate-scale.yaml +0 -29
package/README.md
CHANGED
|
@@ -19,20 +19,20 @@ Scan your project with [Jev by TypeSafe AI](https://docs.typesafe.ai/introductio
|
|
|
19
19
|
npx taste-lint@latest init
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires Node 24.11 or later. Run from your project directory
|
|
22
|
+
Requires Node 24.11 or later. Run it from your project directory. Init installs locally and adds a scan script.
|
|
23
23
|
|
|
24
24
|
## Quickstart
|
|
25
25
|
|
|
26
|
-
Create a [Vercel AI Gateway key](https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys).
|
|
26
|
+
Create a [Vercel AI Gateway key](https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys). Then:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
export AI_GATEWAY_API_KEY="your-vercel-ai-gateway-key"
|
|
30
30
|
npm run taste
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
No account or config
|
|
33
|
+
No taste-lint account or config. AI checks send selected text and rule context to Vercel AI Gateway, billed to your account. Repeat runs reuse cached answers.
|
|
34
34
|
|
|
35
|
-
Use your package manager in place of npm.
|
|
35
|
+
Use your package manager in place of npm. Pass `--agent` to init for agent instructions, or `--dry-run` to preview setup.
|
|
36
36
|
|
|
37
37
|
## What it checks
|
|
38
38
|
|
|
@@ -40,17 +40,13 @@ Use your package manager in place of npm. Add `--agent` to init for agent instru
|
|
|
40
40
|
- **Writing:** Markdown, MDX, and READMEs with `--profile writing`.
|
|
41
41
|
- **Agent instructions:** AGENTS.md and skills with `--profile instructions`.
|
|
42
42
|
|
|
43
|
-
Rules
|
|
43
|
+
Rules come from [Agent Skills](https://github.com/mblode/agent-skills) and [Taste Training](https://blode.co/taste-training). Local checks handle measurable rules. Jev judges meaning and returns probabilities. Uncalibrated AI rules stay advisory. Active findings can fail a run.
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Docs
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
| ---------------- | ---------------------------------------------------- |
|
|
49
|
-
| `--dry-run` | Preview scope and estimated cost without model calls |
|
|
50
|
-
| `--output json` | Save findings for scripts and agents |
|
|
51
|
-
| `--output sarif` | Export findings for code review tools |
|
|
47
|
+
[taste-lint.blode.md](https://taste-lint.blode.md)
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
`taste-lint scan --help` lists every option. `--dry-run` previews scope and cost. `--output json` and `--output sarif` are for scripts and code review.
|
|
54
50
|
|
|
55
51
|
## License
|
|
56
52
|
|
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import { mdxjs } from "micromark-extension-mdxjs";
|
|
|
16
16
|
import { styleText } from "node:util";
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
//#region package.json
|
|
19
|
-
var version = "0.0.
|
|
19
|
+
var version = "0.0.7";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/lib/stats.ts
|
|
22
22
|
const makePRNG = (seed) => {
|
|
@@ -444,6 +444,7 @@ const loadCorpus = (corpusDir, rules, options = {}) => {
|
|
|
444
444
|
if (r.context !== void 0) {
|
|
445
445
|
if (!isRecord$1(r.context)) throw new Error(`Invalid corpus line ${where}: context must be an object`);
|
|
446
446
|
const { docType, role } = r.context;
|
|
447
|
+
if (r.context.fontScale !== void 0 && (!isRecord$1(r.context.fontScale) || Object.values(r.context.fontScale).some((value) => typeof value !== "string"))) throw new Error(`Invalid corpus line ${where}: fontScale must map tokens to strings`);
|
|
447
448
|
if (docType !== void 0 && !DOC_TYPES.includes(docType) || role !== void 0 && !ROLES.includes(role)) throw new Error(`Invalid corpus line ${where}: unknown context docType or role`);
|
|
448
449
|
}
|
|
449
450
|
if (r.neighbours !== void 0) {
|
|
@@ -784,6 +785,51 @@ const resolveLeading = (out, token, cls) => {
|
|
|
784
785
|
out.unresolved.push(cls);
|
|
785
786
|
};
|
|
786
787
|
const splitClasses = (value) => value.split(/\s+/u).filter(Boolean);
|
|
788
|
+
/** Only explicit pixel font tokens establish a scale for this comparison.
|
|
789
|
+
* Relative lengths need a rendered root size; other families need their own
|
|
790
|
+
* theme evidence. Neither is inferred from Tailwind's default typography.
|
|
791
|
+
*/
|
|
792
|
+
const compareScale = (classes, theme = {}) => {
|
|
793
|
+
const matches = [];
|
|
794
|
+
const unresolved = [];
|
|
795
|
+
for (const className of classes) {
|
|
796
|
+
const { base, variants } = stripVariants(className);
|
|
797
|
+
const candidate = base.match(/^(text|p[xytrblse]?|m[xytrblse]?|gap(?:-[xy])?|space-[xy]|rounded(?:-[a-z]+)?|leading|w|h|size|inset|top|bottom|left|right)-\[(-?(?:\d+(?:\.\d+)?|\.\d+))px\]$/u);
|
|
798
|
+
if (!candidate || Math.abs(Number(candidate[2])) <= 2) continue;
|
|
799
|
+
if (candidate[1] !== "text") {
|
|
800
|
+
unresolved.push(`${className}: ${candidate[1]} scale family is unsupported`);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
const entries = Object.entries(theme);
|
|
804
|
+
if (!entries.length) {
|
|
805
|
+
unresolved.push(`${className}: no explicitly declared font scale`);
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
const value = Number(candidate[2]);
|
|
809
|
+
const resolved = entries.flatMap(([name, raw]) => {
|
|
810
|
+
if (!/^[\w-]+$/u.test(name) || !/^(?:\d+(?:\.\d+)?|\.\d+)px$/u.test(raw)) return [];
|
|
811
|
+
const stepPx = Number(raw.slice(0, -2));
|
|
812
|
+
return Number.isFinite(stepPx) && stepPx > 0 ? [{
|
|
813
|
+
difference: Math.abs(value - stepPx),
|
|
814
|
+
name,
|
|
815
|
+
stepPx
|
|
816
|
+
}] : [];
|
|
817
|
+
});
|
|
818
|
+
const nearest = resolved.toSorted((a, b) => a.difference - b.difference || a.name.localeCompare(b.name))[0];
|
|
819
|
+
const tolerance = Number.EPSILON * Math.max(1, Math.abs(value), nearest?.stepPx ?? 0);
|
|
820
|
+
if (nearest && nearest.difference <= 1 + tolerance) matches.push({
|
|
821
|
+
className,
|
|
822
|
+
difference: Math.min(nearest.difference, 1),
|
|
823
|
+
stepPx: nearest.stepPx,
|
|
824
|
+
token: [...variants, `text-${nearest.name}`].join(":")
|
|
825
|
+
});
|
|
826
|
+
else if (resolved.length < entries.length) unresolved.push(`${className}: declared font scale contains unresolved values`);
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
matches,
|
|
830
|
+
unresolved
|
|
831
|
+
};
|
|
832
|
+
};
|
|
787
833
|
//#endregion
|
|
788
834
|
//#region src/lib/errors.ts
|
|
789
835
|
var InputError = class extends Error {
|
|
@@ -2573,6 +2619,29 @@ const shadcn = (spec) => rule$1({
|
|
|
2573
2619
|
unit: ["class-list"]
|
|
2574
2620
|
});
|
|
2575
2621
|
const CLASS_RULES = [
|
|
2622
|
+
rule$1({
|
|
2623
|
+
categoryId: "look-constraints",
|
|
2624
|
+
check: (unit) => {
|
|
2625
|
+
const compared = compareScale(unit.classes ?? [], unit.context.fontScale);
|
|
2626
|
+
if (!compared.matches.length) {
|
|
2627
|
+
if (compared.unresolved.length) throw new UnresolvedError(compared.unresolved.join("; "));
|
|
2628
|
+
return none;
|
|
2629
|
+
}
|
|
2630
|
+
if (unit.context.dynamic) throw new UnresolvedError("Dynamic classes may override the near-scale candidate");
|
|
2631
|
+
return hit(compared.matches.map(({ className, token, stepPx, difference }) => `${className} is ${difference}px from ${token} (${stepPx}px)`).join("; "));
|
|
2632
|
+
},
|
|
2633
|
+
hint: "Review the matching font token before changing this value. If the difference is intentional or recurring, name the existing size in the theme instead of changing its appearance. This comparison does not establish design intent.",
|
|
2634
|
+
id: "craft-near-duplicate-scale",
|
|
2635
|
+
source: {
|
|
2636
|
+
line: 10,
|
|
2637
|
+
path: "skills/ui-design/rules/slop-near-duplicate-scale.md",
|
|
2638
|
+
repo: "mblode/agent-skills",
|
|
2639
|
+
ruleId: "slop-near-duplicate-scale"
|
|
2640
|
+
},
|
|
2641
|
+
status: "review-only",
|
|
2642
|
+
title: "Arbitrary font size within 1px of a declared theme step",
|
|
2643
|
+
unit: ["class-list"]
|
|
2644
|
+
}),
|
|
2576
2645
|
motion({
|
|
2577
2646
|
categoryId: "easing-and-duration",
|
|
2578
2647
|
check: (unit) => {
|
|
@@ -4475,6 +4544,7 @@ const extractTsx = (file, source, options) => {
|
|
|
4475
4544
|
docType,
|
|
4476
4545
|
dynamic: classInfo.dynamic || void 0,
|
|
4477
4546
|
element: name,
|
|
4547
|
+
fontScale: config?.tailwind.theme,
|
|
4478
4548
|
interpolated: classInfo.interpolated || void 0,
|
|
4479
4549
|
role
|
|
4480
4550
|
},
|
|
@@ -4673,6 +4743,7 @@ const agentText = `${marker}
|
|
|
4673
4743
|
Taste Lint uses Jev to judge copy and UI. Preview the taste script with --dry-run, then run it with a user-supplied AI_GATEWAY_API_KEY.
|
|
4674
4744
|
Fix act findings, review advisory findings in context, and recheck the edited files.
|
|
4675
4745
|
Never invent a key or treat unknown checks as passes.
|
|
4746
|
+
Docs: https://taste-lint.blode.md
|
|
4676
4747
|
<!-- /taste-lint -->
|
|
4677
4748
|
`;
|
|
4678
4749
|
function object$1(value) {
|
|
@@ -5486,7 +5557,7 @@ const renderSarif = (result, rules, version) => {
|
|
|
5486
5557
|
ruleIndex: index.get(f.ruleId)
|
|
5487
5558
|
})),
|
|
5488
5559
|
tool: { driver: {
|
|
5489
|
-
informationUri: "https://
|
|
5560
|
+
informationUri: "https://taste-lint.blode.md",
|
|
5490
5561
|
name: "taste-lint",
|
|
5491
5562
|
rules: ruleList.map((r) => ({
|
|
5492
5563
|
fullDescription: { text: r.question?.instructions ?? r.fix.hint },
|
|
@@ -6652,6 +6723,7 @@ Quickstart:
|
|
|
6652
6723
|
|
|
6653
6724
|
Preview: taste-lint scan . --dry-run
|
|
6654
6725
|
Get a key: https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys
|
|
6726
|
+
Docs: https://taste-lint.blode.md
|
|
6655
6727
|
`);
|
|
6656
6728
|
registerLintCommand(program);
|
|
6657
6729
|
registerInitCommand(program);
|