taste-lint 0.0.3 → 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 +11 -10
- package/dist/cli.js +147 -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>
|
|
@@ -16,10 +16,10 @@ Scan your project with local checks and [Jev by TypeSafe AI](https://docs.typesa
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
npx taste-lint@latest init
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires Node 24.11 or later.
|
|
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
|
|
|
@@ -27,11 +27,13 @@ Create a [Vercel AI Gateway key](https://vercel.com/docs/ai-gateway/authenticati
|
|
|
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
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
|
+
Use your package manager in place of npm. Add `--agent` to init for agent instructions, or `--dry-run` to preview setup.
|
|
36
|
+
|
|
35
37
|
## What it checks
|
|
36
38
|
|
|
37
39
|
- **Product interfaces:** copy, typography, interaction, and motion in JSX, TSX, and CSS.
|
|
@@ -42,12 +44,11 @@ Rules draw on [Agent Skills](https://github.com/mblode/agent-skills) and [Taste
|
|
|
42
44
|
|
|
43
45
|
## Useful options
|
|
44
46
|
|
|
45
|
-
| Option
|
|
46
|
-
|
|
|
47
|
-
| `--dry-run`
|
|
48
|
-
| `--
|
|
49
|
-
| `--output
|
|
50
|
-
| `--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 |
|
|
51
52
|
|
|
52
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.
|
|
53
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);
|
|
@@ -3063,10 +3058,10 @@ const at = (evidence, offset = 0) => ({
|
|
|
3063
3058
|
fired: true,
|
|
3064
3059
|
offset
|
|
3065
3060
|
});
|
|
3066
|
-
const object$
|
|
3061
|
+
const object$2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3067
3062
|
const json = (u) => {
|
|
3068
3063
|
try {
|
|
3069
|
-
return object$
|
|
3064
|
+
return object$2(JSON.parse(u.text));
|
|
3070
3065
|
} catch {
|
|
3071
3066
|
throw new UnresolvedError("JSON is not parseable");
|
|
3072
3067
|
}
|
|
@@ -3101,7 +3096,7 @@ const REPOSITORY_RULES = [
|
|
|
3101
3096
|
"typings"
|
|
3102
3097
|
]) if (typeof value[key] === "string") entries.push(value[key]);
|
|
3103
3098
|
if (typeof value.bin === "string") entries.push(value.bin);
|
|
3104
|
-
else entries.push(...Object.values(object$
|
|
3099
|
+
else entries.push(...Object.values(object$2(value.bin)).filter((v) => typeof v === "string"));
|
|
3105
3100
|
const visit = (v) => {
|
|
3106
3101
|
if (typeof v === "string") entries.push(v);
|
|
3107
3102
|
else if (v && typeof v === "object") for (const child of Object.values(v)) visit(child);
|
|
@@ -3112,7 +3107,7 @@ const REPOSITORY_RULES = [
|
|
|
3112
3107
|
const resolved = u.facts.repository.resolve(u.file, entry);
|
|
3113
3108
|
if (resolved === void 0) return at(`Entry escapes repository: ${entry}`);
|
|
3114
3109
|
if (!u.facts.repository.exists(resolved)) {
|
|
3115
|
-
if (/^(?:\.\/)?(?:dist|build|lib)\//.test(entry) && object$
|
|
3110
|
+
if (/^(?:\.\/)?(?:dist|build|lib)\//.test(entry) && object$2(value.scripts).build) throw new UnresolvedError(`Build output not available: ${entry}. Build or inspect the packed artifact.`);
|
|
3116
3111
|
return at(`Missing package entry: ${entry}`);
|
|
3117
3112
|
}
|
|
3118
3113
|
}
|
|
@@ -3120,7 +3115,7 @@ const REPOSITORY_RULES = [
|
|
|
3120
3115
|
}, "Build and inspect the package, then make bin, exports, and declaration paths match its actual artifacts."),
|
|
3121
3116
|
make$1("dx", "package-bin-shebang", "CLI executable has no Node shebang", PKG, "scaffold-cli/references/scaffold-source.md", (u) => {
|
|
3122
3117
|
const value = json(u);
|
|
3123
|
-
const bins = typeof value.bin === "string" ? [value.bin] : Object.values(object$
|
|
3118
|
+
const bins = typeof value.bin === "string" ? [value.bin] : Object.values(object$2(value.bin)).filter((v) => typeof v === "string");
|
|
3124
3119
|
for (const bin of bins) {
|
|
3125
3120
|
if (!/\.[cm]?js$/.test(bin)) continue;
|
|
3126
3121
|
const resolved = u.facts.repository.resolve(u.file, bin);
|
|
@@ -3132,7 +3127,7 @@ const REPOSITORY_RULES = [
|
|
|
3132
3127
|
return none;
|
|
3133
3128
|
}, "Emit exactly one Node shebang in the published executable."),
|
|
3134
3129
|
make$1("dx", "exports-mixed-keys", "Package exports mixes conditions and subpaths", PKG, "dx-audit/rules/api-stable-contract.md", (u) => {
|
|
3135
|
-
const keys = Object.keys(object$
|
|
3130
|
+
const keys = Object.keys(object$2(json(u).exports));
|
|
3136
3131
|
return keys.some((k) => k.startsWith(".")) && keys.some((k) => !k.startsWith(".")) ? at("Exports mixes subpath keys with condition keys at the same level") : none;
|
|
3137
3132
|
}, "Put conditions inside each exported subpath, or use a conditions-only root export."),
|
|
3138
3133
|
make$1("architecture", "undeclared-import", "Import has no declared package dependency", CODE, GUARD, (u) => {
|
|
@@ -3145,7 +3140,7 @@ const REPOSITORY_RULES = [
|
|
|
3145
3140
|
"devDependencies",
|
|
3146
3141
|
"peerDependencies",
|
|
3147
3142
|
"optionalDependencies"
|
|
3148
|
-
].flatMap((key) => Object.keys(object$
|
|
3143
|
+
].flatMap((key) => Object.keys(object$2(value[key])))]);
|
|
3149
3144
|
for (const item of u.facts.imports) {
|
|
3150
3145
|
if (/^(?:\.|\/|#|~|[a-z]+:)/i.test(item.name) || builtins.has(item.name)) continue;
|
|
3151
3146
|
const name = item.name.startsWith("@") ? item.name.split("/").slice(0, 2).join("/") : item.name.split("/")[0];
|
|
@@ -3171,7 +3166,7 @@ const REPOSITORY_RULES = [
|
|
|
3171
3166
|
make$1("architecture", "missing-tsconfig-reference", "TypeScript project reference points to a missing target", ["**/tsconfig*.json"], GUARD, (u) => {
|
|
3172
3167
|
const value = json(u);
|
|
3173
3168
|
for (const entry of Array.isArray(value.references) ? value.references : []) {
|
|
3174
|
-
const target = object$
|
|
3169
|
+
const target = object$2(entry).path;
|
|
3175
3170
|
if (typeof target !== "string") continue;
|
|
3176
3171
|
const resolved = u.facts.repository.resolve(u.file, target);
|
|
3177
3172
|
if (!resolved) throw new UnresolvedError("Project reference outside repository");
|
|
@@ -4554,6 +4549,136 @@ function registerExtractCommand(program) {
|
|
|
4554
4549
|
});
|
|
4555
4550
|
}
|
|
4556
4551
|
//#endregion
|
|
4552
|
+
//#region src/setup/init.ts
|
|
4553
|
+
const managers = [
|
|
4554
|
+
"npm",
|
|
4555
|
+
"pnpm",
|
|
4556
|
+
"yarn",
|
|
4557
|
+
"bun"
|
|
4558
|
+
];
|
|
4559
|
+
const lockfiles = {
|
|
4560
|
+
bun: ["bun.lock", "bun.lockb"],
|
|
4561
|
+
npm: ["package-lock.json", "npm-shrinkwrap.json"],
|
|
4562
|
+
pnpm: ["pnpm-lock.yaml"],
|
|
4563
|
+
yarn: ["yarn.lock"]
|
|
4564
|
+
};
|
|
4565
|
+
const marker = "<!-- taste-lint -->";
|
|
4566
|
+
const agentText = `${marker}
|
|
4567
|
+
## Taste Lint
|
|
4568
|
+
|
|
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.
|
|
4570
|
+
Fix act findings, review advisory findings in context, and recheck the edited files.
|
|
4571
|
+
Never invent a key or treat unknown checks as passes.
|
|
4572
|
+
<!-- /taste-lint -->
|
|
4573
|
+
`;
|
|
4574
|
+
function object$1(value) {
|
|
4575
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4576
|
+
}
|
|
4577
|
+
function initProject(options) {
|
|
4578
|
+
const root = path.resolve(options.root);
|
|
4579
|
+
const manifestPath = path.join(root, "package.json");
|
|
4580
|
+
let manifest;
|
|
4581
|
+
try {
|
|
4582
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
4583
|
+
if (!object$1(parsed)) throw new Error("Expected an object");
|
|
4584
|
+
manifest = parsed;
|
|
4585
|
+
} catch {
|
|
4586
|
+
throw new InputError("INVALID_PROJECT", "Run init in a project with a valid package.json, or use --root <path>.");
|
|
4587
|
+
}
|
|
4588
|
+
const declared = typeof manifest.packageManager === "string" ? manifest.packageManager.split("@")[0] : void 0;
|
|
4589
|
+
const detected = managers.filter((manager) => lockfiles[manager].some((file) => fs.existsSync(path.join(root, file))));
|
|
4590
|
+
const selected = options.pm ?? declared ?? (detected.length === 1 ? detected[0] : void 0);
|
|
4591
|
+
if (!selected && detected.length > 1) throw new InputError("AMBIGUOUS_PACKAGE_MANAGER", "Multiple lockfiles found. Choose --pm npm, pnpm, yarn, or bun.");
|
|
4592
|
+
const pm = selected ?? "npm";
|
|
4593
|
+
if (!managers.includes(pm)) throw new InputError("INVALID_PACKAGE_MANAGER", "Choose --pm npm, pnpm, yarn, or bun.");
|
|
4594
|
+
for (const field of [
|
|
4595
|
+
"scripts",
|
|
4596
|
+
"dependencies",
|
|
4597
|
+
"devDependencies"
|
|
4598
|
+
]) if (manifest[field] !== void 0 && !object$1(manifest[field])) throw new InputError("INVALID_PROJECT", `package.json ${field} must be an object.`);
|
|
4599
|
+
const dependencies = {
|
|
4600
|
+
...manifest.dependencies,
|
|
4601
|
+
...manifest.devDependencies
|
|
4602
|
+
};
|
|
4603
|
+
const profile = [
|
|
4604
|
+
"react",
|
|
4605
|
+
"next",
|
|
4606
|
+
"vue",
|
|
4607
|
+
"svelte",
|
|
4608
|
+
"astro"
|
|
4609
|
+
].some((name) => name in dependencies) ? "product" : "writing";
|
|
4610
|
+
const scripts = { ...manifest.scripts };
|
|
4611
|
+
const added = [];
|
|
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)) {
|
|
4617
|
+
scripts[name] = command;
|
|
4618
|
+
added.push(name);
|
|
4619
|
+
}
|
|
4620
|
+
const agentPath = path.join(root, "AGENTS.md");
|
|
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));
|
|
4632
|
+
const addAgent = Boolean(options.agent && !previousAgent.includes(marker));
|
|
4633
|
+
const needsDependency = !("taste-lint" in dependencies);
|
|
4634
|
+
const installArgs = pm === "npm" ? ["install", "--save-dev"] : ["add", "--dev"];
|
|
4635
|
+
installArgs.push(`taste-lint@^${version}`);
|
|
4636
|
+
const shouldInstall = needsDependency && options.install !== false;
|
|
4637
|
+
const result = {
|
|
4638
|
+
agentAdded: addAgent || upgradeAgent,
|
|
4639
|
+
dryRun: Boolean(options.dryRun),
|
|
4640
|
+
installCommand: shouldInstall ? [pm, ...installArgs] : null,
|
|
4641
|
+
packageManager: pm,
|
|
4642
|
+
profile,
|
|
4643
|
+
root,
|
|
4644
|
+
scriptsAdded: added
|
|
4645
|
+
};
|
|
4646
|
+
if (options.dryRun) return result;
|
|
4647
|
+
if (shouldInstall) {
|
|
4648
|
+
const installed = spawnSync(pm, installArgs, {
|
|
4649
|
+
cwd: root,
|
|
4650
|
+
shell: false,
|
|
4651
|
+
stdio: [
|
|
4652
|
+
"ignore",
|
|
4653
|
+
"inherit",
|
|
4654
|
+
"inherit"
|
|
4655
|
+
]
|
|
4656
|
+
});
|
|
4657
|
+
if (installed.error || installed.status !== 0) throw new InputError("INSTALL_FAILED", `Installation failed. Run ${pm} ${installArgs.join(" ")} and retry init.`);
|
|
4658
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
4659
|
+
}
|
|
4660
|
+
if (added.length > 0) {
|
|
4661
|
+
manifest.scripts = scripts;
|
|
4662
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
4663
|
+
}
|
|
4664
|
+
if (upgradeAgent) fs.writeFileSync(agentPath, previousAgent.replace(legacyAgentText, agentText));
|
|
4665
|
+
if (addAgent) fs.writeFileSync(agentPath, `${previousAgent}${previousAgent ? "\n\n" : ""}${agentText}`);
|
|
4666
|
+
return result;
|
|
4667
|
+
}
|
|
4668
|
+
//#endregion
|
|
4669
|
+
//#region src/commands/init.ts
|
|
4670
|
+
function registerInitCommand(program) {
|
|
4671
|
+
program.command("init").description("Install Taste Lint locally and add project check scripts").option("--root <path>", "Project directory", process.cwd()).option("--pm <name>", "Package manager: npm, pnpm, yarn, or bun").option("--dry-run", "Preview setup without installing or writing files").option("--no-install", "Add scripts without installing the package").option("--agent", "Append Taste Lint guidance to AGENTS.md").action((options) => {
|
|
4672
|
+
const result = initProject(options);
|
|
4673
|
+
process.stdout.write(`${result.dryRun ? "Setup preview" : "Setup complete"}: ${result.packageManager}, ${result.profile} profile\n`);
|
|
4674
|
+
process.stdout.write(`Scripts added: ${result.scriptsAdded.join(", ") || "none (existing scripts preserved)"}\n`);
|
|
4675
|
+
if (result.installCommand) process.stdout.write(`Install: ${result.installCommand.join(" ")}\n`);
|
|
4676
|
+
if (result.agentAdded) process.stdout.write("Agent instructions: AGENTS.md\n");
|
|
4677
|
+
process.stdout.write(`\nSet AI_GATEWAY_API_KEY, then run ${result.packageManager} run taste.\n`);
|
|
4678
|
+
if (options.install === false) process.stdout.write("Installation skipped; the scripts require taste-lint to be installed.\n");
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4681
|
+
//#endregion
|
|
4557
4682
|
//#region src/lib/spawn.ts
|
|
4558
4683
|
function spawnCapture(cmd, argv, options = {}) {
|
|
4559
4684
|
return new Promise((resolve, reject) => {
|
|
@@ -5071,15 +5196,14 @@ const runLint = async (options, ctx = {}) => {
|
|
|
5071
5196
|
const judgeOptions = {
|
|
5072
5197
|
cache,
|
|
5073
5198
|
config,
|
|
5074
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
5075
5199
|
model
|
|
5076
5200
|
};
|
|
5077
5201
|
const prepared = prepareJudgement(units, rules, judgeOptions);
|
|
5078
5202
|
let evaluate = ctx.evaluate;
|
|
5079
5203
|
let recorder;
|
|
5080
|
-
if (!options.dryRun &&
|
|
5204
|
+
if (!options.dryRun && prepared.pending.length > 0) {
|
|
5081
5205
|
evaluate ??= evaluateFromEnv(options.apiKey);
|
|
5082
|
-
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.`);
|
|
5083
5207
|
} else evaluate = void 0;
|
|
5084
5208
|
if (options.printRequests) for (const p of prepared.pending) for (const questions of chunkQuestions(p)) {
|
|
5085
5209
|
const ordered = Object.fromEntries(Object.keys(questions).toSorted().map((k) => [k, questions[k]]));
|
|
@@ -5345,7 +5469,7 @@ const renderTty = (result, options = {}) => {
|
|
|
5345
5469
|
//#region src/commands/lint.ts
|
|
5346
5470
|
const quote = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
5347
5471
|
function registerLintCommand(program) {
|
|
5348
|
-
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) => {
|
|
5349
5473
|
if (!["major", "minor"].includes(options.failOn)) throw new InputError("INVALID_ARGUMENT", "--fail-on must be major or minor");
|
|
5350
5474
|
if (![
|
|
5351
5475
|
"tty",
|
|
@@ -5373,7 +5497,6 @@ function registerLintCommand(program) {
|
|
|
5373
5497
|
failOn: options.failOn,
|
|
5374
5498
|
fix: options.fix,
|
|
5375
5499
|
limitUnits,
|
|
5376
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
5377
5500
|
model: options.model,
|
|
5378
5501
|
noCache: !options.cache,
|
|
5379
5502
|
only: options.only?.split(",").map((s) => s.trim()).filter(Boolean),
|
|
@@ -6006,7 +6129,6 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6006
6129
|
},
|
|
6007
6130
|
exclude,
|
|
6008
6131
|
graph: graph?.digest,
|
|
6009
|
-
mechanicalOnly: !!options.mechanicalOnly,
|
|
6010
6132
|
model: options.model ?? "jev-latest",
|
|
6011
6133
|
profile,
|
|
6012
6134
|
root,
|
|
@@ -6027,7 +6149,6 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6027
6149
|
docTypes: profile.docTypes,
|
|
6028
6150
|
dryRun: options.dryRun,
|
|
6029
6151
|
exclude,
|
|
6030
|
-
mechanicalOnly: options.mechanicalOnly,
|
|
6031
6152
|
model: options.model,
|
|
6032
6153
|
only: rules.map((r) => r.id),
|
|
6033
6154
|
resultsDir: options.resultsDir,
|
|
@@ -6096,7 +6217,7 @@ const runScan = async (options, ctx = {}) => {
|
|
|
6096
6217
|
//#endregion
|
|
6097
6218
|
//#region src/commands/scan.ts
|
|
6098
6219
|
const registerScanCommand = (program) => {
|
|
6099
|
-
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) => {
|
|
6100
6221
|
if (![
|
|
6101
6222
|
"tty",
|
|
6102
6223
|
"json",
|
|
@@ -6426,10 +6547,10 @@ Quickstart:
|
|
|
6426
6547
|
taste-lint scan .
|
|
6427
6548
|
|
|
6428
6549
|
Preview: taste-lint scan . --dry-run
|
|
6429
|
-
Without a key: taste-lint scan . --mechanical-only
|
|
6430
6550
|
Get a key: https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys
|
|
6431
6551
|
`);
|
|
6432
6552
|
registerLintCommand(program);
|
|
6553
|
+
registerInitCommand(program);
|
|
6433
6554
|
registerScanCommand(program);
|
|
6434
6555
|
registerExtractCommand(program);
|
|
6435
6556
|
registerRulesCommand(program);
|