viberails 0.6.4 → 0.6.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.js CHANGED
@@ -1,7 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ assertNotCancelled,
4
+ confirm,
5
+ confirmDangerous,
6
+ getRootPackage,
7
+ promptExistingConfigAction,
8
+ promptInitDecision,
9
+ promptIntegrations,
10
+ promptRuleMenu,
11
+ spawnAsync
12
+ } from "./chunk-XQKOK3FU.js";
2
13
 
3
14
  // src/index.ts
4
- import chalk14 from "chalk";
15
+ import chalk16 from "chalk";
5
16
  import { Command } from "commander";
6
17
 
7
18
  // src/commands/boundaries.ts
@@ -27,581 +38,6 @@ function findProjectRoot(startDir) {
27
38
  }
28
39
  }
29
40
 
30
- // src/utils/prompt.ts
31
- import * as clack5 from "@clack/prompts";
32
-
33
- // src/utils/prompt-integrations.ts
34
- import * as clack from "@clack/prompts";
35
-
36
- // src/utils/spawn-async.ts
37
- import { spawn } from "child_process";
38
- function spawnAsync(command, cwd) {
39
- return new Promise((resolve4) => {
40
- const child = spawn(command, { cwd, shell: true, stdio: "pipe" });
41
- let stdout = "";
42
- let stderr = "";
43
- child.stdout.on("data", (d) => {
44
- stdout += d.toString();
45
- });
46
- child.stderr.on("data", (d) => {
47
- stderr += d.toString();
48
- });
49
- child.on("close", (status) => {
50
- resolve4({ status, stdout, stderr });
51
- });
52
- child.on("error", () => {
53
- resolve4({ status: 1, stdout, stderr });
54
- });
55
- });
56
- }
57
-
58
- // src/utils/prompt-integrations.ts
59
- async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
60
- const choice = await clack.select({
61
- message: "No shared git hook manager detected. Install Lefthook?",
62
- options: [
63
- {
64
- value: "install",
65
- label: "Yes, install Lefthook",
66
- hint: "recommended \u2014 hooks are committed to the repo and shared with your team"
67
- },
68
- {
69
- value: "skip",
70
- label: "No, skip",
71
- hint: "pre-commit hooks will be local-only (.git/hooks) and not shared"
72
- }
73
- ]
74
- });
75
- assertNotCancelled(choice);
76
- if (choice !== "install") return void 0;
77
- const pm = packageManager || "npm";
78
- const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
79
- const s = clack.spinner();
80
- s.start("Installing Lefthook...");
81
- const result = await spawnAsync(installCmd, projectRoot);
82
- if (result.status === 0) {
83
- const fs21 = await import("fs");
84
- const path21 = await import("path");
85
- const lefthookPath = path21.join(projectRoot, "lefthook.yml");
86
- if (!fs21.existsSync(lefthookPath)) {
87
- fs21.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
88
- }
89
- s.stop("Installed Lefthook");
90
- return "Lefthook";
91
- }
92
- s.stop("Failed to install Lefthook");
93
- clack.log.warn(`Install manually: ${installCmd}`);
94
- return void 0;
95
- }
96
- async function promptIntegrations(projectRoot, hookManager, tools) {
97
- let resolvedHookManager = hookManager;
98
- if (!resolvedHookManager) {
99
- resolvedHookManager = await promptHookManagerInstall(
100
- projectRoot,
101
- tools?.packageManager ?? "npm",
102
- tools?.isWorkspace
103
- );
104
- }
105
- const isBareHook = !resolvedHookManager;
106
- const hookLabel = resolvedHookManager ? `Pre-commit hook (${resolvedHookManager})` : "Pre-commit hook (git hook \u2014 local only)";
107
- const hookHint = isBareHook ? "local only \u2014 will NOT be committed or shared with collaborators" : "runs viberails checks when you commit";
108
- const options = [
109
- {
110
- value: "preCommit",
111
- label: hookLabel,
112
- hint: hookHint
113
- }
114
- ];
115
- if (tools?.isTypeScript) {
116
- options.push({
117
- value: "typecheck",
118
- label: "Typecheck (tsc --noEmit)",
119
- hint: "pre-commit hook + CI check"
120
- });
121
- }
122
- if (tools?.linter) {
123
- const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
124
- options.push({
125
- value: "lint",
126
- label: `Lint check (${linterName})`,
127
- hint: "pre-commit hook + CI check"
128
- });
129
- }
130
- options.push(
131
- {
132
- value: "claude",
133
- label: "Claude Code hook",
134
- hint: "checks files when Claude edits them"
135
- },
136
- {
137
- value: "claudeMd",
138
- label: "CLAUDE.md reference",
139
- hint: "appends @.viberails/context.md so Claude loads rules automatically"
140
- },
141
- {
142
- value: "githubAction",
143
- label: "GitHub Actions workflow",
144
- hint: "blocks PRs that fail viberails check"
145
- }
146
- );
147
- const initialValues = isBareHook ? options.filter((o) => o.value !== "preCommit").map((o) => o.value) : options.map((o) => o.value);
148
- const result = await clack.multiselect({
149
- message: "Optional integrations",
150
- options,
151
- initialValues,
152
- required: false
153
- });
154
- assertNotCancelled(result);
155
- return {
156
- preCommitHook: result.includes("preCommit"),
157
- claudeCodeHook: result.includes("claude"),
158
- claudeMdRef: result.includes("claudeMd"),
159
- githubAction: result.includes("githubAction"),
160
- typecheckHook: result.includes("typecheck"),
161
- lintHook: result.includes("lint")
162
- };
163
- }
164
-
165
- // src/utils/prompt-rules.ts
166
- import * as clack4 from "@clack/prompts";
167
-
168
- // src/utils/prompt-menu-handlers.ts
169
- import * as clack3 from "@clack/prompts";
170
-
171
- // src/utils/prompt-package-overrides.ts
172
- import * as clack2 from "@clack/prompts";
173
- function normalizePackageOverrides(packages) {
174
- for (const pkg of packages) {
175
- if (pkg.rules && Object.keys(pkg.rules).length === 0) {
176
- delete pkg.rules;
177
- }
178
- if (pkg.coverage && Object.keys(pkg.coverage).length === 0) {
179
- delete pkg.coverage;
180
- }
181
- }
182
- return packages;
183
- }
184
- function packageCoverageHint(pkg, defaults) {
185
- const coverage = pkg.rules?.testCoverage ?? defaults.testCoverage;
186
- const isExempt = coverage === 0;
187
- const hasSummaryOverride = pkg.coverage?.summaryPath !== void 0 && pkg.coverage.summaryPath !== defaults.coverageSummaryPath;
188
- const defaultCommand = defaults.coverageCommand ?? "";
189
- const hasCommandOverride = pkg.coverage?.command !== void 0 && pkg.coverage.command !== defaultCommand;
190
- const tags = [];
191
- const nameSegments = pkg.name.replace(/^@[^/]+\//, "").split(/[-/]/);
192
- const isTypesOnly = isExempt && nameSegments.some((s) => s === "types");
193
- tags.push(isExempt ? isTypesOnly ? "exempt (types-only)" : "exempt" : `${coverage}%`);
194
- if (hasSummaryOverride) tags.push("summary override");
195
- if (hasCommandOverride) tags.push("command override");
196
- return tags.join(", ");
197
- }
198
- async function promptPackageCoverageOverrides(packages, defaults) {
199
- const editablePackages = packages.filter((pkg) => pkg.path !== ".");
200
- if (editablePackages.length === 0) return packages;
201
- while (true) {
202
- const selectedPath = await clack2.select({
203
- message: "Select package to edit coverage overrides",
204
- options: [
205
- ...editablePackages.map((pkg) => ({
206
- value: pkg.path,
207
- label: `${pkg.path} (${pkg.name})`,
208
- hint: packageCoverageHint(pkg, defaults)
209
- })),
210
- { value: "__done__", label: "Done" }
211
- ]
212
- });
213
- assertNotCancelled(selectedPath);
214
- if (selectedPath === "__done__") break;
215
- const target = editablePackages.find((pkg) => pkg.path === selectedPath);
216
- if (!target) continue;
217
- while (true) {
218
- const effectiveCoverage = target.rules?.testCoverage ?? defaults.testCoverage;
219
- const effectiveSummary = target.coverage?.summaryPath ?? defaults.coverageSummaryPath;
220
- const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ?? "(auto-detect)";
221
- const choice = await clack2.select({
222
- message: `Edit coverage overrides for ${target.path}`,
223
- options: [
224
- { value: "testCoverage", label: "testCoverage", hint: String(effectiveCoverage) },
225
- { value: "summaryPath", label: "coverage.summaryPath", hint: effectiveSummary },
226
- { value: "command", label: "coverage.command", hint: effectiveCommand },
227
- { value: "reset", label: "Reset this package to inherit defaults" },
228
- { value: "back", label: "Back to package list" }
229
- ]
230
- });
231
- assertNotCancelled(choice);
232
- if (choice === "back") break;
233
- if (choice === "testCoverage") {
234
- const result = await clack2.text({
235
- message: "Package testCoverage (0 to exempt package)?",
236
- initialValue: String(effectiveCoverage),
237
- validate: (v) => {
238
- if (typeof v !== "string") return "Enter a number between 0 and 100";
239
- const n = Number.parseInt(v, 10);
240
- if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
241
- }
242
- });
243
- assertNotCancelled(result);
244
- const nextCoverage = Number.parseInt(result, 10);
245
- if (nextCoverage === defaults.testCoverage) {
246
- if (target.rules) {
247
- delete target.rules.testCoverage;
248
- }
249
- } else {
250
- target.rules = { ...target.rules ?? {}, testCoverage: nextCoverage };
251
- }
252
- }
253
- if (choice === "summaryPath") {
254
- const result = await clack2.text({
255
- message: "Package coverage.summaryPath (blank to inherit default)?",
256
- initialValue: target.coverage?.summaryPath !== void 0 ? target.coverage.summaryPath : "",
257
- placeholder: defaults.coverageSummaryPath
258
- });
259
- assertNotCancelled(result);
260
- const value = result.trim();
261
- if (value.length === 0 || value === defaults.coverageSummaryPath) {
262
- if (target.coverage) {
263
- delete target.coverage.summaryPath;
264
- }
265
- } else {
266
- target.coverage = { ...target.coverage ?? {}, summaryPath: value };
267
- }
268
- }
269
- if (choice === "command") {
270
- const result = await clack2.text({
271
- message: "Package coverage.command (blank to inherit default/auto)?",
272
- initialValue: target.coverage?.command !== void 0 ? target.coverage.command : "",
273
- placeholder: defaults.coverageCommand ?? "(auto-detect from package.json test runner)"
274
- });
275
- assertNotCancelled(result);
276
- const value = result.trim();
277
- const defaultCommand = defaults.coverageCommand ?? "";
278
- if (value.length === 0 || value === defaultCommand) {
279
- if (target.coverage) {
280
- delete target.coverage.command;
281
- }
282
- } else {
283
- target.coverage = { ...target.coverage ?? {}, command: value };
284
- }
285
- }
286
- if (choice === "reset") {
287
- if (target.rules) {
288
- delete target.rules.testCoverage;
289
- }
290
- delete target.coverage;
291
- }
292
- normalizePackageOverrides(editablePackages);
293
- }
294
- }
295
- return normalizePackageOverrides(packages);
296
- }
297
-
298
- // src/utils/prompt-menu-handlers.ts
299
- function getPackageDiffs(pkg, root) {
300
- const diffs = [];
301
- const convKeys = ["fileNaming", "componentNaming", "hookNaming", "importAlias"];
302
- for (const key of convKeys) {
303
- if (pkg.conventions?.[key] && pkg.conventions[key] !== root.conventions?.[key]) {
304
- diffs.push(`${key}: ${pkg.conventions[key]}`);
305
- }
306
- }
307
- const stackKeys = [
308
- "framework",
309
- "language",
310
- "styling",
311
- "backend",
312
- "orm",
313
- "linter",
314
- "formatter",
315
- "testRunner",
316
- "packageManager"
317
- ];
318
- for (const key of stackKeys) {
319
- if (pkg.stack?.[key] && pkg.stack[key] !== root.stack?.[key]) {
320
- diffs.push(`${key}: ${pkg.stack[key]}`);
321
- }
322
- }
323
- if (pkg.rules?.maxFileLines !== void 0 && pkg.rules.maxFileLines !== root.rules?.maxFileLines && pkg.rules.maxFileLines > 0) {
324
- diffs.push(`maxFileLines: ${pkg.rules.maxFileLines}`);
325
- }
326
- if (pkg.rules?.testCoverage !== void 0 && pkg.rules.testCoverage !== root.rules?.testCoverage && pkg.rules.testCoverage >= 0) {
327
- diffs.push(`testCoverage: ${pkg.rules.testCoverage}`);
328
- }
329
- if (pkg.coverage?.summaryPath && pkg.coverage.summaryPath !== root.coverage?.summaryPath) {
330
- diffs.push(`coverage.summaryPath: ${pkg.coverage.summaryPath}`);
331
- }
332
- if (pkg.coverage?.command && pkg.coverage.command !== root.coverage?.command) {
333
- diffs.push("coverage.command: (override)");
334
- }
335
- return diffs;
336
- }
337
- function buildMenuOptions(state, packageCount) {
338
- const namingHint = state.enforceNaming ? `yes${state.fileNamingValue ? ` (${state.fileNamingValue})` : ""}` : "no";
339
- const options = [
340
- { value: "maxFileLines", label: "Max file lines", hint: String(state.maxFileLines) },
341
- { value: "enforceNaming", label: "Enforce file naming", hint: namingHint }
342
- ];
343
- if (state.fileNamingValue) {
344
- options.push({
345
- value: "fileNaming",
346
- label: "File naming convention",
347
- hint: state.fileNamingValue
348
- });
349
- }
350
- const isMonorepo = packageCount > 0;
351
- const coverageLabel = isMonorepo ? "Default coverage target" : "Test coverage target";
352
- const coverageHint = state.testCoverage === 0 ? "0 (disabled)" : isMonorepo ? `${state.testCoverage}% (per-package default)` : `${state.testCoverage}%`;
353
- options.push({ value: "testCoverage", label: coverageLabel, hint: coverageHint });
354
- options.push({
355
- value: "enforceMissingTests",
356
- label: "Enforce missing tests",
357
- hint: state.enforceMissingTests ? "yes" : "no"
358
- });
359
- if (state.testCoverage > 0) {
360
- options.push(
361
- {
362
- value: "coverageSummaryPath",
363
- label: isMonorepo ? "Default coverage summary path" : "Coverage summary path",
364
- hint: state.coverageSummaryPath
365
- },
366
- {
367
- value: "coverageCommand",
368
- label: isMonorepo ? "Default coverage command" : "Coverage command",
369
- hint: state.coverageCommand ?? "auto-detect from package.json test runner"
370
- }
371
- );
372
- if (isMonorepo) {
373
- options.push({
374
- value: "packageOverrides",
375
- label: "Per-package coverage overrides",
376
- hint: `${packageCount} package${packageCount > 1 ? "s" : ""} configurable`
377
- });
378
- }
379
- }
380
- options.push(
381
- { value: "reset", label: "Reset all to detected defaults" },
382
- { value: "done", label: "Done" }
383
- );
384
- return options;
385
- }
386
- function clonePackages(packages) {
387
- return packages?.map((pkg) => ({
388
- ...pkg,
389
- stack: pkg.stack ? { ...pkg.stack } : void 0,
390
- structure: pkg.structure ? { ...pkg.structure } : void 0,
391
- conventions: pkg.conventions ? { ...pkg.conventions } : void 0,
392
- rules: pkg.rules ? { ...pkg.rules } : void 0,
393
- coverage: pkg.coverage ? { ...pkg.coverage } : void 0,
394
- ignore: pkg.ignore ? [...pkg.ignore] : void 0,
395
- boundaries: pkg.boundaries ? {
396
- deny: [...pkg.boundaries.deny],
397
- ignore: pkg.boundaries.ignore ? [...pkg.boundaries.ignore] : void 0
398
- } : void 0
399
- }));
400
- }
401
- async function handleMenuChoice(choice, state, defaults, root) {
402
- if (choice === "reset") {
403
- state.maxFileLines = defaults.maxFileLines;
404
- state.testCoverage = defaults.testCoverage;
405
- state.enforceMissingTests = defaults.enforceMissingTests;
406
- state.enforceNaming = defaults.enforceNaming;
407
- state.fileNamingValue = defaults.fileNamingValue;
408
- state.coverageSummaryPath = defaults.coverageSummaryPath;
409
- state.coverageCommand = defaults.coverageCommand;
410
- state.packageOverrides = clonePackages(defaults.packageOverrides);
411
- clack3.log.info("Reset all rules to detected defaults.");
412
- return;
413
- }
414
- if (choice === "packageOverrides") {
415
- if (state.packageOverrides) {
416
- const packageDiffs = root ? state.packageOverrides.filter((pkg) => pkg.path !== root.path).map((pkg) => ({ pkg, diffs: getPackageDiffs(pkg, root) })).filter((entry) => entry.diffs.length > 0) : [];
417
- state.packageOverrides = await promptPackageCoverageOverrides(state.packageOverrides, {
418
- testCoverage: state.testCoverage,
419
- coverageSummaryPath: state.coverageSummaryPath,
420
- coverageCommand: state.coverageCommand
421
- });
422
- const lines = packageDiffs.map((entry) => `${entry.pkg.path}
423
- ${entry.diffs.join(", ")}`);
424
- if (lines.length > 0) {
425
- clack3.note(lines.join("\n\n"), "Existing package differences");
426
- }
427
- }
428
- return;
429
- }
430
- if (choice === "maxFileLines") {
431
- const result = await clack3.text({
432
- message: "Maximum lines per source file?",
433
- initialValue: String(state.maxFileLines),
434
- validate: (v) => {
435
- if (typeof v !== "string") return "Enter a positive number";
436
- const n = Number.parseInt(v, 10);
437
- if (Number.isNaN(n) || n < 1) return "Enter a positive number";
438
- }
439
- });
440
- assertNotCancelled(result);
441
- state.maxFileLines = Number.parseInt(result, 10);
442
- }
443
- if (choice === "enforceMissingTests") {
444
- const result = await clack3.confirm({
445
- message: "Require every source file to have a corresponding test file?",
446
- initialValue: state.enforceMissingTests
447
- });
448
- assertNotCancelled(result);
449
- state.enforceMissingTests = result;
450
- }
451
- if (choice === "testCoverage") {
452
- const result = await clack3.text({
453
- message: "Test coverage target (0 disables coverage checks)?",
454
- initialValue: String(state.testCoverage),
455
- validate: (v) => {
456
- if (typeof v !== "string") return "Enter a number between 0 and 100";
457
- const n = Number.parseInt(v, 10);
458
- if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
459
- }
460
- });
461
- assertNotCancelled(result);
462
- state.testCoverage = Number.parseInt(result, 10);
463
- }
464
- if (choice === "coverageSummaryPath") {
465
- const result = await clack3.text({
466
- message: "Coverage summary path (relative to package root)?",
467
- initialValue: state.coverageSummaryPath,
468
- validate: (v) => {
469
- if (typeof v !== "string" || v.trim().length === 0) return "Path cannot be empty";
470
- }
471
- });
472
- assertNotCancelled(result);
473
- state.coverageSummaryPath = result.trim();
474
- }
475
- if (choice === "coverageCommand") {
476
- const result = await clack3.text({
477
- message: "Coverage command (blank to auto-detect from package.json)?",
478
- initialValue: state.coverageCommand ?? "",
479
- placeholder: "(auto-detect from package.json test runner)"
480
- });
481
- assertNotCancelled(result);
482
- const trimmed = result.trim();
483
- state.coverageCommand = trimmed.length > 0 ? trimmed : void 0;
484
- }
485
- if (choice === "enforceNaming") {
486
- const result = await clack3.confirm({
487
- message: state.fileNamingValue ? `Enforce file naming? (detected: ${state.fileNamingValue})` : "Enforce file naming?",
488
- initialValue: state.enforceNaming
489
- });
490
- assertNotCancelled(result);
491
- state.enforceNaming = result;
492
- }
493
- if (choice === "fileNaming") {
494
- const selected = await clack3.select({
495
- message: "Which file naming convention should be enforced?",
496
- options: [
497
- { value: "kebab-case", label: "kebab-case" },
498
- { value: "camelCase", label: "camelCase" },
499
- { value: "PascalCase", label: "PascalCase" },
500
- { value: "snake_case", label: "snake_case" }
501
- ],
502
- initialValue: state.fileNamingValue
503
- });
504
- assertNotCancelled(selected);
505
- state.fileNamingValue = selected;
506
- }
507
- }
508
-
509
- // src/utils/prompt-rules.ts
510
- function getRootPackage(packages) {
511
- return packages.find((pkg) => pkg.path === ".") ?? packages[0];
512
- }
513
- async function promptRuleMenu(defaults) {
514
- const state = {
515
- ...defaults,
516
- packageOverrides: clonePackages(defaults.packageOverrides)
517
- };
518
- const root = state.packageOverrides && state.packageOverrides.length > 0 ? getRootPackage(state.packageOverrides) : void 0;
519
- const packageCount = state.packageOverrides?.filter((pkg) => pkg.path !== ".").length ?? 0;
520
- while (true) {
521
- const options = buildMenuOptions(state, packageCount);
522
- const choice = await clack4.select({ message: "Customize rules", options });
523
- assertNotCancelled(choice);
524
- if (choice === "done") break;
525
- await handleMenuChoice(choice, state, defaults, root);
526
- }
527
- return {
528
- maxFileLines: state.maxFileLines,
529
- testCoverage: state.testCoverage,
530
- enforceMissingTests: state.enforceMissingTests,
531
- enforceNaming: state.enforceNaming,
532
- fileNamingValue: state.fileNamingValue,
533
- coverageSummaryPath: state.coverageSummaryPath,
534
- coverageCommand: state.coverageCommand,
535
- packageOverrides: state.packageOverrides
536
- };
537
- }
538
-
539
- // src/utils/prompt.ts
540
- function assertNotCancelled(value) {
541
- if (clack5.isCancel(value)) {
542
- clack5.cancel("Setup cancelled.");
543
- process.exit(0);
544
- }
545
- }
546
- async function confirm3(message) {
547
- const result = await clack5.confirm({ message, initialValue: true });
548
- assertNotCancelled(result);
549
- return result;
550
- }
551
- async function confirmDangerous(message) {
552
- const result = await clack5.confirm({ message, initialValue: false });
553
- assertNotCancelled(result);
554
- return result;
555
- }
556
- async function promptExistingConfigAction(configFile) {
557
- const result = await clack5.select({
558
- message: `${configFile} already exists. What do you want to do?`,
559
- options: [
560
- {
561
- value: "edit",
562
- label: "Edit existing config",
563
- hint: "open the current rules and save updates in place"
564
- },
565
- {
566
- value: "replace",
567
- label: "Replace with a fresh scan",
568
- hint: "re-scan the project and overwrite the current config"
569
- },
570
- {
571
- value: "cancel",
572
- label: "Cancel",
573
- hint: "leave the current setup unchanged"
574
- }
575
- ]
576
- });
577
- assertNotCancelled(result);
578
- return result;
579
- }
580
- async function promptInitDecision() {
581
- const result = await clack5.select({
582
- message: "How do you want to proceed?",
583
- options: [
584
- {
585
- value: "accept",
586
- label: "Accept defaults",
587
- hint: "writes the config with these defaults; use --enforce in CI to block"
588
- },
589
- {
590
- value: "customize",
591
- label: "Customize rules",
592
- hint: "edit limits, naming, test coverage, and package overrides"
593
- },
594
- {
595
- value: "review",
596
- label: "Review detected details",
597
- hint: "show the full scan report with package and structure details"
598
- }
599
- ]
600
- });
601
- assertNotCancelled(result);
602
- return result;
603
- }
604
-
605
41
  // src/utils/resolve-workspace-packages.ts
606
42
  import * as fs2 from "fs";
607
43
  import * as path2 from "path";
@@ -706,7 +142,7 @@ ${chalk.bold("Inferred boundary rules:")}
706
142
  console.log(`
707
143
  ${totalRules} denied`);
708
144
  console.log("");
709
- const shouldSave = await confirm3("Save to viberails.config.json?");
145
+ const shouldSave = await confirm("Save to viberails.config.json?");
710
146
  if (shouldSave) {
711
147
  config.boundaries = inferred;
712
148
  config.rules.enforceBoundaries = true;
@@ -1218,13 +654,13 @@ function checkMissingTests(projectRoot, config, severity) {
1218
654
  const testSuffix = testPattern.replace("*", "");
1219
655
  const sourceFiles = collectSourceFiles(srcPath, projectRoot);
1220
656
  for (const relFile of sourceFiles) {
1221
- const basename9 = path6.basename(relFile);
1222
- if (basename9.includes(".test.") || basename9.includes(".spec.") || basename9.startsWith("index.") || basename9.endsWith(".d.ts")) {
657
+ const basename10 = path6.basename(relFile);
658
+ if (basename10.includes(".test.") || basename10.includes(".spec.") || basename10.startsWith("index.") || basename10.endsWith(".d.ts")) {
1223
659
  continue;
1224
660
  }
1225
- const ext = path6.extname(basename9);
661
+ const ext = path6.extname(basename10);
1226
662
  if (!SOURCE_EXTS2.has(ext)) continue;
1227
- const stem = basename9.slice(0, -ext.length);
663
+ const stem = basename10.slice(0, -ext.length);
1228
664
  const expectedTestFile = `${stem}${testSuffix}`;
1229
665
  const dir = path6.dirname(path6.join(projectRoot, relFile));
1230
666
  const colocatedTest = path6.join(dir, expectedTestFile);
@@ -1305,9 +741,9 @@ async function checkCommand(options, cwd) {
1305
741
  }
1306
742
  const violations = [];
1307
743
  const severity = options.enforce ? "error" : "warn";
1308
- const log7 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(chalk3.dim(msg)) : () => {
744
+ const log5 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(chalk3.dim(msg)) : () => {
1309
745
  };
1310
- log7(" Checking files...");
746
+ log5(" Checking files...");
1311
747
  for (const file of filesToCheck) {
1312
748
  const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
1313
749
  const relPath = path7.relative(projectRoot, absPath);
@@ -1340,9 +776,9 @@ async function checkCommand(options, cwd) {
1340
776
  }
1341
777
  }
1342
778
  }
1343
- log7(" done\n");
779
+ log5(" done\n");
1344
780
  if (!options.files) {
1345
- log7(" Checking missing tests...");
781
+ log5(" Checking missing tests...");
1346
782
  const testViolations = checkMissingTests(projectRoot, config, severity);
1347
783
  if (options.staged) {
1348
784
  const stagedSet = new Set(filesToCheck);
@@ -1355,14 +791,14 @@ async function checkCommand(options, cwd) {
1355
791
  } else {
1356
792
  violations.push(...testViolations);
1357
793
  }
1358
- log7(" done\n");
794
+ log5(" done\n");
1359
795
  }
1360
796
  if (!options.files && !options.staged && !options.diffBase) {
1361
- log7(" Running test coverage...\n");
797
+ log5(" Running test coverage...\n");
1362
798
  const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
1363
799
  staged: options.staged,
1364
800
  enforce: options.enforce,
1365
- onProgress: (pkg) => log7(` Coverage: ${pkg}...
801
+ onProgress: (pkg) => log5(` Coverage: ${pkg}...
1366
802
  `)
1367
803
  });
1368
804
  violations.push(...coverageViolations);
@@ -1387,7 +823,7 @@ async function checkCommand(options, cwd) {
1387
823
  severity
1388
824
  });
1389
825
  }
1390
- log7(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
826
+ log5(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
1391
827
  `);
1392
828
  }
1393
829
  if (options.format === "json") {
@@ -1463,7 +899,7 @@ async function hookCheckCommand(cwd) {
1463
899
  // src/commands/config.ts
1464
900
  import * as fs10 from "fs";
1465
901
  import * as path9 from "path";
1466
- import * as clack6 from "@clack/prompts";
902
+ import * as clack from "@clack/prompts";
1467
903
  import { compactConfig as compactConfig2, loadConfig as loadConfig3, mergeConfig } from "@viberails/config";
1468
904
  import { scan } from "@viberails/scanner";
1469
905
  import chalk6 from "chalk";
@@ -1654,15 +1090,6 @@ function formatMonorepoResultsText(scanResult) {
1654
1090
  }
1655
1091
 
1656
1092
  // src/display.ts
1657
- var INIT_OVERVIEW_NAMES = {
1658
- typescript: "TypeScript",
1659
- javascript: "JavaScript",
1660
- eslint: "ESLint",
1661
- prettier: "Prettier",
1662
- jest: "Jest",
1663
- vitest: "Vitest",
1664
- biome: "Biome"
1665
- };
1666
1093
  function formatItem(item, nameMap) {
1667
1094
  const name = nameMap?.[item.name] ?? item.name;
1668
1095
  return item.version ? `${name} ${item.version}` : name;
@@ -1792,134 +1219,6 @@ function displayRulesPreview(config) {
1792
1219
  );
1793
1220
  console.log("");
1794
1221
  }
1795
- function formatDetectedOverview(scanResult) {
1796
- const { stack } = scanResult;
1797
- const primaryParts = [];
1798
- const secondaryParts = [];
1799
- const formatOverviewItem = (item, nameMap) => formatItem(item, { ...INIT_OVERVIEW_NAMES, ...nameMap });
1800
- if (scanResult.packages.length > 1) {
1801
- primaryParts.push("monorepo");
1802
- primaryParts.push(`${scanResult.packages.length} packages`);
1803
- } else if (stack.framework) {
1804
- primaryParts.push(formatItem(stack.framework, FRAMEWORK_NAMES2));
1805
- } else {
1806
- primaryParts.push("single package");
1807
- }
1808
- primaryParts.push(formatOverviewItem(stack.language));
1809
- if (stack.styling) {
1810
- primaryParts.push(formatOverviewItem(stack.styling, STYLING_NAMES2));
1811
- }
1812
- if (stack.packageManager) secondaryParts.push(formatOverviewItem(stack.packageManager));
1813
- if (stack.linter) secondaryParts.push(formatOverviewItem(stack.linter));
1814
- if (stack.formatter) secondaryParts.push(formatOverviewItem(stack.formatter));
1815
- if (stack.testRunner) secondaryParts.push(formatOverviewItem(stack.testRunner));
1816
- const primary = primaryParts.map((part) => chalk5.cyan(part)).join(chalk5.dim(" \xB7 "));
1817
- const secondary = secondaryParts.join(chalk5.dim(" \xB7 "));
1818
- return secondary ? `${primary}
1819
- ${chalk5.dim(secondary)}` : primary;
1820
- }
1821
- function displayInitOverview(scanResult, config, exemptedPackages) {
1822
- const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1823
- const isMonorepo = config.packages.length > 1;
1824
- const ok = chalk5.green("\u2713");
1825
- const info = chalk5.yellow("~");
1826
- console.log("");
1827
- console.log(` ${chalk5.bold("Ready to initialize:")}`);
1828
- console.log(` ${formatDetectedOverview(scanResult)}`);
1829
- console.log("");
1830
- console.log(` ${chalk5.bold("Rules to apply:")}`);
1831
- console.log(` ${ok} Max file size: ${chalk5.cyan(`${config.rules.maxFileLines} lines`)}`);
1832
- const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1833
- if (config.rules.enforceNaming && fileNaming) {
1834
- console.log(` ${ok} File naming: ${chalk5.cyan(fileNaming)}`);
1835
- } else {
1836
- console.log(` ${info} File naming: ${chalk5.dim("not enforced")}`);
1837
- }
1838
- const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1839
- if (config.rules.enforceMissingTests && testPattern) {
1840
- console.log(` ${ok} Missing tests: ${chalk5.cyan(`enforced (${testPattern})`)}`);
1841
- } else if (config.rules.enforceMissingTests) {
1842
- console.log(` ${ok} Missing tests: ${chalk5.cyan("enforced")}`);
1843
- } else {
1844
- console.log(` ${info} Missing tests: ${chalk5.dim("not enforced")}`);
1845
- }
1846
- if (config.rules.testCoverage > 0) {
1847
- if (isMonorepo) {
1848
- const withCoverage = config.packages.filter(
1849
- (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
1850
- );
1851
- console.log(
1852
- ` ${ok} Coverage: ${chalk5.cyan(`${config.rules.testCoverage}%`)} default ${chalk5.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1853
- );
1854
- } else {
1855
- console.log(` ${ok} Coverage: ${chalk5.cyan(`${config.rules.testCoverage}%`)}`);
1856
- }
1857
- } else {
1858
- console.log(` ${info} Coverage: ${chalk5.dim("disabled")}`);
1859
- }
1860
- if (exemptedPackages.length > 0) {
1861
- console.log(
1862
- ` ${chalk5.dim(" exempted:")} ${chalk5.dim(exemptedPackages.join(", "))} ${chalk5.dim("(types-only)")}`
1863
- );
1864
- }
1865
- console.log("");
1866
- console.log(` ${chalk5.bold("Also available:")}`);
1867
- if (isMonorepo) {
1868
- console.log(` ${info} Infer boundaries from current imports`);
1869
- }
1870
- console.log(` ${info} Set up hooks, Claude integration, and CI checks`);
1871
- console.log(
1872
- `
1873
- ${chalk5.dim("Defaults warn locally. Use --enforce in CI when you want failures to block.")}`
1874
- );
1875
- console.log("");
1876
- }
1877
- function summarizeSelectedIntegrations(integrations, opts) {
1878
- const lines = [];
1879
- if (opts.hasBoundaries) {
1880
- lines.push("\u2713 Boundary rules: inferred from current imports");
1881
- } else {
1882
- lines.push("~ Boundary rules: not enabled");
1883
- }
1884
- if (opts.hasCoverage) {
1885
- lines.push("\u2713 Coverage checks: enabled");
1886
- } else {
1887
- lines.push("~ Coverage checks: disabled");
1888
- }
1889
- const selectedIntegrations = [
1890
- integrations.preCommitHook ? "pre-commit hook" : void 0,
1891
- integrations.typecheckHook ? "typecheck" : void 0,
1892
- integrations.lintHook ? "lint check" : void 0,
1893
- integrations.claudeCodeHook ? "Claude Code hook" : void 0,
1894
- integrations.claudeMdRef ? "CLAUDE.md reference" : void 0,
1895
- integrations.githubAction ? "GitHub Actions workflow" : void 0
1896
- ].filter(Boolean);
1897
- if (selectedIntegrations.length > 0) {
1898
- lines.push(`\u2713 Integrations: ${selectedIntegrations.join(" \xB7 ")}`);
1899
- } else {
1900
- lines.push("~ Integrations: none selected");
1901
- }
1902
- return lines;
1903
- }
1904
- function displaySetupPlan(config, integrations, opts = {}) {
1905
- const configFile = opts.configFile ?? "viberails.config.json";
1906
- const lines = summarizeSelectedIntegrations(integrations, {
1907
- hasBoundaries: config.rules.enforceBoundaries,
1908
- hasCoverage: config.rules.testCoverage > 0
1909
- });
1910
- console.log("");
1911
- console.log(` ${chalk5.bold("Ready to write:")}`);
1912
- console.log(
1913
- ` ${opts.replacingExistingConfig ? chalk5.yellow("!") : chalk5.green("\u2713")} ${configFile}${opts.replacingExistingConfig ? chalk5.dim(" (replacing existing config)") : ""}`
1914
- );
1915
- console.log(` ${chalk5.green("\u2713")} .viberails/context.md`);
1916
- console.log(` ${chalk5.green("\u2713")} .viberails/scan-result.json`);
1917
- for (const line of lines) {
1918
- const icon = line.startsWith("\u2713") ? chalk5.green("\u2713") : chalk5.yellow("~");
1919
- console.log(` ${icon} ${line.slice(2)}`);
1920
- }
1921
- console.log("");
1922
- }
1923
1222
 
1924
1223
  // src/display-text.ts
1925
1224
  function plainConfidenceLabel(convention) {
@@ -2038,7 +1337,9 @@ function formatScanResultsText(scanResult) {
2038
1337
  // src/utils/apply-rule-overrides.ts
2039
1338
  function applyRuleOverrides(config, overrides) {
2040
1339
  if (overrides.packageOverrides) config.packages = overrides.packageOverrides;
1340
+ const rootPkg = getRootPackage(config.packages);
2041
1341
  config.rules.maxFileLines = overrides.maxFileLines;
1342
+ config.rules.maxTestFileLines = overrides.maxTestFileLines;
2042
1343
  config.rules.testCoverage = overrides.testCoverage;
2043
1344
  config.rules.enforceMissingTests = overrides.enforceMissingTests;
2044
1345
  config.rules.enforceNaming = overrides.enforceNaming;
@@ -2052,7 +1353,6 @@ function applyRuleOverrides(config, overrides) {
2052
1353
  }
2053
1354
  }
2054
1355
  if (overrides.fileNamingValue) {
2055
- const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
2056
1356
  const oldNaming = rootPkg.conventions?.fileNaming;
2057
1357
  rootPkg.conventions = rootPkg.conventions ?? {};
2058
1358
  rootPkg.conventions.fileNaming = overrides.fileNamingValue;
@@ -2064,6 +1364,18 @@ function applyRuleOverrides(config, overrides) {
2064
1364
  }
2065
1365
  }
2066
1366
  }
1367
+ if (rootPkg) {
1368
+ rootPkg.conventions = rootPkg.conventions ?? {};
1369
+ if (overrides.componentNaming !== void 0) {
1370
+ rootPkg.conventions.componentNaming = overrides.componentNaming || void 0;
1371
+ }
1372
+ if (overrides.hookNaming !== void 0) {
1373
+ rootPkg.conventions.hookNaming = overrides.hookNaming || void 0;
1374
+ }
1375
+ if (overrides.importAlias !== void 0) {
1376
+ rootPkg.conventions.importAlias = overrides.importAlias || void 0;
1377
+ }
1378
+ }
2067
1379
  }
2068
1380
 
2069
1381
  // src/utils/diff-configs.ts
@@ -2232,27 +1544,31 @@ async function configCommand(options, cwd) {
2232
1544
  return;
2233
1545
  }
2234
1546
  if (!options.suppressIntro) {
2235
- clack6.intro("viberails config");
1547
+ clack.intro("viberails config");
2236
1548
  }
2237
1549
  const config = await loadConfig3(configPath);
2238
1550
  let scanResult = options.rescan ? await rescanAndMerge(projectRoot, config) : void 0;
2239
- clack6.note(formatRulesText(config).join("\n"), "Current rules");
1551
+ clack.note(formatRulesText(config).join("\n"), "Current rules");
2240
1552
  const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
2241
1553
  const overrides = await promptRuleMenu({
2242
1554
  maxFileLines: config.rules.maxFileLines,
1555
+ maxTestFileLines: config.rules.maxTestFileLines,
2243
1556
  testCoverage: config.rules.testCoverage,
2244
1557
  enforceMissingTests: config.rules.enforceMissingTests,
2245
1558
  enforceNaming: config.rules.enforceNaming,
2246
1559
  fileNamingValue: rootPkg.conventions?.fileNaming,
1560
+ componentNaming: rootPkg.conventions?.componentNaming,
1561
+ hookNaming: rootPkg.conventions?.hookNaming,
1562
+ importAlias: rootPkg.conventions?.importAlias,
2247
1563
  coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
2248
1564
  coverageCommand: config.defaults?.coverage?.command,
2249
1565
  packageOverrides: config.packages
2250
1566
  });
2251
1567
  applyRuleOverrides(config, overrides);
2252
1568
  if (options.rescan && config.packages.length > 1) {
2253
- const shouldInfer = await confirm3("Re-infer boundary rules from import patterns?");
1569
+ const shouldInfer = await confirm("Re-infer boundary rules from import patterns?");
2254
1570
  if (shouldInfer) {
2255
- const bs = clack6.spinner();
1571
+ const bs = clack.spinner();
2256
1572
  bs.start("Building import graph...");
2257
1573
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
2258
1574
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -2268,31 +1584,31 @@ async function configCommand(options, cwd) {
2268
1584
  }
2269
1585
  }
2270
1586
  }
2271
- const shouldWrite = await confirm3("Save updated configuration?");
1587
+ const shouldWrite = await confirm("Save updated configuration?");
2272
1588
  if (!shouldWrite) {
2273
- clack6.outro("No changes written.");
1589
+ clack.outro("No changes written.");
2274
1590
  return;
2275
1591
  }
2276
1592
  const compacted = compactConfig2(config);
2277
1593
  fs10.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
2278
1594
  `);
2279
1595
  if (!scanResult) {
2280
- const s = clack6.spinner();
1596
+ const s = clack.spinner();
2281
1597
  s.start("Scanning for context generation...");
2282
1598
  scanResult = await scan(projectRoot);
2283
1599
  s.stop("Scan complete");
2284
1600
  }
2285
1601
  writeGeneratedFiles(projectRoot, config, scanResult);
2286
- clack6.log.success(
1602
+ clack.log.success(
2287
1603
  `Updated:
2288
1604
  ${CONFIG_FILE3}
2289
1605
  .viberails/context.md
2290
1606
  .viberails/scan-result.json`
2291
1607
  );
2292
- clack6.outro("Done! Run viberails check to verify.");
1608
+ clack.outro("Done! Run viberails check to verify.");
2293
1609
  }
2294
1610
  async function rescanAndMerge(projectRoot, config) {
2295
- const s = clack6.spinner();
1611
+ const s = clack.spinner();
2296
1612
  s.start("Re-scanning project...");
2297
1613
  const scanResult = await scan(projectRoot);
2298
1614
  const merged = mergeConfig(config, scanResult);
@@ -2303,9 +1619,9 @@ async function rescanAndMerge(projectRoot, config) {
2303
1619
  const icon = c.type === "removed" ? "-" : "+";
2304
1620
  return `${icon} ${c.description}`;
2305
1621
  }).join("\n");
2306
- clack6.note(changeLines, "Changes detected");
1622
+ clack.note(changeLines, "Changes detected");
2307
1623
  } else {
2308
- clack6.log.info("No new changes detected from scan.");
1624
+ clack.log.info("No new changes detected from scan.");
2309
1625
  }
2310
1626
  Object.assign(config, merged);
2311
1627
  return scanResult;
@@ -2624,10 +1940,10 @@ function generateTestStub(sourceRelPath, config, projectRoot) {
2624
1940
  const pkg = resolvePackageForFile(sourceRelPath, config);
2625
1941
  const testPattern = pkg?.structure?.testPattern;
2626
1942
  if (!testPattern) return null;
2627
- const basename9 = path12.basename(sourceRelPath);
2628
- const ext = path12.extname(basename9);
1943
+ const basename10 = path12.basename(sourceRelPath);
1944
+ const ext = path12.extname(basename10);
2629
1945
  if (!ext) return null;
2630
- const stem = basename9.slice(0, -ext.length);
1946
+ const stem = basename10.slice(0, -ext.length);
2631
1947
  const testSuffix = testPattern.replace("*", "");
2632
1948
  const testFilename = `${stem}${testSuffix}`;
2633
1949
  const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
@@ -2799,18 +2115,159 @@ ${chalk8.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
2799
2115
  }
2800
2116
 
2801
2117
  // src/commands/init.ts
2802
- import * as fs19 from "fs";
2803
- import * as path19 from "path";
2804
- import * as clack8 from "@clack/prompts";
2805
- import { compactConfig as compactConfig3, generateConfig } from "@viberails/config";
2806
- import { scan as scan2 } from "@viberails/scanner";
2807
- import chalk12 from "chalk";
2118
+ import * as fs20 from "fs";
2119
+ import * as path20 from "path";
2120
+ import * as clack4 from "@clack/prompts";
2121
+ import { compactConfig as compactConfig4, generateConfig as generateConfig2 } from "@viberails/config";
2122
+ import { scan as scan3 } from "@viberails/scanner";
2123
+ import chalk14 from "chalk";
2124
+
2125
+ // src/display-init.ts
2126
+ import { FRAMEWORK_NAMES as FRAMEWORK_NAMES5, STYLING_NAMES as STYLING_NAMES5 } from "@viberails/types";
2127
+ import chalk9 from "chalk";
2128
+ var INIT_OVERVIEW_NAMES = {
2129
+ typescript: "TypeScript",
2130
+ javascript: "JavaScript",
2131
+ eslint: "ESLint",
2132
+ prettier: "Prettier",
2133
+ jest: "Jest",
2134
+ vitest: "Vitest",
2135
+ biome: "Biome"
2136
+ };
2137
+ function formatDetectedOverview(scanResult) {
2138
+ const { stack } = scanResult;
2139
+ const primaryParts = [];
2140
+ const secondaryParts = [];
2141
+ const formatOverviewItem = (item, nameMap) => formatItem(item, { ...INIT_OVERVIEW_NAMES, ...nameMap });
2142
+ if (scanResult.packages.length > 1) {
2143
+ primaryParts.push("monorepo");
2144
+ primaryParts.push(`${scanResult.packages.length} packages`);
2145
+ } else if (stack.framework) {
2146
+ primaryParts.push(formatItem(stack.framework, FRAMEWORK_NAMES5));
2147
+ } else {
2148
+ primaryParts.push("single package");
2149
+ }
2150
+ primaryParts.push(formatOverviewItem(stack.language));
2151
+ if (stack.styling) {
2152
+ primaryParts.push(formatOverviewItem(stack.styling, STYLING_NAMES5));
2153
+ }
2154
+ if (stack.packageManager) secondaryParts.push(formatOverviewItem(stack.packageManager));
2155
+ if (stack.linter) secondaryParts.push(formatOverviewItem(stack.linter));
2156
+ if (stack.formatter) secondaryParts.push(formatOverviewItem(stack.formatter));
2157
+ if (stack.testRunner) secondaryParts.push(formatOverviewItem(stack.testRunner));
2158
+ const primary = primaryParts.map((part) => chalk9.cyan(part)).join(chalk9.dim(" \xB7 "));
2159
+ const secondary = secondaryParts.join(chalk9.dim(" \xB7 "));
2160
+ return secondary ? `${primary}
2161
+ ${chalk9.dim(secondary)}` : primary;
2162
+ }
2163
+ function displayInitOverview(scanResult, config, exemptedPackages) {
2164
+ const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
2165
+ const isMonorepo = config.packages.length > 1;
2166
+ const ok = chalk9.green("\u2713");
2167
+ const info = chalk9.yellow("~");
2168
+ console.log("");
2169
+ console.log(` ${chalk9.bold("Ready to initialize:")}`);
2170
+ console.log(` ${formatDetectedOverview(scanResult)}`);
2171
+ console.log("");
2172
+ console.log(` ${chalk9.bold("Rules to apply:")}`);
2173
+ console.log(` ${ok} Max file size: ${chalk9.cyan(`${config.rules.maxFileLines} lines`)}`);
2174
+ const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
2175
+ if (config.rules.enforceNaming && fileNaming) {
2176
+ console.log(` ${ok} File naming: ${chalk9.cyan(fileNaming)}`);
2177
+ } else {
2178
+ console.log(` ${info} File naming: ${chalk9.dim("not enforced")}`);
2179
+ }
2180
+ const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
2181
+ if (config.rules.enforceMissingTests && testPattern) {
2182
+ console.log(` ${ok} Missing tests: ${chalk9.cyan(`enforced (${testPattern})`)}`);
2183
+ } else if (config.rules.enforceMissingTests) {
2184
+ console.log(` ${ok} Missing tests: ${chalk9.cyan("enforced")}`);
2185
+ } else {
2186
+ console.log(` ${info} Missing tests: ${chalk9.dim("not enforced")}`);
2187
+ }
2188
+ if (config.rules.testCoverage > 0) {
2189
+ if (isMonorepo) {
2190
+ const withCoverage = config.packages.filter(
2191
+ (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
2192
+ );
2193
+ console.log(
2194
+ ` ${ok} Coverage: ${chalk9.cyan(`${config.rules.testCoverage}%`)} default ${chalk9.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
2195
+ );
2196
+ } else {
2197
+ console.log(` ${ok} Coverage: ${chalk9.cyan(`${config.rules.testCoverage}%`)}`);
2198
+ }
2199
+ } else {
2200
+ console.log(` ${info} Coverage: ${chalk9.dim("disabled")}`);
2201
+ }
2202
+ if (exemptedPackages.length > 0) {
2203
+ console.log(
2204
+ ` ${chalk9.dim(" exempted:")} ${chalk9.dim(exemptedPackages.join(", "))} ${chalk9.dim("(types-only)")}`
2205
+ );
2206
+ }
2207
+ console.log("");
2208
+ console.log(` ${chalk9.bold("Also available:")}`);
2209
+ if (isMonorepo) {
2210
+ console.log(` ${info} Infer boundaries from current imports`);
2211
+ }
2212
+ console.log(` ${info} Set up hooks, Claude integration, and CI checks`);
2213
+ console.log(
2214
+ `
2215
+ ${chalk9.dim("Defaults warn locally. Use --enforce in CI when you want failures to block.")}`
2216
+ );
2217
+ console.log("");
2218
+ }
2219
+ function summarizeSelectedIntegrations(integrations, opts) {
2220
+ const lines = [];
2221
+ if (opts.hasBoundaries) {
2222
+ lines.push("\u2713 Boundary rules: inferred from current imports");
2223
+ } else {
2224
+ lines.push("~ Boundary rules: not enabled");
2225
+ }
2226
+ if (opts.hasCoverage) {
2227
+ lines.push("\u2713 Coverage checks: enabled");
2228
+ } else {
2229
+ lines.push("~ Coverage checks: disabled");
2230
+ }
2231
+ const selectedIntegrations = [
2232
+ integrations.preCommitHook ? "pre-commit hook" : void 0,
2233
+ integrations.typecheckHook ? "typecheck" : void 0,
2234
+ integrations.lintHook ? "lint check" : void 0,
2235
+ integrations.claudeCodeHook ? "Claude Code hook" : void 0,
2236
+ integrations.claudeMdRef ? "CLAUDE.md reference" : void 0,
2237
+ integrations.githubAction ? "GitHub Actions workflow" : void 0
2238
+ ].filter(Boolean);
2239
+ if (selectedIntegrations.length > 0) {
2240
+ lines.push(`\u2713 Integrations: ${selectedIntegrations.join(" \xB7 ")}`);
2241
+ } else {
2242
+ lines.push("~ Integrations: none selected");
2243
+ }
2244
+ return lines;
2245
+ }
2246
+ function displaySetupPlan(config, integrations, opts = {}) {
2247
+ const configFile = opts.configFile ?? "viberails.config.json";
2248
+ const lines = summarizeSelectedIntegrations(integrations, {
2249
+ hasBoundaries: config.rules.enforceBoundaries,
2250
+ hasCoverage: config.rules.testCoverage > 0
2251
+ });
2252
+ console.log("");
2253
+ console.log(` ${chalk9.bold("Ready to write:")}`);
2254
+ console.log(
2255
+ ` ${opts.replacingExistingConfig ? chalk9.yellow("!") : chalk9.green("\u2713")} ${configFile}${opts.replacingExistingConfig ? chalk9.dim(" (replacing existing config)") : ""}`
2256
+ );
2257
+ console.log(` ${chalk9.green("\u2713")} .viberails/context.md`);
2258
+ console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json`);
2259
+ for (const line of lines) {
2260
+ const icon = line.startsWith("\u2713") ? chalk9.green("\u2713") : chalk9.yellow("~");
2261
+ console.log(` ${icon} ${line.slice(2)}`);
2262
+ }
2263
+ console.log("");
2264
+ }
2808
2265
 
2809
2266
  // src/utils/check-prerequisites.ts
2810
2267
  import * as fs14 from "fs";
2811
2268
  import * as path14 from "path";
2812
- import * as clack7 from "@clack/prompts";
2813
- import chalk9 from "chalk";
2269
+ import * as clack2 from "@clack/prompts";
2270
+ import chalk10 from "chalk";
2814
2271
  function checkCoveragePrereqs(projectRoot, scanResult) {
2815
2272
  const pm = scanResult.stack.packageManager.name;
2816
2273
  const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
@@ -2841,9 +2298,9 @@ function displayMissingPrereqs(prereqs) {
2841
2298
  const missing = prereqs.filter((p) => !p.installed);
2842
2299
  for (const m of missing) {
2843
2300
  const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2844
- console.log(` ${chalk9.yellow("!")} ${m.label} not installed${suffix}`);
2301
+ console.log(` ${chalk10.yellow("!")} ${m.label} not installed${suffix}`);
2845
2302
  if (m.installCommand) {
2846
- console.log(` Install: ${chalk9.cyan(m.installCommand)}`);
2303
+ console.log(` Install: ${chalk10.cyan(m.installCommand)}`);
2847
2304
  }
2848
2305
  }
2849
2306
  }
@@ -2855,13 +2312,13 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
2855
2312
  const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
2856
2313
  return `\u2717 ${p.label} \u2014 ${detail}`;
2857
2314
  }).join("\n");
2858
- clack7.note(prereqLines, "Coverage support");
2315
+ clack2.note(prereqLines, "Coverage support");
2859
2316
  let disableCoverage = false;
2860
2317
  for (const m of missing) {
2861
2318
  if (!m.installCommand) continue;
2862
2319
  const pkgCount = m.affectedPackages?.length;
2863
2320
  const message = pkgCount ? `${m.label} is not installed. Required for coverage in ${pkgCount} packages using vitest.` : `${m.label} is not installed. It is required for coverage percentage checks.`;
2864
- const choice = await clack7.select({
2321
+ const choice = await clack2.select({
2865
2322
  message,
2866
2323
  options: [
2867
2324
  {
@@ -2883,23 +2340,23 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
2883
2340
  });
2884
2341
  assertNotCancelled(choice);
2885
2342
  if (choice === "install") {
2886
- const is = clack7.spinner();
2343
+ const is = clack2.spinner();
2887
2344
  is.start(`Installing ${m.label}...`);
2888
2345
  const result = await spawnAsync(m.installCommand, projectRoot);
2889
2346
  if (result.status === 0) {
2890
2347
  is.stop(`Installed ${m.label}`);
2891
2348
  } else {
2892
2349
  is.stop(`Failed to install ${m.label}`);
2893
- clack7.log.warn(
2350
+ clack2.log.warn(
2894
2351
  `Install manually: ${m.installCommand}
2895
2352
  Coverage percentage checks will not work until the dependency is installed.`
2896
2353
  );
2897
2354
  }
2898
2355
  } else if (choice === "disable") {
2899
2356
  disableCoverage = true;
2900
- clack7.log.info("Coverage percentage checks disabled. Missing-test checks remain active.");
2357
+ clack2.log.info("Coverage percentage checks disabled. Missing-test checks remain active.");
2901
2358
  } else {
2902
- clack7.log.info(
2359
+ clack2.log.info(
2903
2360
  `Coverage percentage checks will fail until ${m.label} is installed.
2904
2361
  Install later: ${m.installCommand}`
2905
2362
  );
@@ -2917,20 +2374,6 @@ function hasDependency(projectRoot, name) {
2917
2374
  }
2918
2375
  }
2919
2376
 
2920
- // src/utils/filter-confidence.ts
2921
- function filterHighConfidence(conventions, meta) {
2922
- if (!meta) return conventions;
2923
- const filtered = {};
2924
- for (const [key, value] of Object.entries(conventions)) {
2925
- if (value === void 0) continue;
2926
- const convMeta = meta[key];
2927
- if (!convMeta || convMeta.confidence === "high") {
2928
- filtered[key] = value;
2929
- }
2930
- }
2931
- return filtered;
2932
- }
2933
-
2934
2377
  // src/utils/update-gitignore.ts
2935
2378
  import * as fs15 from "fs";
2936
2379
  import * as path15 from "path";
@@ -2951,7 +2394,7 @@ function updateGitignore(projectRoot) {
2951
2394
  // src/commands/init-hooks.ts
2952
2395
  import * as fs17 from "fs";
2953
2396
  import * as path17 from "path";
2954
- import chalk10 from "chalk";
2397
+ import chalk11 from "chalk";
2955
2398
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2956
2399
 
2957
2400
  // src/commands/resolve-typecheck.ts
@@ -2996,13 +2439,13 @@ function setupPreCommitHook(projectRoot) {
2996
2439
  const lefthookPath = path17.join(projectRoot, "lefthook.yml");
2997
2440
  if (fs17.existsSync(lefthookPath)) {
2998
2441
  addLefthookPreCommit(lefthookPath);
2999
- console.log(` ${chalk10.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
2442
+ console.log(` ${chalk11.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
3000
2443
  return "lefthook.yml";
3001
2444
  }
3002
2445
  const huskyDir = path17.join(projectRoot, ".husky");
3003
2446
  if (fs17.existsSync(huskyDir)) {
3004
2447
  writeHuskyPreCommit(huskyDir);
3005
- console.log(` ${chalk10.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
2448
+ console.log(` ${chalk11.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
3006
2449
  return ".husky/pre-commit";
3007
2450
  }
3008
2451
  const gitDir = path17.join(projectRoot, ".git");
@@ -3012,7 +2455,7 @@ function setupPreCommitHook(projectRoot) {
3012
2455
  fs17.mkdirSync(hooksDir, { recursive: true });
3013
2456
  }
3014
2457
  writeGitHookPreCommit(hooksDir);
3015
- console.log(` ${chalk10.green("\u2713")} .git/hooks/pre-commit`);
2458
+ console.log(` ${chalk11.green("\u2713")} .git/hooks/pre-commit`);
3016
2459
  return ".git/hooks/pre-commit";
3017
2460
  }
3018
2461
  return void 0;
@@ -3073,9 +2516,9 @@ function setupClaudeCodeHook(projectRoot) {
3073
2516
  settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
3074
2517
  } catch {
3075
2518
  console.warn(
3076
- ` ${chalk10.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
2519
+ ` ${chalk11.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
3077
2520
  );
3078
- console.warn(` Fix the JSON manually, then re-run ${chalk10.cyan("viberails init --force")}`);
2521
+ console.warn(` Fix the JSON manually, then re-run ${chalk11.cyan("viberails init --force")}`);
3079
2522
  return;
3080
2523
  }
3081
2524
  }
@@ -3098,7 +2541,7 @@ function setupClaudeCodeHook(projectRoot) {
3098
2541
  settings.hooks = hooks;
3099
2542
  fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
3100
2543
  `);
3101
- console.log(` ${chalk10.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
2544
+ console.log(` ${chalk11.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
3102
2545
  }
3103
2546
  function setupClaudeMdReference(projectRoot) {
3104
2547
  const claudeMdPath = path17.join(projectRoot, "CLAUDE.md");
@@ -3110,7 +2553,7 @@ function setupClaudeMdReference(projectRoot) {
3110
2553
  const ref = "\n@.viberails/context.md\n";
3111
2554
  const prefix = content.length === 0 ? "" : content.trimEnd();
3112
2555
  fs17.writeFileSync(claudeMdPath, prefix + ref);
3113
- console.log(` ${chalk10.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2556
+ console.log(` ${chalk11.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
3114
2557
  }
3115
2558
  function setupGithubAction(projectRoot, packageManager, options) {
3116
2559
  const workflowDir = path17.join(projectRoot, ".github", "workflows");
@@ -3196,7 +2639,7 @@ ${cmd}
3196
2639
  // src/commands/init-hooks-extra.ts
3197
2640
  import * as fs18 from "fs";
3198
2641
  import * as path18 from "path";
3199
- import chalk11 from "chalk";
2642
+ import chalk12 from "chalk";
3200
2643
  import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
3201
2644
  function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
3202
2645
  const lefthookPath = path18.join(projectRoot, "lefthook.yml");
@@ -3256,12 +2699,12 @@ ${command}
3256
2699
  function setupTypecheckHook(projectRoot, packageManager) {
3257
2700
  const resolved = resolveTypecheckCommand(projectRoot, packageManager);
3258
2701
  if (!resolved.command) {
3259
- console.log(` ${chalk11.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
2702
+ console.log(` ${chalk12.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
3260
2703
  return void 0;
3261
2704
  }
3262
2705
  const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
3263
2706
  if (target) {
3264
- console.log(` ${chalk11.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
2707
+ console.log(` ${chalk12.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
3265
2708
  }
3266
2709
  return target;
3267
2710
  }
@@ -3282,7 +2725,7 @@ function setupLintHook(projectRoot, linter) {
3282
2725
  }
3283
2726
  const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
3284
2727
  if (target) {
3285
- console.log(` ${chalk11.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
2728
+ console.log(` ${chalk12.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
3286
2729
  }
3287
2730
  return target;
3288
2731
  }
@@ -3318,34 +2761,34 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
3318
2761
  return created;
3319
2762
  }
3320
2763
 
3321
- // src/commands/init.ts
3322
- var CONFIG_FILE5 = "viberails.config.json";
3323
- function getExemptedPackages(config) {
3324
- return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3325
- }
3326
- async function initCommand(options, cwd) {
3327
- const projectRoot = findProjectRoot(cwd ?? process.cwd());
3328
- if (!projectRoot) {
3329
- throw new Error(
3330
- "No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
3331
- );
3332
- }
3333
- const configPath = path19.join(projectRoot, CONFIG_FILE5);
3334
- if (fs19.existsSync(configPath) && !options.force) {
3335
- if (!options.yes) {
3336
- return initInteractive(projectRoot, configPath, options);
2764
+ // src/commands/init-non-interactive.ts
2765
+ import * as fs19 from "fs";
2766
+ import * as path19 from "path";
2767
+ import * as clack3 from "@clack/prompts";
2768
+ import { compactConfig as compactConfig3, generateConfig } from "@viberails/config";
2769
+ import { scan as scan2 } from "@viberails/scanner";
2770
+ import chalk13 from "chalk";
2771
+
2772
+ // src/utils/filter-confidence.ts
2773
+ function filterHighConfidence(conventions, meta) {
2774
+ if (!meta) return conventions;
2775
+ const filtered = {};
2776
+ for (const [key, value] of Object.entries(conventions)) {
2777
+ if (value === void 0) continue;
2778
+ const convMeta = meta[key];
2779
+ if (!convMeta || convMeta.confidence === "high") {
2780
+ filtered[key] = value;
3337
2781
  }
3338
- console.log(
3339
- `${chalk12.yellow("!")} viberails is already initialized.
3340
- Run ${chalk12.cyan("viberails")} to review or edit the existing setup, ${chalk12.cyan("viberails sync")} to update generated files, or ${chalk12.cyan("viberails init --force")} to replace it.`
3341
- );
3342
- return;
3343
2782
  }
3344
- if (options.yes) return initNonInteractive(projectRoot, configPath);
3345
- await initInteractive(projectRoot, configPath, options);
2783
+ return filtered;
2784
+ }
2785
+
2786
+ // src/commands/init-non-interactive.ts
2787
+ function getExemptedPackages(config) {
2788
+ return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
3346
2789
  }
3347
2790
  async function initNonInteractive(projectRoot, configPath) {
3348
- const s = clack8.spinner();
2791
+ const s = clack3.spinner();
3349
2792
  s.start("Scanning project...");
3350
2793
  const scanResult = await scan2(projectRoot);
3351
2794
  const config = generateConfig(scanResult);
@@ -3360,11 +2803,11 @@ async function initNonInteractive(projectRoot, configPath) {
3360
2803
  const exempted = getExemptedPackages(config);
3361
2804
  if (exempted.length > 0) {
3362
2805
  console.log(
3363
- ` ${chalk12.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk12.dim("(types-only)")}`
2806
+ ` ${chalk13.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${chalk13.dim("(types-only)")}`
3364
2807
  );
3365
2808
  }
3366
2809
  if (config.packages.length > 1) {
3367
- const bs = clack8.spinner();
2810
+ const bs = clack3.spinner();
3368
2811
  bs.start("Building import graph...");
3369
2812
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3370
2813
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3397,14 +2840,14 @@ async function initNonInteractive(projectRoot, configPath) {
3397
2840
  const hookManager = detectHookManager(projectRoot);
3398
2841
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
3399
2842
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
3400
- const ok = chalk12.green("\u2713");
2843
+ const ok = chalk13.green("\u2713");
3401
2844
  const created = [
3402
2845
  `${ok} ${path19.basename(configPath)}`,
3403
2846
  `${ok} .viberails/context.md`,
3404
2847
  `${ok} .viberails/scan-result.json`,
3405
2848
  `${ok} .claude/settings.json \u2014 added viberails hook`,
3406
2849
  `${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
3407
- preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk12.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
2850
+ preCommitTarget ? `${ok} ${preCommitTarget}` : `${chalk13.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
3408
2851
  actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
3409
2852
  ].filter(Boolean);
3410
2853
  if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
@@ -3413,13 +2856,40 @@ async function initNonInteractive(projectRoot, configPath) {
3413
2856
  Created:
3414
2857
  ${created.map((f) => ` ${f}`).join("\n")}`);
3415
2858
  }
2859
+
2860
+ // src/commands/init.ts
2861
+ var CONFIG_FILE5 = "viberails.config.json";
2862
+ function getExemptedPackages2(config) {
2863
+ return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
2864
+ }
2865
+ async function initCommand(options, cwd) {
2866
+ const projectRoot = findProjectRoot(cwd ?? process.cwd());
2867
+ if (!projectRoot) {
2868
+ throw new Error(
2869
+ "No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
2870
+ );
2871
+ }
2872
+ const configPath = path20.join(projectRoot, CONFIG_FILE5);
2873
+ if (fs20.existsSync(configPath) && !options.force) {
2874
+ if (!options.yes) {
2875
+ return initInteractive(projectRoot, configPath, options);
2876
+ }
2877
+ console.log(
2878
+ `${chalk14.yellow("!")} viberails is already initialized.
2879
+ Run ${chalk14.cyan("viberails")} to review or edit the existing setup, ${chalk14.cyan("viberails sync")} to update generated files, or ${chalk14.cyan("viberails init --force")} to replace it.`
2880
+ );
2881
+ return;
2882
+ }
2883
+ if (options.yes) return initNonInteractive(projectRoot, configPath);
2884
+ await initInteractive(projectRoot, configPath, options);
2885
+ }
3416
2886
  async function initInteractive(projectRoot, configPath, options) {
3417
- clack8.intro("viberails");
3418
- const replacingExistingConfig = fs19.existsSync(configPath);
3419
- if (fs19.existsSync(configPath) && !options.force) {
3420
- const action = await promptExistingConfigAction(path19.basename(configPath));
2887
+ clack4.intro("viberails");
2888
+ const replacingExistingConfig = fs20.existsSync(configPath);
2889
+ if (fs20.existsSync(configPath) && !options.force) {
2890
+ const action = await promptExistingConfigAction(path20.basename(configPath));
3421
2891
  if (action === "cancel") {
3422
- clack8.outro("Aborted. No files were written.");
2892
+ clack4.outro("Aborted. No files were written.");
3423
2893
  return;
3424
2894
  }
3425
2895
  if (action === "edit") {
@@ -3428,45 +2898,51 @@ async function initInteractive(projectRoot, configPath, options) {
3428
2898
  }
3429
2899
  options.force = true;
3430
2900
  }
3431
- if (fs19.existsSync(configPath) && options.force) {
2901
+ if (fs20.existsSync(configPath) && options.force) {
3432
2902
  const replace = await confirmDangerous(
3433
- `${path19.basename(configPath)} already exists and will be replaced. Continue?`
2903
+ `${path20.basename(configPath)} already exists and will be replaced. Continue?`
3434
2904
  );
3435
2905
  if (!replace) {
3436
- clack8.outro("Aborted. No files were written.");
2906
+ clack4.outro("Aborted. No files were written.");
3437
2907
  return;
3438
2908
  }
3439
2909
  }
3440
- const s = clack8.spinner();
2910
+ const s = clack4.spinner();
3441
2911
  s.start("Scanning project...");
3442
- const scanResult = await scan2(projectRoot);
3443
- const config = generateConfig(scanResult);
2912
+ const scanResult = await scan3(projectRoot);
2913
+ const config = generateConfig2(scanResult);
3444
2914
  s.stop("Scan complete");
3445
2915
  if (scanResult.statistics.totalFiles === 0) {
3446
- clack8.log.warn(
2916
+ clack4.log.warn(
3447
2917
  "No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
3448
2918
  );
3449
2919
  }
3450
- const exemptedPkgs = getExemptedPackages(config);
2920
+ const exemptedPkgs = getExemptedPackages2(config);
3451
2921
  let decision;
3452
2922
  while (true) {
3453
2923
  displayInitOverview(scanResult, config, exemptedPkgs);
3454
2924
  const nextDecision = await promptInitDecision();
3455
2925
  if (nextDecision === "review") {
3456
- clack8.note(formatScanResultsText(scanResult), "Detected details");
2926
+ clack4.note(formatScanResultsText(scanResult), "Detected details");
3457
2927
  continue;
3458
2928
  }
3459
2929
  decision = nextDecision;
3460
2930
  break;
3461
2931
  }
3462
2932
  if (decision === "customize") {
2933
+ const { resolveNamingDefault } = await import("./prompt-naming-default-AH54HEBC.js");
2934
+ await resolveNamingDefault(config, scanResult);
3463
2935
  const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
3464
2936
  const overrides = await promptRuleMenu({
3465
2937
  maxFileLines: config.rules.maxFileLines,
2938
+ maxTestFileLines: config.rules.maxTestFileLines,
3466
2939
  testCoverage: config.rules.testCoverage,
3467
2940
  enforceMissingTests: config.rules.enforceMissingTests,
3468
2941
  enforceNaming: config.rules.enforceNaming,
3469
2942
  fileNamingValue: rootPkg.conventions?.fileNaming,
2943
+ componentNaming: rootPkg.conventions?.componentNaming,
2944
+ hookNaming: rootPkg.conventions?.hookNaming,
2945
+ importAlias: rootPkg.conventions?.importAlias,
3470
2946
  coverageSummaryPath: "coverage/coverage-summary.json",
3471
2947
  coverageCommand: config.defaults?.coverage?.command,
3472
2948
  packageOverrides: config.packages
@@ -3474,13 +2950,13 @@ async function initInteractive(projectRoot, configPath, options) {
3474
2950
  applyRuleOverrides(config, overrides);
3475
2951
  }
3476
2952
  if (config.packages.length > 1) {
3477
- clack8.note(
2953
+ clack4.note(
3478
2954
  "Optional for monorepos. viberails can infer package boundaries\nfrom imports that already work today, so you start with rules\nthat match the current codebase.",
3479
2955
  "Boundaries"
3480
2956
  );
3481
- const shouldInfer = await confirm3("Infer boundary rules from current import patterns?");
2957
+ const shouldInfer = await confirm("Infer boundary rules from current import patterns?");
3482
2958
  if (shouldInfer) {
3483
- const bs = clack8.spinner();
2959
+ const bs = clack4.spinner();
3484
2960
  bs.start("Building import graph...");
3485
2961
  const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
3486
2962
  const packages = resolveWorkspacePackages(projectRoot, config.packages);
@@ -3501,7 +2977,7 @@ async function initInteractive(projectRoot, configPath, options) {
3501
2977
  const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
3502
2978
  const hasMissingPrereqs = coveragePrereqs.some((p) => !p.installed) || !hookManager;
3503
2979
  if (hasMissingPrereqs) {
3504
- clack8.log.info("Some dependencies are needed for full functionality.");
2980
+ clack4.log.info("Some dependencies are needed for full functionality.");
3505
2981
  }
3506
2982
  const prereqResult = await promptMissingPrereqs(projectRoot, coveragePrereqs);
3507
2983
  if (prereqResult.disableCoverage) {
@@ -3516,48 +2992,48 @@ async function initInteractive(projectRoot, configPath, options) {
3516
2992
  });
3517
2993
  displaySetupPlan(config, integrations, {
3518
2994
  replacingExistingConfig,
3519
- configFile: path19.basename(configPath)
2995
+ configFile: path20.basename(configPath)
3520
2996
  });
3521
- const shouldWrite = await confirm3("Apply this setup?");
2997
+ const shouldWrite = await confirm("Apply this setup?");
3522
2998
  if (!shouldWrite) {
3523
- clack8.outro("Aborted. No files were written.");
2999
+ clack4.outro("Aborted. No files were written.");
3524
3000
  return;
3525
3001
  }
3526
- const ws = clack8.spinner();
3002
+ const ws = clack4.spinner();
3527
3003
  ws.start("Writing configuration...");
3528
- const compacted = compactConfig3(config);
3529
- fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3004
+ const compacted = compactConfig4(config);
3005
+ fs20.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
3530
3006
  `);
3531
3007
  writeGeneratedFiles(projectRoot, config, scanResult);
3532
3008
  updateGitignore(projectRoot);
3533
3009
  ws.stop("Configuration written");
3534
- const ok = chalk12.green("\u2713");
3535
- clack8.log.step(`${ok} ${path19.basename(configPath)}`);
3536
- clack8.log.step(`${ok} .viberails/context.md`);
3537
- clack8.log.step(`${ok} .viberails/scan-result.json`);
3010
+ const ok = chalk14.green("\u2713");
3011
+ clack4.log.step(`${ok} ${path20.basename(configPath)}`);
3012
+ clack4.log.step(`${ok} .viberails/context.md`);
3013
+ clack4.log.step(`${ok} .viberails/scan-result.json`);
3538
3014
  setupSelectedIntegrations(projectRoot, integrations, {
3539
3015
  linter: rootPkgStack?.linter?.split("@")[0],
3540
3016
  packageManager: rootPkgStack?.packageManager?.split("@")[0]
3541
3017
  });
3542
- clack8.outro(
3018
+ clack4.outro(
3543
3019
  `Done! Next: review viberails.config.json, then run viberails check
3544
- ${chalk12.dim("Tip: use")} ${chalk12.cyan("viberails check --enforce")} ${chalk12.dim("in CI to block PRs on violations.")}`
3020
+ ${chalk14.dim("Tip: use")} ${chalk14.cyan("viberails check --enforce")} ${chalk14.dim("in CI to block PRs on violations.")}`
3545
3021
  );
3546
3022
  }
3547
3023
 
3548
3024
  // src/commands/sync.ts
3549
- import * as fs20 from "fs";
3550
- import * as path20 from "path";
3551
- import * as clack9 from "@clack/prompts";
3552
- import { compactConfig as compactConfig4, loadConfig as loadConfig5, mergeConfig as mergeConfig2 } from "@viberails/config";
3553
- import { scan as scan3 } from "@viberails/scanner";
3554
- import chalk13 from "chalk";
3025
+ import * as fs21 from "fs";
3026
+ import * as path21 from "path";
3027
+ import * as clack5 from "@clack/prompts";
3028
+ import { compactConfig as compactConfig5, loadConfig as loadConfig5, mergeConfig as mergeConfig2 } from "@viberails/config";
3029
+ import { scan as scan4 } from "@viberails/scanner";
3030
+ import chalk15 from "chalk";
3555
3031
  var CONFIG_FILE6 = "viberails.config.json";
3556
3032
  var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
3557
3033
  function loadPreviousStats(projectRoot) {
3558
- const scanResultPath = path20.join(projectRoot, SCAN_RESULT_FILE2);
3034
+ const scanResultPath = path21.join(projectRoot, SCAN_RESULT_FILE2);
3559
3035
  try {
3560
- const raw = fs20.readFileSync(scanResultPath, "utf-8");
3036
+ const raw = fs21.readFileSync(scanResultPath, "utf-8");
3561
3037
  const parsed = JSON.parse(raw);
3562
3038
  if (parsed?.statistics?.totalFiles !== void 0) {
3563
3039
  return parsed.statistics;
@@ -3574,17 +3050,17 @@ async function syncCommand(options, cwd) {
3574
3050
  "No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
3575
3051
  );
3576
3052
  }
3577
- const configPath = path20.join(projectRoot, CONFIG_FILE6);
3053
+ const configPath = path21.join(projectRoot, CONFIG_FILE6);
3578
3054
  const existing = await loadConfig5(configPath);
3579
3055
  const previousStats = loadPreviousStats(projectRoot);
3580
- const s = clack9.spinner();
3056
+ const s = clack5.spinner();
3581
3057
  s.start("Scanning project...");
3582
- const scanResult = await scan3(projectRoot);
3058
+ const scanResult = await scan4(projectRoot);
3583
3059
  s.stop("Scan complete");
3584
3060
  const merged = mergeConfig2(existing, scanResult);
3585
- const compacted = compactConfig4(merged);
3061
+ const compacted = compactConfig5(merged);
3586
3062
  const compactedJson = JSON.stringify(compacted, null, 2);
3587
- const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
3063
+ const rawDisk = fs21.readFileSync(configPath, "utf-8").trim();
3588
3064
  const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3589
3065
  const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
3590
3066
  const configChanged = diskWithoutSync !== mergedWithoutSync;
@@ -3592,19 +3068,19 @@ async function syncCommand(options, cwd) {
3592
3068
  const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
3593
3069
  if (changes.length > 0 || statsDelta) {
3594
3070
  console.log(`
3595
- ${chalk13.bold("Changes:")}`);
3071
+ ${chalk15.bold("Changes:")}`);
3596
3072
  for (const change of changes) {
3597
- const icon = change.type === "removed" ? chalk13.red("-") : chalk13.green("+");
3073
+ const icon = change.type === "removed" ? chalk15.red("-") : chalk15.green("+");
3598
3074
  console.log(` ${icon} ${change.description}`);
3599
3075
  }
3600
3076
  if (statsDelta) {
3601
- console.log(` ${chalk13.dim(statsDelta)}`);
3077
+ console.log(` ${chalk15.dim(statsDelta)}`);
3602
3078
  }
3603
3079
  }
3604
3080
  if (options?.interactive) {
3605
- clack9.intro("viberails sync (interactive)");
3606
- clack9.note(formatRulesText(merged).join("\n"), "Rules after sync");
3607
- const decision = await clack9.select({
3081
+ clack5.intro("viberails sync (interactive)");
3082
+ clack5.note(formatRulesText(merged).join("\n"), "Rules after sync");
3083
+ const decision = await clack5.select({
3608
3084
  message: "How would you like to proceed?",
3609
3085
  options: [
3610
3086
  { value: "accept", label: "Accept changes" },
@@ -3614,47 +3090,51 @@ ${chalk13.bold("Changes:")}`);
3614
3090
  });
3615
3091
  assertNotCancelled(decision);
3616
3092
  if (decision === "cancel") {
3617
- clack9.outro("Sync cancelled. No files were written.");
3093
+ clack5.outro("Sync cancelled. No files were written.");
3618
3094
  return;
3619
3095
  }
3620
3096
  if (decision === "customize") {
3621
3097
  const rootPkg = merged.packages.find((p) => p.path === ".") ?? merged.packages[0];
3622
3098
  const overrides = await promptRuleMenu({
3623
3099
  maxFileLines: merged.rules.maxFileLines,
3100
+ maxTestFileLines: merged.rules.maxTestFileLines,
3624
3101
  testCoverage: merged.rules.testCoverage,
3625
3102
  enforceMissingTests: merged.rules.enforceMissingTests,
3626
3103
  enforceNaming: merged.rules.enforceNaming,
3627
3104
  fileNamingValue: rootPkg.conventions?.fileNaming,
3105
+ componentNaming: rootPkg.conventions?.componentNaming,
3106
+ hookNaming: rootPkg.conventions?.hookNaming,
3107
+ importAlias: rootPkg.conventions?.importAlias,
3628
3108
  coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
3629
3109
  coverageCommand: merged.defaults?.coverage?.command,
3630
3110
  packageOverrides: merged.packages
3631
3111
  });
3632
3112
  applyRuleOverrides(merged, overrides);
3633
- const recompacted = compactConfig4(merged);
3634
- fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3113
+ const recompacted = compactConfig5(merged);
3114
+ fs21.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
3635
3115
  `);
3636
3116
  writeGeneratedFiles(projectRoot, merged, scanResult);
3637
- clack9.log.success("Updated config with your customizations.");
3638
- clack9.outro("Done! Run viberails check to verify.");
3117
+ clack5.log.success("Updated config with your customizations.");
3118
+ clack5.outro("Done! Run viberails check to verify.");
3639
3119
  return;
3640
3120
  }
3641
3121
  }
3642
- fs20.writeFileSync(configPath, `${compactedJson}
3122
+ fs21.writeFileSync(configPath, `${compactedJson}
3643
3123
  `);
3644
3124
  writeGeneratedFiles(projectRoot, merged, scanResult);
3645
3125
  console.log(`
3646
- ${chalk13.bold("Synced:")}`);
3126
+ ${chalk15.bold("Synced:")}`);
3647
3127
  if (configChanged) {
3648
- console.log(` ${chalk13.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3128
+ console.log(` ${chalk15.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
3649
3129
  } else {
3650
- console.log(` ${chalk13.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3130
+ console.log(` ${chalk15.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
3651
3131
  }
3652
- console.log(` ${chalk13.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3653
- console.log(` ${chalk13.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3132
+ console.log(` ${chalk15.green("\u2713")} .viberails/context.md \u2014 regenerated`);
3133
+ console.log(` ${chalk15.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
3654
3134
  }
3655
3135
 
3656
3136
  // src/index.ts
3657
- var VERSION = "0.6.4";
3137
+ var VERSION = "0.6.5";
3658
3138
  var program = new Command();
3659
3139
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3660
3140
  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) => {
@@ -3662,7 +3142,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
3662
3142
  await initCommand(options);
3663
3143
  } catch (err) {
3664
3144
  const message = err instanceof Error ? err.message : String(err);
3665
- console.error(`${chalk14.red("Error:")} ${message}`);
3145
+ console.error(`${chalk16.red("Error:")} ${message}`);
3666
3146
  process.exit(1);
3667
3147
  }
3668
3148
  });
@@ -3671,7 +3151,7 @@ program.command("sync").description("Re-scan and update generated files").option
3671
3151
  await syncCommand(options);
3672
3152
  } catch (err) {
3673
3153
  const message = err instanceof Error ? err.message : String(err);
3674
- console.error(`${chalk14.red("Error:")} ${message}`);
3154
+ console.error(`${chalk16.red("Error:")} ${message}`);
3675
3155
  process.exit(1);
3676
3156
  }
3677
3157
  });
@@ -3680,7 +3160,7 @@ program.command("config").description("Interactively edit existing config rules"
3680
3160
  await configCommand(options);
3681
3161
  } catch (err) {
3682
3162
  const message = err instanceof Error ? err.message : String(err);
3683
- console.error(`${chalk14.red("Error:")} ${message}`);
3163
+ console.error(`${chalk16.red("Error:")} ${message}`);
3684
3164
  process.exit(1);
3685
3165
  }
3686
3166
  });
@@ -3701,7 +3181,7 @@ program.command("check").description("Check files against enforced rules").optio
3701
3181
  process.exit(exitCode);
3702
3182
  } catch (err) {
3703
3183
  const message = err instanceof Error ? err.message : String(err);
3704
- console.error(`${chalk14.red("Error:")} ${message}`);
3184
+ console.error(`${chalk16.red("Error:")} ${message}`);
3705
3185
  process.exit(1);
3706
3186
  }
3707
3187
  }
@@ -3712,7 +3192,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
3712
3192
  process.exit(exitCode);
3713
3193
  } catch (err) {
3714
3194
  const message = err instanceof Error ? err.message : String(err);
3715
- console.error(`${chalk14.red("Error:")} ${message}`);
3195
+ console.error(`${chalk16.red("Error:")} ${message}`);
3716
3196
  process.exit(1);
3717
3197
  }
3718
3198
  });
@@ -3721,7 +3201,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
3721
3201
  await boundariesCommand(options);
3722
3202
  } catch (err) {
3723
3203
  const message = err instanceof Error ? err.message : String(err);
3724
- console.error(`${chalk14.red("Error:")} ${message}`);
3204
+ console.error(`${chalk16.red("Error:")} ${message}`);
3725
3205
  process.exit(1);
3726
3206
  }
3727
3207
  });