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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Catch AI slop before you ship.**
6
6
 
7
- Scan your project with local checks and [Jev by TypeSafe AI](https://docs.typesafe.ai/introduction).
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
- npm install -g taste-lint
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
- taste-lint scan .
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 | What it does |
46
- | ------------------- | ---------------------------------------------------- |
47
- | `--dry-run` | Preview scope and estimated cost without model calls |
48
- | `--mechanical-only` | Run local checks without an API key |
49
- | `--output json` | Save findings for scripts and agents |
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.3";
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. Use --mechanical-only for lint/scan without a key.";
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, options.mechanicalOnly ? rules.filter((r) => r.tier === "mechanical") : rules, options.config);
2007
- if (options.mechanicalOnly) for (const unit of units) {
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$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3061
+ const object$2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3067
3062
  const json = (u) => {
3068
3063
  try {
3069
- return object$1(JSON.parse(u.text));
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$1(value.bin)).filter((v) => typeof v === "string"));
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$1(value.scripts).build) throw new UnresolvedError(`Build output not available: ${entry}. Build or inspect the packed artifact.`);
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$1(value.bin)).filter((v) => typeof v === "string");
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$1(json(u).exports));
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$1(value[key])))]);
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$1(entry).path;
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 && !options.mechanicalOnly && prepared.pending.length > 0) {
5204
+ if (!options.dryRun && prepared.pending.length > 0) {
5081
5205
  evaluate ??= evaluateFromEnv(options.apiKey);
5082
- if (!evaluate) throw new InputError("MISSING_CREDENTIALS", `${KEY_HINT} Or run with --dry-run or --mechanical-only.`);
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("--mechanical-only", "Skip every Jev-backed rule").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) => {
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("--mechanical-only", "Run deterministic checks only").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) => {
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);