taste-lint 0.0.4 → 0.0.5
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 -9
- package/dist/cli.js +27 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Catch AI slop before you ship.**
|
|
6
6
|
|
|
7
|
-
Scan your project with
|
|
7
|
+
Scan your project with [Jev by TypeSafe AI](https://docs.typesafe.ai/introduction).
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/taste-lint"><img alt="npm version" src="https://img.shields.io/npm/v/taste-lint?style=flat&colorA=000000&colorB=000000" /></a>
|
|
@@ -19,7 +19,7 @@ Scan your project with local checks and [Jev by TypeSafe AI](https://docs.typesa
|
|
|
19
19
|
npx taste-lint@latest init
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires Node 24.11 or later. Run from your project directory to install locally and add
|
|
22
|
+
Requires Node 24.11 or later. Run from your project directory to install locally and add a scan script.
|
|
23
23
|
|
|
24
24
|
## Quickstart
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ npm run taste
|
|
|
32
32
|
|
|
33
33
|
No account or config for taste-lint. AI checks send selected text and rule context to Vercel AI Gateway, billed to your account. Answers are cached for repeat runs.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Use your package manager in place of npm. Add `--agent` to init for agent instructions, or `--dry-run` to preview setup.
|
|
36
36
|
|
|
37
37
|
## What it checks
|
|
38
38
|
|
|
@@ -44,12 +44,11 @@ Rules draw on [Agent Skills](https://github.com/mblode/agent-skills) and [Taste
|
|
|
44
44
|
|
|
45
45
|
## Useful options
|
|
46
46
|
|
|
47
|
-
| Option
|
|
48
|
-
|
|
|
49
|
-
| `--dry-run`
|
|
50
|
-
| `--
|
|
51
|
-
| `--output
|
|
52
|
-
| `--output sarif` | Export findings for code review tools |
|
|
47
|
+
| Option | What it does |
|
|
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 |
|
|
53
52
|
|
|
54
53
|
Run `taste-lint scan --help` for all options. See the [scan guide](https://github.com/mblode/taste-lint/blob/main/docs/SCANS.md), [usage reference](https://github.com/mblode/taste-lint/blob/main/docs/USAGE.md), and [changelog](https://github.com/mblode/taste-lint/blob/main/CHANGELOG.md) for more.
|
|
55
54
|
|
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.5";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/lib/stats.ts
|
|
22
22
|
const makePRNG = (seed) => {
|
|
@@ -1211,7 +1211,7 @@ const GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v4/ai";
|
|
|
1211
1211
|
const DEFAULT_MODEL = "jev-latest";
|
|
1212
1212
|
/** The gateway's id for Jev; a `--model` with a slash in it is passed through. */
|
|
1213
1213
|
const GATEWAY_MODEL = "typesafe-ai/jev";
|
|
1214
|
-
const KEY_HINT = "Set AI_GATEWAY_API_KEY to your Vercel AI Gateway key, then rerun. Create a key at https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys.
|
|
1214
|
+
const KEY_HINT = "Set AI_GATEWAY_API_KEY to your Vercel AI Gateway key, then rerun. Create a key at https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys.";
|
|
1215
1215
|
const MAX_RETRY_AFTER_MS = 3e4;
|
|
1216
1216
|
var ProviderError = class extends Error {
|
|
1217
1217
|
category;
|
|
@@ -2003,13 +2003,8 @@ const planRequests = (units, rules, config) => {
|
|
|
2003
2003
|
//#endregion
|
|
2004
2004
|
//#region src/map/judge.ts
|
|
2005
2005
|
const prepareJudgement = (units, rules, options) => {
|
|
2006
|
-
const plan = planRequests(units,
|
|
2007
|
-
|
|
2008
|
-
const skipped = plan.skipped.get(unit.id) ?? {};
|
|
2009
|
-
for (const rule of rules) if (rule.tier !== "mechanical") skipped[rule.id] = "mechanical_only";
|
|
2010
|
-
plan.skipped.set(unit.id, skipped);
|
|
2011
|
-
}
|
|
2012
|
-
let jobs = options.mechanicalOnly ? [] : plan.jobs;
|
|
2006
|
+
const plan = planRequests(units, rules, options.config);
|
|
2007
|
+
let jobs = plan.jobs;
|
|
2013
2008
|
if (options.jobFilter) jobs = jobs.map((job) => options.jobFilter?.(job) ?? null).filter((job) => job !== null);
|
|
2014
2009
|
const prepared = prepareRequests(jobs, options.cache, options.model);
|
|
2015
2010
|
const pending = prepared.filter((p) => Object.keys(p.questions).length > 0);
|
|
@@ -4571,8 +4566,7 @@ const marker = "<!-- taste-lint -->";
|
|
|
4571
4566
|
const agentText = `${marker}
|
|
4572
4567
|
## Taste Lint
|
|
4573
4568
|
|
|
4574
|
-
|
|
4575
|
-
For AI checks, preview the taste script with --dry-run, then run it with a user-supplied AI_GATEWAY_API_KEY.
|
|
4569
|
+
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.
|
|
4576
4570
|
Fix act findings, review advisory findings in context, and recheck the edited files.
|
|
4577
4571
|
Never invent a key or treat unknown checks as passes.
|
|
4578
4572
|
<!-- /taste-lint -->
|
|
@@ -4615,22 +4609,33 @@ function initProject(options) {
|
|
|
4615
4609
|
].some((name) => name in dependencies) ? "product" : "writing";
|
|
4616
4610
|
const scripts = { ...manifest.scripts };
|
|
4617
4611
|
const added = [];
|
|
4618
|
-
for (const [
|
|
4619
|
-
"check:taste"
|
|
4620
|
-
|
|
4621
|
-
}
|
|
4612
|
+
for (const oldProfile of ["product", "writing"]) if (scripts["check:taste"] === `taste-lint scan . --profile ${oldProfile} --mechanical-only`) {
|
|
4613
|
+
scripts["check:taste"] = `taste-lint scan . --profile ${oldProfile}`;
|
|
4614
|
+
added.push("check:taste");
|
|
4615
|
+
}
|
|
4616
|
+
for (const [name, command] of Object.entries({ taste: `taste-lint scan . --profile ${profile}` })) if (!(name in scripts)) {
|
|
4622
4617
|
scripts[name] = command;
|
|
4623
4618
|
added.push(name);
|
|
4624
4619
|
}
|
|
4625
4620
|
const agentPath = path.join(root, "AGENTS.md");
|
|
4626
4621
|
const previousAgent = options.agent && fs.existsSync(agentPath) ? fs.readFileSync(agentPath, "utf-8") : "";
|
|
4622
|
+
const legacyAgentText = `${marker}
|
|
4623
|
+
## Taste Lint
|
|
4624
|
+
|
|
4625
|
+
Run the local check with the project's check:taste script after editing copy or UI.
|
|
4626
|
+
For AI checks, preview the taste script with --dry-run, then run it with a user-supplied AI_GATEWAY_API_KEY.
|
|
4627
|
+
Fix act findings, review advisory findings in context, and recheck the edited files.
|
|
4628
|
+
Never invent a key or treat unknown checks as passes.
|
|
4629
|
+
<!-- /taste-lint -->
|
|
4630
|
+
`;
|
|
4631
|
+
const upgradeAgent = Boolean(options.agent && previousAgent.includes(legacyAgentText));
|
|
4627
4632
|
const addAgent = Boolean(options.agent && !previousAgent.includes(marker));
|
|
4628
4633
|
const needsDependency = !("taste-lint" in dependencies);
|
|
4629
4634
|
const installArgs = pm === "npm" ? ["install", "--save-dev"] : ["add", "--dev"];
|
|
4630
4635
|
installArgs.push(`taste-lint@^${version}`);
|
|
4631
4636
|
const shouldInstall = needsDependency && options.install !== false;
|
|
4632
4637
|
const result = {
|
|
4633
|
-
agentAdded: addAgent,
|
|
4638
|
+
agentAdded: addAgent || upgradeAgent,
|
|
4634
4639
|
dryRun: Boolean(options.dryRun),
|
|
4635
4640
|
installCommand: shouldInstall ? [pm, ...installArgs] : null,
|
|
4636
4641
|
packageManager: pm,
|
|
@@ -4656,6 +4661,7 @@ function initProject(options) {
|
|
|
4656
4661
|
manifest.scripts = scripts;
|
|
4657
4662
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
4658
4663
|
}
|
|
4664
|
+
if (upgradeAgent) fs.writeFileSync(agentPath, previousAgent.replace(legacyAgentText, agentText));
|
|
4659
4665
|
if (addAgent) fs.writeFileSync(agentPath, `${previousAgent}${previousAgent ? "\n\n" : ""}${agentText}`);
|
|
4660
4666
|
return result;
|
|
4661
4667
|
}
|
|
@@ -4668,7 +4674,7 @@ function registerInitCommand(program) {
|
|
|
4668
4674
|
process.stdout.write(`Scripts added: ${result.scriptsAdded.join(", ") || "none (existing scripts preserved)"}\n`);
|
|
4669
4675
|
if (result.installCommand) process.stdout.write(`Install: ${result.installCommand.join(" ")}\n`);
|
|
4670
4676
|
if (result.agentAdded) process.stdout.write("Agent instructions: AGENTS.md\n");
|
|
4671
|
-
process.stdout.write(`\
|
|
4677
|
+
process.stdout.write(`\nSet AI_GATEWAY_API_KEY, then run ${result.packageManager} run taste.\n`);
|
|
4672
4678
|
if (options.install === false) process.stdout.write("Installation skipped; the scripts require taste-lint to be installed.\n");
|
|
4673
4679
|
});
|
|
4674
4680
|
}
|
|
@@ -5190,15 +5196,14 @@ const runLint = async (options, ctx = {}) => {
|
|
|
5190
5196
|
const judgeOptions = {
|
|
5191
5197
|
cache,
|
|
5192
5198
|
config,
|
|
5193
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
5194
5199
|
model
|
|
5195
5200
|
};
|
|
5196
5201
|
const prepared = prepareJudgement(units, rules, judgeOptions);
|
|
5197
5202
|
let evaluate = ctx.evaluate;
|
|
5198
5203
|
let recorder;
|
|
5199
|
-
if (!options.dryRun &&
|
|
5204
|
+
if (!options.dryRun && prepared.pending.length > 0) {
|
|
5200
5205
|
evaluate ??= evaluateFromEnv(options.apiKey);
|
|
5201
|
-
if (!evaluate) throw new InputError("MISSING_CREDENTIALS", `${KEY_HINT}
|
|
5206
|
+
if (!evaluate) throw new InputError("MISSING_CREDENTIALS", `${KEY_HINT} Use --dry-run to preview without calling Jev.`);
|
|
5202
5207
|
} else evaluate = void 0;
|
|
5203
5208
|
if (options.printRequests) for (const p of prepared.pending) for (const questions of chunkQuestions(p)) {
|
|
5204
5209
|
const ordered = Object.fromEntries(Object.keys(questions).toSorted().map((k) => [k, questions[k]]));
|
|
@@ -5464,7 +5469,7 @@ const renderTty = (result, options = {}) => {
|
|
|
5464
5469
|
//#region src/commands/lint.ts
|
|
5465
5470
|
const quote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
5466
5471
|
function registerLintCommand(program) {
|
|
5467
|
-
program.command("lint").description("Lint files (or a rendered page) against the taste rules").argument("[paths...]", "Files or directories to lint, relative to --root").option("--root <path>", "Project root for globs and config", process.cwd()).option("--rules <path>", "Rules directory (default: packaged data/rules)").option("--only <ids>", "Comma-separated rule ids").option("--exclude <globs>", "Comma-separated globs to skip (added to taste-lint.config.json exclude)").option("--dry-run", "Plan and estimate cost without calling Jev").option("--print-requests", "With --dry-run and --output tty, print request JSONL to stderr").option("--writing-context <file>", "Explicit JSON facts/profile/instructions for personal writing; sent to Jev on live runs").option("--
|
|
5472
|
+
program.command("lint").description("Lint files (or a rendered page) against the taste rules").argument("[paths...]", "Files or directories to lint, relative to --root").option("--root <path>", "Project root for globs and config", process.cwd()).option("--rules <path>", "Rules directory (default: packaged data/rules)").option("--only <ids>", "Comma-separated rule ids").option("--exclude <globs>", "Comma-separated globs to skip (added to taste-lint.config.json exclude)").option("--dry-run", "Plan and estimate cost without calling Jev").option("--print-requests", "With --dry-run and --output tty, print request JSONL to stderr").option("--writing-context <file>", "Explicit JSON facts/profile/instructions for personal writing; sent to Jev on live runs").option("--no-cache", "Ignore cached answers (new answers are still recorded)").option("--limit-units <n>", "Only consider the first n units").option("--fail-on <severity>", "Lowest severity that fails the run: major or minor", "minor").option("--fix", "Apply deterministic fixes for act-band findings").option("--output <format>", "tty, json or sarif", "tty").option("--results-dir <path>", "Where logs and cache live", defaultResultsDir()).option("--model <id>", "Jev model id", DEFAULT_MODEL).option("--progress", "Print periodic progress on stderr even when piped").option("--verbose", "Show suppressed findings and unknowns").option("--url <url>", "Lint a rendered page through style-capture").option("--selector <css>", "Root selector for --url", "body").option("--capture <file>", "Lint a saved style-capture CaptureResult JSON").action(async (paths, options) => {
|
|
5468
5473
|
if (!["major", "minor"].includes(options.failOn)) throw new InputError("INVALID_ARGUMENT", "--fail-on must be major or minor");
|
|
5469
5474
|
if (![
|
|
5470
5475
|
"tty",
|
|
@@ -5492,7 +5497,6 @@ function registerLintCommand(program) {
|
|
|
5492
5497
|
failOn: options.failOn,
|
|
5493
5498
|
fix: options.fix,
|
|
5494
5499
|
limitUnits,
|
|
5495
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
5496
5500
|
model: options.model,
|
|
5497
5501
|
noCache: !options.cache,
|
|
5498
5502
|
only: options.only?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
@@ -6125,7 +6129,6 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6125
6129
|
},
|
|
6126
6130
|
exclude,
|
|
6127
6131
|
graph: graph?.digest,
|
|
6128
|
-
mechanicalOnly: !!options.mechanicalOnly,
|
|
6129
6132
|
model: options.model ?? "jev-latest",
|
|
6130
6133
|
profile,
|
|
6131
6134
|
root,
|
|
@@ -6146,7 +6149,6 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6146
6149
|
docTypes: profile.docTypes,
|
|
6147
6150
|
dryRun: options.dryRun,
|
|
6148
6151
|
exclude,
|
|
6149
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
6150
6152
|
model: options.model,
|
|
6151
6153
|
only: rules.map((r) => r.id),
|
|
6152
6154
|
resultsDir: options.resultsDir,
|
|
@@ -6215,7 +6217,7 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6215
6217
|
//#endregion
|
|
6216
6218
|
//#region src/commands/scan.ts
|
|
6217
6219
|
const registerScanCommand = (program) => {
|
|
6218
|
-
const scan = program.command("scan").enablePositionalOptions().description("Run a scoped scan with stable findings, baselines, and review exports").argument("[paths...]", "Targets within the root; defaults to .").option("--root <path>", "Repository root", process.cwd()).option("--profile <name>", `Scan objective: ${PROFILE_NAMES.join(", ")}`, "product").option("--rules <path>", "Rules directory").option("--only <ids>", "Comma-separated rule IDs within the selected profile").option("--exclude <globs>", "Additional comma-separated exclusions").option("--dry-run", "Preview scope and cost without provider calls").option("--
|
|
6220
|
+
const scan = program.command("scan").enablePositionalOptions().description("Run a scoped scan with stable findings, baselines, and review exports").argument("[paths...]", "Targets within the root; defaults to .").option("--root <path>", "Repository root", process.cwd()).option("--profile <name>", `Scan objective: ${PROFILE_NAMES.join(", ")}`, "product").option("--rules <path>", "Rules directory").option("--only <ids>", "Comma-separated rule IDs within the selected profile").option("--exclude <globs>", "Additional comma-separated exclusions").option("--dry-run", "Preview scope and cost without provider calls").option("--model <id>", "Evaluation model", "jev-latest").option("--results-dir <path>", "Answer cache and sanitized request records").option("--baseline <file>", "Compare a compatible completed scan").option("--new-only", "Report and gate only new findings against the baseline").option("--since <ref>", "Report changed lines against a Git commit; retain analysis context").option("--decisions <file>", "Apply accepted or dismissed review decisions").option("--dependency-cruiser <file>", "Import dependency-cruiser JSON violations").option("--samples <file>", "Export blind labeling samples, including source text, without predicted scores").option("--save <file>", "Save a scan report; only completed scans can serve as baselines").option("--output <format>", "tty, json, or sarif", "tty").option("--progress", "Print provider progress to stderr").action(async (targets, options) => {
|
|
6219
6221
|
if (![
|
|
6220
6222
|
"tty",
|
|
6221
6223
|
"json",
|
|
@@ -6545,7 +6547,6 @@ Quickstart:
|
|
|
6545
6547
|
taste-lint scan .
|
|
6546
6548
|
|
|
6547
6549
|
Preview: taste-lint scan . --dry-run
|
|
6548
|
-
Without a key: taste-lint scan . --mechanical-only
|
|
6549
6550
|
Get a key: https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys
|
|
6550
6551
|
`);
|
|
6551
6552
|
registerLintCommand(program);
|