viberails 0.5.3 → 0.5.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/dist/index.cjs CHANGED
@@ -66,7 +66,7 @@ var clack5 = __toESM(require("@clack/prompts"), 1);
66
66
  // src/utils/prompt-integrations.ts
67
67
  var import_node_child_process = require("child_process");
68
68
  var clack = __toESM(require("@clack/prompts"), 1);
69
- async function promptHookManagerInstall(projectRoot, packageManager) {
69
+ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
70
70
  const choice = await clack.select({
71
71
  message: "No git hook manager detected. Install Lefthook for shareable pre-commit hooks?",
72
72
  options: [
@@ -85,7 +85,7 @@ async function promptHookManagerInstall(projectRoot, packageManager) {
85
85
  assertNotCancelled(choice);
86
86
  if (choice !== "install") return void 0;
87
87
  const pm = packageManager || "npm";
88
- const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? "pnpm add -D lefthook" : "npm install -D lefthook";
88
+ const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
89
89
  const s = clack.spinner();
90
90
  s.start("Installing Lefthook...");
91
91
  const result = (0, import_node_child_process.spawnSync)(installCmd, {
@@ -113,7 +113,8 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
113
113
  if (!resolvedHookManager) {
114
114
  resolvedHookManager = await promptHookManagerInstall(
115
115
  projectRoot,
116
- tools?.packageManager ?? "npm"
116
+ tools?.packageManager ?? "npm",
117
+ tools?.isWorkspace
117
118
  );
118
119
  }
119
120
  const isBareHook = !resolvedHookManager;
@@ -130,7 +131,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
130
131
  options.push({
131
132
  value: "typecheck",
132
133
  label: "Typecheck (tsc --noEmit)",
133
- hint: "catches type errors before commit"
134
+ hint: "pre-commit hook + CI check"
134
135
  });
135
136
  }
136
137
  if (tools?.linter) {
@@ -138,7 +139,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
138
139
  options.push({
139
140
  value: "lint",
140
141
  label: `Lint check (${linterName})`,
141
- hint: "runs linter on staged files before commit"
142
+ hint: "pre-commit hook + CI check"
142
143
  });
143
144
  }
144
145
  options.push(
@@ -1441,7 +1442,8 @@ function formatPackageSummary(pkg) {
1441
1442
  if (pkg.stack.styling) {
1442
1443
  parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
1443
1444
  }
1444
- const files = `${pkg.statistics.totalFiles} files`;
1445
+ const n = pkg.statistics.totalFiles;
1446
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1445
1447
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1446
1448
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1447
1449
  }
@@ -1497,7 +1499,8 @@ function formatPackageSummaryPlain(pkg) {
1497
1499
  if (pkg.stack.styling) {
1498
1500
  parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
1499
1501
  }
1500
- const files = `${pkg.statistics.totalFiles} files`;
1502
+ const n = pkg.statistics.totalFiles;
1503
+ const files = `${n} ${n === 1 ? "file" : "files"}`;
1501
1504
  const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
1502
1505
  return ` ${pkg.relativePath} \u2014 ${detail}`;
1503
1506
  }
@@ -2500,7 +2503,8 @@ function checkCoveragePrereqs(projectRoot, scanResult) {
2500
2503
  return hasDependency(pkgDir, "@vitest/coverage-v8") || hasDependency(pkgDir, "@vitest/coverage-istanbul");
2501
2504
  });
2502
2505
  }
2503
- const addCmd = pm === "yarn" ? "yarn add -D" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2506
+ const isWorkspace = scanResult.packages.length > 1;
2507
+ const addCmd = pm === "yarn" ? "yarn add -D" : pm === "pnpm" && isWorkspace ? "pnpm add -D -w" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2504
2508
  const affectedPackages = vitestPackages.length > 1 ? vitestPackages : void 0;
2505
2509
  const reason = affectedPackages ? `Required for coverage in: ${affectedPackages.join(", ")}` : "Required for coverage percentage checks with vitest";
2506
2510
  return [
@@ -2701,7 +2705,6 @@ function addLefthookPreCommit(lefthookPath) {
2701
2705
  function detectHookManager(projectRoot) {
2702
2706
  if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2703
2707
  if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
2704
- if (fs16.existsSync(path16.join(projectRoot, ".git"))) return "git hook";
2705
2708
  return void 0;
2706
2709
  }
2707
2710
  function setupClaudeCodeHook(projectRoot) {
@@ -2755,7 +2758,7 @@ function setupClaudeMdReference(projectRoot) {
2755
2758
  fs16.writeFileSync(claudeMdPath, prefix + ref);
2756
2759
  console.log(` ${import_chalk9.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2757
2760
  }
2758
- function setupGithubAction(projectRoot, packageManager) {
2761
+ function setupGithubAction(projectRoot, packageManager, options) {
2759
2762
  const workflowDir = path16.join(projectRoot, ".github", "workflows");
2760
2763
  const workflowPath = path16.join(workflowDir, "viberails.yml");
2761
2764
  if (fs16.existsSync(workflowPath)) {
@@ -2791,7 +2794,16 @@ function setupGithubAction(projectRoot, packageManager) {
2791
2794
  " node-version: 22",
2792
2795
  pm !== "npm" ? ` cache: ${pm}` : "",
2793
2796
  "",
2794
- ` - run: ${installCmd}`,
2797
+ ` - run: ${installCmd}`
2798
+ );
2799
+ if (options?.typecheck) {
2800
+ lines.push(` - run: ${runPrefix} tsc --noEmit`);
2801
+ }
2802
+ if (options?.linter) {
2803
+ const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2804
+ lines.push(` - run: ${runPrefix} ${lintCmd}`);
2805
+ }
2806
+ lines.push(
2795
2807
  ` - run: ${runPrefix} viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
2796
2808
  ""
2797
2809
  );
@@ -2912,7 +2924,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2912
2924
  created.push("CLAUDE.md \u2014 added @.viberails/context.md reference");
2913
2925
  }
2914
2926
  if (integrations.githubAction) {
2915
- const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm");
2927
+ const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm", {
2928
+ linter: integrations.lintHook ? opts.linter : void 0,
2929
+ typecheck: integrations.typecheckHook
2930
+ });
2916
2931
  if (t) created.push(`${t} \u2014 blocks PRs on violations`);
2917
2932
  }
2918
2933
  return created;
@@ -2980,11 +2995,15 @@ async function initNonInteractive(projectRoot, configPath) {
2980
2995
  setupClaudeMdReference(projectRoot);
2981
2996
  const rootPkg = config.packages[0];
2982
2997
  const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
2983
- const actionTarget = setupGithubAction(projectRoot, rootPkgPm);
2998
+ const linter = rootPkg?.stack?.linter?.split("@")[0];
2999
+ const isTypeScript = rootPkg?.stack?.language === "typescript";
3000
+ const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
3001
+ linter,
3002
+ typecheck: isTypeScript
3003
+ });
2984
3004
  const hookManager = detectHookManager(projectRoot);
2985
3005
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
2986
3006
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
2987
- const linter = rootPkg?.stack?.linter?.split("@")[0];
2988
3007
  const ok = import_chalk11.default.green("\u2713");
2989
3008
  const created = [
2990
3009
  `${ok} ${path18.basename(configPath)}`,
@@ -3017,13 +3036,6 @@ async function initInteractive(projectRoot, configPath, options) {
3017
3036
  const scanResult = await (0, import_scanner2.scan)(projectRoot);
3018
3037
  const config = (0, import_config8.generateConfig)(scanResult);
3019
3038
  s.stop("Scan complete");
3020
- const prereqResult = await promptMissingPrereqs(
3021
- projectRoot,
3022
- checkCoveragePrereqs(projectRoot, scanResult)
3023
- );
3024
- if (prereqResult.disableCoverage) {
3025
- config.rules.testCoverage = 0;
3026
- }
3027
3039
  if (scanResult.statistics.totalFiles === 0) {
3028
3040
  clack8.log.warn(
3029
3041
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
@@ -3064,20 +3076,29 @@ async function initInteractive(projectRoot, configPath, options) {
3064
3076
  if (denyCount > 0) {
3065
3077
  config.boundaries = inferred;
3066
3078
  config.rules.enforceBoundaries = true;
3067
- bs.stop(`Inferred ${denyCount} boundary rules`);
3068
- const boundaryLines = Object.entries(inferred.deny).map(([pkg, denied]) => `${pkg} must NOT import from: ${denied.join(", ")}`).join("\n");
3069
- clack8.note(boundaryLines, "Boundary rules");
3079
+ const pkgCount = Object.keys(inferred.deny).length;
3080
+ bs.stop(`Inferred ${denyCount} boundary rules across ${pkgCount} packages`);
3070
3081
  } else {
3071
3082
  bs.stop("No boundary rules inferred");
3072
3083
  }
3073
3084
  }
3074
3085
  }
3075
3086
  const hookManager = detectHookManager(projectRoot);
3087
+ const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3088
+ const hasMissingPrereqs = coveragePrereqs.some((p) => !p.installed) || !hookManager;
3089
+ if (hasMissingPrereqs) {
3090
+ clack8.log.info("Some dependencies are needed for full functionality.");
3091
+ }
3092
+ const prereqResult = await promptMissingPrereqs(projectRoot, coveragePrereqs);
3093
+ if (prereqResult.disableCoverage) {
3094
+ config.rules.testCoverage = 0;
3095
+ }
3076
3096
  const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
3077
3097
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3078
3098
  isTypeScript: rootPkgStack?.language === "typescript",
3079
3099
  linter: rootPkgStack?.linter?.split("@")[0],
3080
- packageManager: rootPkgStack?.packageManager
3100
+ packageManager: rootPkgStack?.packageManager,
3101
+ isWorkspace: config.packages.length > 1
3081
3102
  });
3082
3103
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
3083
3104
  if (!shouldWrite) {
@@ -3089,17 +3110,14 @@ async function initInteractive(projectRoot, configPath, options) {
3089
3110
  `);
3090
3111
  writeGeneratedFiles(projectRoot, config, scanResult);
3091
3112
  updateGitignore(projectRoot);
3092
- const createdFiles = [
3093
- path18.basename(configPath),
3094
- ".viberails/context.md",
3095
- ".viberails/scan-result.json",
3096
- ...setupSelectedIntegrations(projectRoot, integrations, {
3097
- linter: rootPkgStack?.linter?.split("@")[0],
3098
- packageManager: rootPkgStack?.packageManager
3099
- })
3100
- ];
3101
- clack8.log.success(`Created:
3102
- ${createdFiles.map((f) => ` ${f}`).join("\n")}`);
3113
+ const ok = import_chalk11.default.green("\u2713");
3114
+ clack8.log.step(`${ok} ${path18.basename(configPath)}`);
3115
+ clack8.log.step(`${ok} .viberails/context.md`);
3116
+ clack8.log.step(`${ok} .viberails/scan-result.json`);
3117
+ setupSelectedIntegrations(projectRoot, integrations, {
3118
+ linter: rootPkgStack?.linter?.split("@")[0],
3119
+ packageManager: rootPkgStack?.packageManager
3120
+ });
3103
3121
  clack8.outro(
3104
3122
  `Done! Next: review viberails.config.json, then run viberails check
3105
3123
  ${import_chalk11.default.dim("Tip: use")} ${import_chalk11.default.cyan("viberails check --enforce")} ${import_chalk11.default.dim("in CI to block PRs on violations.")}`
@@ -3213,7 +3231,7 @@ ${import_chalk12.default.bold("Synced:")}`);
3213
3231
  }
3214
3232
 
3215
3233
  // src/index.ts
3216
- var VERSION = "0.5.3";
3234
+ var VERSION = "0.5.5";
3217
3235
  var program = new import_commander.Command();
3218
3236
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3219
3237
  program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {