viberails 0.3.0 → 0.3.1
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 +498 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +498 -381
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Command } from "commander";
|
|
|
8
8
|
import * as fs3 from "fs";
|
|
9
9
|
import * as path3 from "path";
|
|
10
10
|
import { loadConfig } from "@viberails/config";
|
|
11
|
-
import
|
|
11
|
+
import chalk2 from "chalk";
|
|
12
12
|
|
|
13
13
|
// src/utils/find-project-root.ts
|
|
14
14
|
import * as fs from "fs";
|
|
@@ -29,6 +29,7 @@ function findProjectRoot(startDir) {
|
|
|
29
29
|
|
|
30
30
|
// src/utils/prompt.ts
|
|
31
31
|
import * as readline from "readline";
|
|
32
|
+
import chalk from "chalk";
|
|
32
33
|
async function confirm(message) {
|
|
33
34
|
const rl = readline.createInterface({
|
|
34
35
|
input: process.stdin,
|
|
@@ -42,6 +43,29 @@ async function confirm(message) {
|
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
}
|
|
46
|
+
async function selectIntegrations(hookManager) {
|
|
47
|
+
const result = { preCommitHook: true, claudeCodeHook: true };
|
|
48
|
+
const hookLabel = hookManager ? `Pre-commit hook (detected: ${hookManager})` : "Pre-commit hook (git hook)";
|
|
49
|
+
console.log("Set up integrations:");
|
|
50
|
+
const rl = readline.createInterface({
|
|
51
|
+
input: process.stdin,
|
|
52
|
+
output: process.stdout
|
|
53
|
+
});
|
|
54
|
+
const askYn = (label, defaultYes) => new Promise((resolve4) => {
|
|
55
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
56
|
+
rl.question(` ${label}? (${hint}) `, (answer) => {
|
|
57
|
+
const trimmed = answer.trim().toLowerCase();
|
|
58
|
+
if (trimmed === "") resolve4(defaultYes);
|
|
59
|
+
else resolve4(trimmed === "y" || trimmed === "yes");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
console.log(` ${chalk.dim("Runs viberails check automatically when you commit")}`);
|
|
63
|
+
result.preCommitHook = await askYn(hookLabel, true);
|
|
64
|
+
console.log(` ${chalk.dim("Checks files against your rules when Claude edits them")}`);
|
|
65
|
+
result.claudeCodeHook = await askYn("Claude Code hook", true);
|
|
66
|
+
rl.close();
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
45
69
|
|
|
46
70
|
// src/utils/resolve-workspace-packages.ts
|
|
47
71
|
import * as fs2 from "fs";
|
|
@@ -66,7 +90,7 @@ function resolveWorkspacePackages(projectRoot, workspace) {
|
|
|
66
90
|
];
|
|
67
91
|
packages.push({ name, path: absPath, relativePath, internalDeps: allDeps });
|
|
68
92
|
}
|
|
69
|
-
const packageNames = new Set(packages.map((
|
|
93
|
+
const packageNames = new Set(packages.map((p) => p.name));
|
|
70
94
|
for (const pkg of packages) {
|
|
71
95
|
pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));
|
|
72
96
|
}
|
|
@@ -96,80 +120,68 @@ async function boundariesCommand(options, cwd) {
|
|
|
96
120
|
}
|
|
97
121
|
displayRules(config);
|
|
98
122
|
}
|
|
99
|
-
function countBoundaries(boundaries) {
|
|
100
|
-
if (!boundaries) return 0;
|
|
101
|
-
if (Array.isArray(boundaries)) return boundaries.length;
|
|
102
|
-
return Object.values(boundaries).reduce((sum, denied) => sum + denied.length, 0);
|
|
103
|
-
}
|
|
104
123
|
function displayRules(config) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.log(
|
|
108
|
-
console.log(`Run ${chalk.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
124
|
+
if (!config.boundaries || config.boundaries.length === 0) {
|
|
125
|
+
console.log(chalk2.yellow("No boundary rules configured."));
|
|
126
|
+
console.log(`Run ${chalk2.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
109
127
|
return;
|
|
110
128
|
}
|
|
129
|
+
const allowRules = config.boundaries.filter((r) => r.allow);
|
|
130
|
+
const denyRules = config.boundaries.filter((r) => !r.allow);
|
|
111
131
|
console.log(`
|
|
112
|
-
${
|
|
132
|
+
${chalk2.bold(`Boundary rules (${config.boundaries.length} rules):`)}
|
|
113
133
|
`);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
for (const r of denyRules) {
|
|
121
|
-
const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
|
|
122
|
-
console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
123
|
-
}
|
|
124
|
-
} else if (config.boundaries) {
|
|
125
|
-
for (const [from, denied] of Object.entries(config.boundaries)) {
|
|
126
|
-
for (const to of denied) {
|
|
127
|
-
console.log(` ${chalk.red("\u2717")} ${from} \u2192 ${to}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
134
|
+
for (const r of allowRules) {
|
|
135
|
+
console.log(` ${chalk2.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
136
|
+
}
|
|
137
|
+
for (const r of denyRules) {
|
|
138
|
+
const reason = r.reason ? chalk2.dim(` (${r.reason})`) : "";
|
|
139
|
+
console.log(` ${chalk2.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
130
140
|
}
|
|
131
141
|
console.log(
|
|
132
142
|
`
|
|
133
|
-
Enforcement: ${config.rules.enforceBoundaries ?
|
|
143
|
+
Enforcement: ${config.rules.enforceBoundaries ? chalk2.green("on") : chalk2.yellow("off")}`
|
|
134
144
|
);
|
|
135
145
|
}
|
|
136
146
|
async function inferAndDisplay(projectRoot, config, configPath) {
|
|
137
|
-
console.log(
|
|
147
|
+
console.log(chalk2.dim("Analyzing imports..."));
|
|
138
148
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
139
149
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
140
150
|
const graph = await buildImportGraph(projectRoot, {
|
|
141
151
|
packages,
|
|
142
152
|
ignore: config.ignore
|
|
143
153
|
});
|
|
144
|
-
console.log(
|
|
154
|
+
console.log(chalk2.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
145
155
|
const inferred = inferBoundaries(graph);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.log(chalk.yellow("No boundary rules could be inferred."));
|
|
156
|
+
if (inferred.length === 0) {
|
|
157
|
+
console.log(chalk2.yellow("No boundary rules could be inferred."));
|
|
149
158
|
return;
|
|
150
159
|
}
|
|
151
|
-
const
|
|
160
|
+
const allow = inferred.filter((r) => r.allow);
|
|
161
|
+
const deny = inferred.filter((r) => !r.allow);
|
|
152
162
|
console.log(`
|
|
153
|
-
${
|
|
163
|
+
${chalk2.bold("Inferred boundary rules:")}
|
|
154
164
|
`);
|
|
155
|
-
for (const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
for (const r of allow) {
|
|
166
|
+
console.log(` ${chalk2.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
167
|
+
}
|
|
168
|
+
for (const r of deny) {
|
|
169
|
+
const reason = r.reason ? chalk2.dim(` (${r.reason})`) : "";
|
|
170
|
+
console.log(` ${chalk2.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
159
171
|
}
|
|
160
172
|
console.log(`
|
|
161
|
-
${
|
|
173
|
+
${allow.length} allowed, ${deny.length} denied`);
|
|
162
174
|
const shouldSave = await confirm("\nSave to viberails.config.json?");
|
|
163
175
|
if (shouldSave) {
|
|
164
176
|
config.boundaries = inferred;
|
|
165
177
|
config.rules.enforceBoundaries = true;
|
|
166
178
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
167
179
|
`);
|
|
168
|
-
console.log(`${
|
|
180
|
+
console.log(`${chalk2.green("\u2713")} Saved ${inferred.length} rules`);
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
async function showGraph(projectRoot, config) {
|
|
172
|
-
console.log(
|
|
184
|
+
console.log(chalk2.dim("Building import graph..."));
|
|
173
185
|
const { buildImportGraph } = await import("@viberails/graph");
|
|
174
186
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
175
187
|
const graph = await buildImportGraph(projectRoot, {
|
|
@@ -177,20 +189,20 @@ async function showGraph(projectRoot, config) {
|
|
|
177
189
|
ignore: config.ignore
|
|
178
190
|
});
|
|
179
191
|
console.log(`
|
|
180
|
-
${
|
|
192
|
+
${chalk2.bold("Import dependency graph:")}
|
|
181
193
|
`);
|
|
182
194
|
console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
|
|
183
195
|
`);
|
|
184
196
|
if (graph.packages.length > 0) {
|
|
185
197
|
for (const pkg of graph.packages) {
|
|
186
198
|
const deps = pkg.internalDeps.length > 0 ? `
|
|
187
|
-
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` :
|
|
199
|
+
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : chalk2.dim(" (no internal deps)");
|
|
188
200
|
console.log(` ${pkg.name}${deps}`);
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
203
|
if (graph.cycles.length > 0) {
|
|
192
204
|
console.log(`
|
|
193
|
-
${
|
|
205
|
+
${chalk2.yellow("Cycles detected:")}`);
|
|
194
206
|
for (const cycle of graph.cycles) {
|
|
195
207
|
const paths = cycle.map((f) => path3.relative(projectRoot, f));
|
|
196
208
|
console.log(` ${paths.join(" \u2192 ")}`);
|
|
@@ -202,7 +214,7 @@ ${chalk.yellow("Cycles detected:")}`);
|
|
|
202
214
|
import * as fs6 from "fs";
|
|
203
215
|
import * as path6 from "path";
|
|
204
216
|
import { loadConfig as loadConfig2 } from "@viberails/config";
|
|
205
|
-
import
|
|
217
|
+
import chalk3 from "chalk";
|
|
206
218
|
|
|
207
219
|
// src/commands/check-config.ts
|
|
208
220
|
function resolveConfigForFile(relPath, config) {
|
|
@@ -235,6 +247,7 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
235
247
|
import { execSync } from "child_process";
|
|
236
248
|
import * as fs4 from "fs";
|
|
237
249
|
import * as path4 from "path";
|
|
250
|
+
import picomatch from "picomatch";
|
|
238
251
|
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
239
252
|
"node_modules",
|
|
240
253
|
".git",
|
|
@@ -270,25 +283,9 @@ var NAMING_PATTERNS = {
|
|
|
270
283
|
snake_case: /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/
|
|
271
284
|
};
|
|
272
285
|
function isIgnored(relPath, ignorePatterns) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (startsGlob && endsGlob) {
|
|
277
|
-
const middle = pattern.slice(3, -3);
|
|
278
|
-
if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
} else if (endsGlob) {
|
|
282
|
-
const prefix = pattern.slice(0, -3);
|
|
283
|
-
if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
|
|
284
|
-
} else if (startsGlob) {
|
|
285
|
-
const suffix = pattern.slice(3);
|
|
286
|
-
if (relPath.endsWith(suffix) || relPath === suffix) return true;
|
|
287
|
-
} else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return false;
|
|
286
|
+
if (ignorePatterns.length === 0) return false;
|
|
287
|
+
const isMatch = picomatch(ignorePatterns, { dot: true });
|
|
288
|
+
return isMatch(relPath);
|
|
292
289
|
}
|
|
293
290
|
function countFileLines(filePath) {
|
|
294
291
|
try {
|
|
@@ -455,12 +452,12 @@ function printGroupedViolations(violations, limit) {
|
|
|
455
452
|
const toShow = group.slice(0, remaining);
|
|
456
453
|
const hidden = group.length - toShow.length;
|
|
457
454
|
for (const v of toShow) {
|
|
458
|
-
const icon = v.severity === "error" ?
|
|
459
|
-
console.log(`${icon} ${
|
|
455
|
+
const icon = v.severity === "error" ? chalk3.red("\u2717") : chalk3.yellow("!");
|
|
456
|
+
console.log(`${icon} ${chalk3.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
460
457
|
}
|
|
461
458
|
totalShown += toShow.length;
|
|
462
459
|
if (hidden > 0) {
|
|
463
|
-
console.log(
|
|
460
|
+
console.log(chalk3.dim(` ... and ${hidden} more ${rule} violations`));
|
|
464
461
|
}
|
|
465
462
|
}
|
|
466
463
|
}
|
|
@@ -478,13 +475,13 @@ async function checkCommand(options, cwd) {
|
|
|
478
475
|
const startDir = cwd ?? process.cwd();
|
|
479
476
|
const projectRoot = findProjectRoot(startDir);
|
|
480
477
|
if (!projectRoot) {
|
|
481
|
-
console.error(`${
|
|
478
|
+
console.error(`${chalk3.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
482
479
|
return 1;
|
|
483
480
|
}
|
|
484
481
|
const configPath = path6.join(projectRoot, CONFIG_FILE2);
|
|
485
482
|
if (!fs6.existsSync(configPath)) {
|
|
486
483
|
console.error(
|
|
487
|
-
`${
|
|
484
|
+
`${chalk3.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
488
485
|
);
|
|
489
486
|
return 1;
|
|
490
487
|
}
|
|
@@ -498,7 +495,7 @@ async function checkCommand(options, cwd) {
|
|
|
498
495
|
filesToCheck = getAllSourceFiles(projectRoot, config);
|
|
499
496
|
}
|
|
500
497
|
if (filesToCheck.length === 0) {
|
|
501
|
-
console.log(`${
|
|
498
|
+
console.log(`${chalk3.green("\u2713")} No files to check.`);
|
|
502
499
|
return 0;
|
|
503
500
|
}
|
|
504
501
|
const violations = [];
|
|
@@ -539,8 +536,7 @@ async function checkCommand(options, cwd) {
|
|
|
539
536
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
540
537
|
violations.push(...testViolations);
|
|
541
538
|
}
|
|
542
|
-
|
|
543
|
-
if (config.rules.enforceBoundaries && hasBoundaries && !options.noBoundaries) {
|
|
539
|
+
if (config.rules.enforceBoundaries && config.boundaries && config.boundaries.length > 0 && !options.noBoundaries) {
|
|
544
540
|
const startTime = Date.now();
|
|
545
541
|
const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
|
|
546
542
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
@@ -561,10 +557,20 @@ async function checkCommand(options, cwd) {
|
|
|
561
557
|
});
|
|
562
558
|
}
|
|
563
559
|
const elapsed = Date.now() - startTime;
|
|
564
|
-
console.log(
|
|
560
|
+
console.log(chalk3.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
|
|
561
|
+
}
|
|
562
|
+
if (options.format === "json") {
|
|
563
|
+
console.log(
|
|
564
|
+
JSON.stringify({
|
|
565
|
+
violations,
|
|
566
|
+
checkedFiles: filesToCheck.length,
|
|
567
|
+
enforcement: config.enforcement
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
|
|
565
571
|
}
|
|
566
572
|
if (violations.length === 0) {
|
|
567
|
-
console.log(`${
|
|
573
|
+
console.log(`${chalk3.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
568
574
|
return 0;
|
|
569
575
|
}
|
|
570
576
|
if (!options.quiet) {
|
|
@@ -572,7 +578,7 @@ async function checkCommand(options, cwd) {
|
|
|
572
578
|
}
|
|
573
579
|
printSummary(violations);
|
|
574
580
|
if (config.enforcement === "enforce") {
|
|
575
|
-
console.log(
|
|
581
|
+
console.log(chalk3.red("Fix violations before committing."));
|
|
576
582
|
return 1;
|
|
577
583
|
}
|
|
578
584
|
return 0;
|
|
@@ -582,23 +588,23 @@ async function checkCommand(options, cwd) {
|
|
|
582
588
|
import * as fs9 from "fs";
|
|
583
589
|
import * as path10 from "path";
|
|
584
590
|
import { loadConfig as loadConfig3 } from "@viberails/config";
|
|
585
|
-
import
|
|
591
|
+
import chalk5 from "chalk";
|
|
586
592
|
|
|
587
593
|
// src/commands/fix-helpers.ts
|
|
588
594
|
import { execSync as execSync2 } from "child_process";
|
|
589
595
|
import { createInterface as createInterface2 } from "readline";
|
|
590
|
-
import
|
|
596
|
+
import chalk4 from "chalk";
|
|
591
597
|
function printPlan(renames, stubs) {
|
|
592
598
|
if (renames.length > 0) {
|
|
593
|
-
console.log(
|
|
599
|
+
console.log(chalk4.bold("\nFile renames:"));
|
|
594
600
|
for (const r of renames) {
|
|
595
|
-
console.log(` ${
|
|
601
|
+
console.log(` ${chalk4.red(r.oldPath)} \u2192 ${chalk4.green(r.newPath)}`);
|
|
596
602
|
}
|
|
597
603
|
}
|
|
598
604
|
if (stubs.length > 0) {
|
|
599
|
-
console.log(
|
|
605
|
+
console.log(chalk4.bold("\nTest stubs to create:"));
|
|
600
606
|
for (const s of stubs) {
|
|
601
|
-
console.log(` ${
|
|
607
|
+
console.log(` ${chalk4.green("+")} ${s.path}`);
|
|
602
608
|
}
|
|
603
609
|
}
|
|
604
610
|
}
|
|
@@ -662,7 +668,8 @@ async function updateImportsAfterRenames(renames, projectRoot) {
|
|
|
662
668
|
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
663
669
|
for (const sourceFile of project.getSourceFiles()) {
|
|
664
670
|
const filePath = sourceFile.getFilePath();
|
|
665
|
-
|
|
671
|
+
const segments = filePath.split(path7.sep);
|
|
672
|
+
if (segments.includes("node_modules") || segments.includes("dist")) continue;
|
|
666
673
|
const fileDir = path7.dirname(filePath);
|
|
667
674
|
for (const decl of sourceFile.getImportDeclarations()) {
|
|
668
675
|
const specifier = decl.getModuleSpecifierValue();
|
|
@@ -848,13 +855,13 @@ async function fixCommand(options, cwd) {
|
|
|
848
855
|
const startDir = cwd ?? process.cwd();
|
|
849
856
|
const projectRoot = findProjectRoot(startDir);
|
|
850
857
|
if (!projectRoot) {
|
|
851
|
-
console.error(`${
|
|
858
|
+
console.error(`${chalk5.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
852
859
|
return 1;
|
|
853
860
|
}
|
|
854
861
|
const configPath = path10.join(projectRoot, CONFIG_FILE3);
|
|
855
862
|
if (!fs9.existsSync(configPath)) {
|
|
856
863
|
console.error(
|
|
857
|
-
`${
|
|
864
|
+
`${chalk5.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
858
865
|
);
|
|
859
866
|
return 1;
|
|
860
867
|
}
|
|
@@ -863,7 +870,7 @@ async function fixCommand(options, cwd) {
|
|
|
863
870
|
const isDirty = checkGitDirty(projectRoot);
|
|
864
871
|
if (isDirty) {
|
|
865
872
|
console.log(
|
|
866
|
-
|
|
873
|
+
chalk5.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
867
874
|
);
|
|
868
875
|
}
|
|
869
876
|
}
|
|
@@ -893,12 +900,12 @@ async function fixCommand(options, cwd) {
|
|
|
893
900
|
}
|
|
894
901
|
}
|
|
895
902
|
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
896
|
-
console.log(`${
|
|
903
|
+
console.log(`${chalk5.green("\u2713")} No fixable violations found.`);
|
|
897
904
|
return 0;
|
|
898
905
|
}
|
|
899
906
|
printPlan(dedupedRenames, testStubs);
|
|
900
907
|
if (options.dryRun) {
|
|
901
|
-
console.log(
|
|
908
|
+
console.log(chalk5.dim("\nDry run \u2014 no changes applied."));
|
|
902
909
|
return 0;
|
|
903
910
|
}
|
|
904
911
|
if (!options.yes) {
|
|
@@ -929,15 +936,15 @@ async function fixCommand(options, cwd) {
|
|
|
929
936
|
}
|
|
930
937
|
console.log("");
|
|
931
938
|
if (renameCount > 0) {
|
|
932
|
-
console.log(`${
|
|
939
|
+
console.log(`${chalk5.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
933
940
|
}
|
|
934
941
|
if (importUpdateCount > 0) {
|
|
935
942
|
console.log(
|
|
936
|
-
`${
|
|
943
|
+
`${chalk5.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
937
944
|
);
|
|
938
945
|
}
|
|
939
946
|
if (stubCount > 0) {
|
|
940
|
-
console.log(`${
|
|
947
|
+
console.log(`${chalk5.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
941
948
|
}
|
|
942
949
|
return 0;
|
|
943
950
|
}
|
|
@@ -945,11 +952,262 @@ async function fixCommand(options, cwd) {
|
|
|
945
952
|
// src/commands/init.ts
|
|
946
953
|
import * as fs12 from "fs";
|
|
947
954
|
import * as path13 from "path";
|
|
948
|
-
import * as p2 from "@clack/prompts";
|
|
949
955
|
import { generateConfig } from "@viberails/config";
|
|
950
956
|
import { scan } from "@viberails/scanner";
|
|
951
957
|
import chalk9 from "chalk";
|
|
952
958
|
|
|
959
|
+
// src/display.ts
|
|
960
|
+
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, ORM_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
|
|
961
|
+
import chalk7 from "chalk";
|
|
962
|
+
|
|
963
|
+
// src/display-helpers.ts
|
|
964
|
+
import { ROLE_DESCRIPTIONS } from "@viberails/types";
|
|
965
|
+
function groupByRole(directories) {
|
|
966
|
+
const map = /* @__PURE__ */ new Map();
|
|
967
|
+
for (const dir of directories) {
|
|
968
|
+
if (dir.role === "unknown") continue;
|
|
969
|
+
const existing = map.get(dir.role);
|
|
970
|
+
if (existing) {
|
|
971
|
+
existing.dirs.push(dir);
|
|
972
|
+
} else {
|
|
973
|
+
map.set(dir.role, { dirs: [dir] });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
const groups = [];
|
|
977
|
+
for (const [role, { dirs }] of map) {
|
|
978
|
+
const label = ROLE_DESCRIPTIONS[role] ?? role;
|
|
979
|
+
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
980
|
+
groups.push({
|
|
981
|
+
role,
|
|
982
|
+
label,
|
|
983
|
+
dirCount: dirs.length,
|
|
984
|
+
totalFiles,
|
|
985
|
+
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
return groups;
|
|
989
|
+
}
|
|
990
|
+
function formatSummary(stats, packageCount) {
|
|
991
|
+
const parts = [];
|
|
992
|
+
if (packageCount && packageCount > 1) {
|
|
993
|
+
parts.push(`${packageCount} packages`);
|
|
994
|
+
}
|
|
995
|
+
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
996
|
+
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
997
|
+
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
998
|
+
return parts.join(" \xB7 ");
|
|
999
|
+
}
|
|
1000
|
+
function formatExtensions(filesByExtension, maxEntries = 4) {
|
|
1001
|
+
return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
|
|
1002
|
+
}
|
|
1003
|
+
function formatRoleGroup(group) {
|
|
1004
|
+
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1005
|
+
if (group.singlePath) {
|
|
1006
|
+
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1007
|
+
}
|
|
1008
|
+
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1009
|
+
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/display-monorepo.ts
|
|
1013
|
+
import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
|
|
1014
|
+
import chalk6 from "chalk";
|
|
1015
|
+
function formatPackageSummary(pkg) {
|
|
1016
|
+
const parts = [];
|
|
1017
|
+
if (pkg.stack.framework) {
|
|
1018
|
+
parts.push(formatItem(pkg.stack.framework, FRAMEWORK_NAMES));
|
|
1019
|
+
}
|
|
1020
|
+
if (pkg.stack.styling) {
|
|
1021
|
+
parts.push(formatItem(pkg.stack.styling, STYLING_NAMES));
|
|
1022
|
+
}
|
|
1023
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1024
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1025
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1026
|
+
}
|
|
1027
|
+
function displayMonorepoResults(scanResult) {
|
|
1028
|
+
const { stack, packages } = scanResult;
|
|
1029
|
+
console.log(`
|
|
1030
|
+
${chalk6.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1031
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1032
|
+
if (stack.packageManager) {
|
|
1033
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1034
|
+
}
|
|
1035
|
+
if (stack.linter) {
|
|
1036
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1037
|
+
}
|
|
1038
|
+
if (stack.formatter) {
|
|
1039
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (stack.testRunner) {
|
|
1042
|
+
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1043
|
+
}
|
|
1044
|
+
console.log("");
|
|
1045
|
+
for (const pkg of packages) {
|
|
1046
|
+
console.log(formatPackageSummary(pkg));
|
|
1047
|
+
}
|
|
1048
|
+
const packagesWithDirs = packages.filter(
|
|
1049
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1050
|
+
);
|
|
1051
|
+
if (packagesWithDirs.length > 0) {
|
|
1052
|
+
console.log(`
|
|
1053
|
+
${chalk6.bold("Structure:")}`);
|
|
1054
|
+
for (const pkg of packagesWithDirs) {
|
|
1055
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1056
|
+
if (groups.length === 0) continue;
|
|
1057
|
+
console.log(` ${pkg.relativePath}:`);
|
|
1058
|
+
for (const group of groups) {
|
|
1059
|
+
console.log(` ${chalk6.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
displayConventions(scanResult);
|
|
1064
|
+
displaySummarySection(scanResult);
|
|
1065
|
+
console.log("");
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/display.ts
|
|
1069
|
+
var CONVENTION_LABELS = {
|
|
1070
|
+
fileNaming: "File naming",
|
|
1071
|
+
componentNaming: "Component naming",
|
|
1072
|
+
hookNaming: "Hook naming",
|
|
1073
|
+
importAlias: "Import alias"
|
|
1074
|
+
};
|
|
1075
|
+
function formatItem(item, nameMap) {
|
|
1076
|
+
const name = nameMap?.[item.name] ?? item.name;
|
|
1077
|
+
return item.version ? `${name} ${item.version}` : name;
|
|
1078
|
+
}
|
|
1079
|
+
function confidenceLabel(convention) {
|
|
1080
|
+
const pct = Math.round(convention.consistency);
|
|
1081
|
+
if (convention.confidence === "high") {
|
|
1082
|
+
return `${pct}% \u2014 high confidence, will enforce`;
|
|
1083
|
+
}
|
|
1084
|
+
return `${pct}% \u2014 medium confidence, suggested only`;
|
|
1085
|
+
}
|
|
1086
|
+
function displayConventions(scanResult) {
|
|
1087
|
+
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1088
|
+
if (conventionEntries.length === 0) return;
|
|
1089
|
+
console.log(`
|
|
1090
|
+
${chalk7.bold("Conventions:")}`);
|
|
1091
|
+
for (const [key, convention] of conventionEntries) {
|
|
1092
|
+
if (convention.confidence === "low") continue;
|
|
1093
|
+
const label = CONVENTION_LABELS[key] ?? key;
|
|
1094
|
+
if (scanResult.packages.length > 1) {
|
|
1095
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1096
|
+
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1097
|
+
if (allSame || pkgValues.length <= 1) {
|
|
1098
|
+
const ind = convention.confidence === "high" ? chalk7.green("\u2713") : chalk7.yellow("~");
|
|
1099
|
+
const detail = chalk7.dim(`(${confidenceLabel(convention)})`);
|
|
1100
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1101
|
+
} else {
|
|
1102
|
+
console.log(` ${chalk7.yellow("~")} ${label}: varies by package`);
|
|
1103
|
+
for (const pv of pkgValues) {
|
|
1104
|
+
const pct = Math.round(pv.convention.consistency);
|
|
1105
|
+
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
} else {
|
|
1109
|
+
const ind = convention.confidence === "high" ? chalk7.green("\u2713") : chalk7.yellow("~");
|
|
1110
|
+
const detail = chalk7.dim(`(${confidenceLabel(convention)})`);
|
|
1111
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function displaySummarySection(scanResult) {
|
|
1116
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1117
|
+
console.log(`
|
|
1118
|
+
${chalk7.bold("Summary:")}`);
|
|
1119
|
+
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1120
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1121
|
+
if (ext) {
|
|
1122
|
+
console.log(` ${ext}`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function displayScanResults(scanResult) {
|
|
1126
|
+
if (scanResult.packages.length > 1) {
|
|
1127
|
+
displayMonorepoResults(scanResult);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const { stack } = scanResult;
|
|
1131
|
+
console.log(`
|
|
1132
|
+
${chalk7.bold("Detected:")}`);
|
|
1133
|
+
if (stack.framework) {
|
|
1134
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
|
|
1135
|
+
}
|
|
1136
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1137
|
+
if (stack.styling) {
|
|
1138
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
|
|
1139
|
+
}
|
|
1140
|
+
if (stack.backend) {
|
|
1141
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
|
|
1142
|
+
}
|
|
1143
|
+
if (stack.orm) {
|
|
1144
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.orm, ORM_NAMES)}`);
|
|
1145
|
+
}
|
|
1146
|
+
if (stack.linter) {
|
|
1147
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1148
|
+
}
|
|
1149
|
+
if (stack.formatter) {
|
|
1150
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1151
|
+
}
|
|
1152
|
+
if (stack.testRunner) {
|
|
1153
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1154
|
+
}
|
|
1155
|
+
if (stack.packageManager) {
|
|
1156
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1157
|
+
}
|
|
1158
|
+
if (stack.libraries.length > 0) {
|
|
1159
|
+
for (const lib of stack.libraries) {
|
|
1160
|
+
console.log(` ${chalk7.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1164
|
+
if (groups.length > 0) {
|
|
1165
|
+
console.log(`
|
|
1166
|
+
${chalk7.bold("Structure:")}`);
|
|
1167
|
+
for (const group of groups) {
|
|
1168
|
+
console.log(` ${chalk7.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
displayConventions(scanResult);
|
|
1172
|
+
displaySummarySection(scanResult);
|
|
1173
|
+
console.log("");
|
|
1174
|
+
}
|
|
1175
|
+
function getConventionStr(cv) {
|
|
1176
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1177
|
+
}
|
|
1178
|
+
function displayRulesPreview(config) {
|
|
1179
|
+
console.log(`${chalk7.bold("Rules:")}`);
|
|
1180
|
+
console.log(` ${chalk7.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1181
|
+
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1182
|
+
console.log(
|
|
1183
|
+
` ${chalk7.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
|
|
1184
|
+
);
|
|
1185
|
+
} else if (config.rules.requireTests) {
|
|
1186
|
+
console.log(` ${chalk7.dim("\u2022")} Require test files: yes`);
|
|
1187
|
+
} else {
|
|
1188
|
+
console.log(` ${chalk7.dim("\u2022")} Require test files: no`);
|
|
1189
|
+
}
|
|
1190
|
+
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1191
|
+
console.log(
|
|
1192
|
+
` ${chalk7.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
|
|
1193
|
+
);
|
|
1194
|
+
} else {
|
|
1195
|
+
console.log(` ${chalk7.dim("\u2022")} Enforce file naming: no`);
|
|
1196
|
+
}
|
|
1197
|
+
console.log(
|
|
1198
|
+
` ${chalk7.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1199
|
+
);
|
|
1200
|
+
console.log("");
|
|
1201
|
+
if (config.enforcement === "enforce") {
|
|
1202
|
+
console.log(`${chalk7.bold("Enforcement mode:")} enforce (violations will block commits)`);
|
|
1203
|
+
} else {
|
|
1204
|
+
console.log(
|
|
1205
|
+
`${chalk7.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
console.log("");
|
|
1209
|
+
}
|
|
1210
|
+
|
|
953
1211
|
// src/utils/write-generated-files.ts
|
|
954
1212
|
import * as fs10 from "fs";
|
|
955
1213
|
import * as path11 from "path";
|
|
@@ -979,60 +1237,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
979
1237
|
// src/commands/init-hooks.ts
|
|
980
1238
|
import * as fs11 from "fs";
|
|
981
1239
|
import * as path12 from "path";
|
|
982
|
-
import
|
|
983
|
-
function setupClaudeCodeHook(projectRoot) {
|
|
984
|
-
const claudeDir = path12.join(projectRoot, ".claude");
|
|
985
|
-
if (!fs11.existsSync(claudeDir)) {
|
|
986
|
-
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
987
|
-
}
|
|
988
|
-
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
989
|
-
let settings = {};
|
|
990
|
-
if (fs11.existsSync(settingsPath)) {
|
|
991
|
-
try {
|
|
992
|
-
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
993
|
-
} catch {
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
const hooks = settings.hooks ?? {};
|
|
997
|
-
const postToolUse = hooks.PostToolUse;
|
|
998
|
-
if (Array.isArray(postToolUse)) {
|
|
999
|
-
const hasViberails = postToolUse.some(
|
|
1000
|
-
(entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
|
|
1001
|
-
(h) => typeof h === "object" && h !== null && typeof h.command === "string" && h.command.includes("viberails")
|
|
1002
|
-
)
|
|
1003
|
-
);
|
|
1004
|
-
if (hasViberails) return;
|
|
1005
|
-
}
|
|
1006
|
-
const viberailsHook = {
|
|
1007
|
-
matcher: "Edit|Write",
|
|
1008
|
-
hooks: [
|
|
1009
|
-
{
|
|
1010
|
-
type: "command",
|
|
1011
|
-
command: "jq -r '.tool_input.file_path' | xargs npx viberails check --files"
|
|
1012
|
-
}
|
|
1013
|
-
]
|
|
1014
|
-
};
|
|
1015
|
-
if (!hooks.PostToolUse) {
|
|
1016
|
-
hooks.PostToolUse = [viberailsHook];
|
|
1017
|
-
} else if (Array.isArray(hooks.PostToolUse)) {
|
|
1018
|
-
hooks.PostToolUse.push(viberailsHook);
|
|
1019
|
-
}
|
|
1020
|
-
settings.hooks = hooks;
|
|
1021
|
-
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1022
|
-
`);
|
|
1023
|
-
console.log(` ${chalk5.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1024
|
-
}
|
|
1240
|
+
import chalk8 from "chalk";
|
|
1025
1241
|
function setupPreCommitHook(projectRoot) {
|
|
1026
1242
|
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1027
1243
|
if (fs11.existsSync(lefthookPath)) {
|
|
1028
1244
|
addLefthookPreCommit(lefthookPath);
|
|
1029
|
-
console.log(` ${
|
|
1245
|
+
console.log(` ${chalk8.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1030
1246
|
return;
|
|
1031
1247
|
}
|
|
1032
1248
|
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1033
1249
|
if (fs11.existsSync(huskyDir)) {
|
|
1034
1250
|
writeHuskyPreCommit(huskyDir);
|
|
1035
|
-
console.log(` ${
|
|
1251
|
+
console.log(` ${chalk8.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1036
1252
|
return;
|
|
1037
1253
|
}
|
|
1038
1254
|
const gitDir = path12.join(projectRoot, ".git");
|
|
@@ -1042,7 +1258,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
1042
1258
|
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1043
1259
|
}
|
|
1044
1260
|
writeGitHookPreCommit(hooksDir);
|
|
1045
|
-
console.log(` ${
|
|
1261
|
+
console.log(` ${chalk8.green("\u2713")} .git/hooks/pre-commit`);
|
|
1046
1262
|
}
|
|
1047
1263
|
}
|
|
1048
1264
|
function writeGitHookPreCommit(hooksDir) {
|
|
@@ -1072,10 +1288,72 @@ npx viberails check --staged
|
|
|
1072
1288
|
function addLefthookPreCommit(lefthookPath) {
|
|
1073
1289
|
const content = fs11.readFileSync(lefthookPath, "utf-8");
|
|
1074
1290
|
if (content.includes("viberails")) return;
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1291
|
+
const hasPreCommit = /^pre-commit:/m.test(content);
|
|
1292
|
+
if (hasPreCommit) {
|
|
1293
|
+
const commandBlock = ["", " viberails:", " run: npx viberails check --staged"].join(
|
|
1294
|
+
"\n"
|
|
1295
|
+
);
|
|
1296
|
+
const updated = `${content.trimEnd()}
|
|
1297
|
+
${commandBlock}
|
|
1298
|
+
`;
|
|
1299
|
+
fs11.writeFileSync(lefthookPath, updated);
|
|
1300
|
+
} else {
|
|
1301
|
+
const section = [
|
|
1302
|
+
"",
|
|
1303
|
+
"pre-commit:",
|
|
1304
|
+
" commands:",
|
|
1305
|
+
" viberails:",
|
|
1306
|
+
" run: npx viberails check --staged"
|
|
1307
|
+
].join("\n");
|
|
1308
|
+
fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
|
|
1309
|
+
${section}
|
|
1310
|
+
`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function detectHookManager(projectRoot) {
|
|
1314
|
+
if (fs11.existsSync(path12.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
1315
|
+
if (fs11.existsSync(path12.join(projectRoot, ".husky"))) return "Husky";
|
|
1316
|
+
if (fs11.existsSync(path12.join(projectRoot, ".git"))) return "git hook";
|
|
1317
|
+
return void 0;
|
|
1318
|
+
}
|
|
1319
|
+
function setupClaudeCodeHook(projectRoot) {
|
|
1320
|
+
const claudeDir = path12.join(projectRoot, ".claude");
|
|
1321
|
+
if (!fs11.existsSync(claudeDir)) {
|
|
1322
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
1323
|
+
}
|
|
1324
|
+
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
1325
|
+
let settings = {};
|
|
1326
|
+
if (fs11.existsSync(settingsPath)) {
|
|
1327
|
+
try {
|
|
1328
|
+
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
1329
|
+
} catch {
|
|
1330
|
+
console.warn(
|
|
1331
|
+
` ${chalk8.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
|
|
1332
|
+
);
|
|
1333
|
+
settings = {};
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const hooks = settings.hooks ?? {};
|
|
1337
|
+
const existing = hooks.PostToolUse ?? [];
|
|
1338
|
+
if (existing.some((h) => JSON.stringify(h).includes("viberails"))) return;
|
|
1339
|
+
const extractFile = `node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input?.file_path??'')}catch{}"`;
|
|
1340
|
+
const hookCommand = `FILE=$(${extractFile}) && [ -n "$FILE" ] && npx viberails check --files "$FILE" --format json; exit 0`;
|
|
1341
|
+
hooks.PostToolUse = [
|
|
1342
|
+
...existing,
|
|
1343
|
+
{
|
|
1344
|
+
matcher: "Edit|Write",
|
|
1345
|
+
hooks: [
|
|
1346
|
+
{
|
|
1347
|
+
type: "command",
|
|
1348
|
+
command: hookCommand
|
|
1349
|
+
}
|
|
1350
|
+
]
|
|
1351
|
+
}
|
|
1352
|
+
];
|
|
1353
|
+
settings.hooks = hooks;
|
|
1354
|
+
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1078
1355
|
`);
|
|
1356
|
+
console.log(` ${chalk8.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1079
1357
|
}
|
|
1080
1358
|
function writeHuskyPreCommit(huskyDir) {
|
|
1081
1359
|
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
@@ -1091,187 +1369,6 @@ npx viberails check --staged
|
|
|
1091
1369
|
fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
1092
1370
|
}
|
|
1093
1371
|
|
|
1094
|
-
// src/commands/init-wizard.ts
|
|
1095
|
-
import * as p from "@clack/prompts";
|
|
1096
|
-
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES3, LIBRARY_NAMES as LIBRARY_NAMES2, STYLING_NAMES as STYLING_NAMES3 } from "@viberails/types";
|
|
1097
|
-
import chalk8 from "chalk";
|
|
1098
|
-
|
|
1099
|
-
// src/display.ts
|
|
1100
|
-
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
|
|
1101
|
-
import chalk7 from "chalk";
|
|
1102
|
-
|
|
1103
|
-
// src/display-helpers.ts
|
|
1104
|
-
import { ROLE_DESCRIPTIONS } from "@viberails/types";
|
|
1105
|
-
function groupByRole(directories) {
|
|
1106
|
-
const map = /* @__PURE__ */ new Map();
|
|
1107
|
-
for (const dir of directories) {
|
|
1108
|
-
if (dir.role === "unknown") continue;
|
|
1109
|
-
const existing = map.get(dir.role);
|
|
1110
|
-
if (existing) {
|
|
1111
|
-
existing.dirs.push(dir);
|
|
1112
|
-
} else {
|
|
1113
|
-
map.set(dir.role, { dirs: [dir] });
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
const groups = [];
|
|
1117
|
-
for (const [role, { dirs }] of map) {
|
|
1118
|
-
const label = ROLE_DESCRIPTIONS[role] ?? role;
|
|
1119
|
-
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
1120
|
-
groups.push({
|
|
1121
|
-
role,
|
|
1122
|
-
label,
|
|
1123
|
-
dirCount: dirs.length,
|
|
1124
|
-
totalFiles,
|
|
1125
|
-
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
|
-
return groups;
|
|
1129
|
-
}
|
|
1130
|
-
function formatSummary(stats, packageCount) {
|
|
1131
|
-
const parts = [];
|
|
1132
|
-
if (packageCount && packageCount > 1) {
|
|
1133
|
-
parts.push(`${packageCount} packages`);
|
|
1134
|
-
}
|
|
1135
|
-
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
1136
|
-
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
1137
|
-
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
1138
|
-
return parts.join(" \xB7 ");
|
|
1139
|
-
}
|
|
1140
|
-
function formatRoleGroup(group) {
|
|
1141
|
-
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1142
|
-
if (group.singlePath) {
|
|
1143
|
-
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1144
|
-
}
|
|
1145
|
-
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1146
|
-
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// src/display-monorepo.ts
|
|
1150
|
-
import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
|
|
1151
|
-
import chalk6 from "chalk";
|
|
1152
|
-
|
|
1153
|
-
// src/display.ts
|
|
1154
|
-
function formatItem(item, nameMap) {
|
|
1155
|
-
const name = nameMap?.[item.name] ?? item.name;
|
|
1156
|
-
return item.version ? `${name} ${item.version}` : name;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// src/commands/init-wizard.ts
|
|
1160
|
-
var DEFAULT_WIZARD_RESULT = {
|
|
1161
|
-
enforcement: "warn",
|
|
1162
|
-
checks: {
|
|
1163
|
-
fileSize: true,
|
|
1164
|
-
naming: true,
|
|
1165
|
-
tests: true,
|
|
1166
|
-
boundaries: false
|
|
1167
|
-
},
|
|
1168
|
-
integration: ["pre-commit"]
|
|
1169
|
-
};
|
|
1170
|
-
async function runWizard(scanResult) {
|
|
1171
|
-
const isMonorepo = scanResult.packages.length > 1;
|
|
1172
|
-
displayScanSummary(scanResult);
|
|
1173
|
-
const enforcement = await p.select({
|
|
1174
|
-
message: "How strict should viberails be?",
|
|
1175
|
-
initialValue: "warn",
|
|
1176
|
-
options: [
|
|
1177
|
-
{ value: "warn", label: "Warn", hint: "show issues, never block commits" },
|
|
1178
|
-
{ value: "enforce", label: "Enforce", hint: "block commits with violations" }
|
|
1179
|
-
]
|
|
1180
|
-
});
|
|
1181
|
-
if (p.isCancel(enforcement)) {
|
|
1182
|
-
p.cancel("Setup cancelled.");
|
|
1183
|
-
return null;
|
|
1184
|
-
}
|
|
1185
|
-
const checkOptions = [
|
|
1186
|
-
{ value: "fileSize", label: "File size limit (300 lines)" },
|
|
1187
|
-
{ value: "naming", label: "File naming conventions" },
|
|
1188
|
-
{ value: "tests", label: "Missing test files" }
|
|
1189
|
-
];
|
|
1190
|
-
if (isMonorepo) {
|
|
1191
|
-
checkOptions.push({ value: "boundaries", label: "Import boundaries" });
|
|
1192
|
-
}
|
|
1193
|
-
const enabledChecks = await p.multiselect({
|
|
1194
|
-
message: "Which checks should viberails run?",
|
|
1195
|
-
options: checkOptions,
|
|
1196
|
-
initialValues: ["fileSize", "naming", "tests"],
|
|
1197
|
-
required: false
|
|
1198
|
-
});
|
|
1199
|
-
if (p.isCancel(enabledChecks)) {
|
|
1200
|
-
p.cancel("Setup cancelled.");
|
|
1201
|
-
return null;
|
|
1202
|
-
}
|
|
1203
|
-
const checks = {
|
|
1204
|
-
fileSize: enabledChecks.includes("fileSize"),
|
|
1205
|
-
naming: enabledChecks.includes("naming"),
|
|
1206
|
-
tests: enabledChecks.includes("tests"),
|
|
1207
|
-
boundaries: enabledChecks.includes("boundaries")
|
|
1208
|
-
};
|
|
1209
|
-
const integrationOptions = [
|
|
1210
|
-
{ value: "pre-commit", label: "Git pre-commit hook", hint: "runs on every commit" },
|
|
1211
|
-
{
|
|
1212
|
-
value: "claude-hook",
|
|
1213
|
-
label: "Claude Code hook",
|
|
1214
|
-
hint: "checks files as Claude edits them"
|
|
1215
|
-
},
|
|
1216
|
-
{ value: "context-only", label: "Context files only", hint: "no hooks" }
|
|
1217
|
-
];
|
|
1218
|
-
const integration = await p.multiselect({
|
|
1219
|
-
message: "Where should checks run?",
|
|
1220
|
-
options: integrationOptions,
|
|
1221
|
-
initialValues: ["pre-commit"],
|
|
1222
|
-
required: true
|
|
1223
|
-
});
|
|
1224
|
-
if (p.isCancel(integration)) {
|
|
1225
|
-
p.cancel("Setup cancelled.");
|
|
1226
|
-
return null;
|
|
1227
|
-
}
|
|
1228
|
-
const finalIntegration = integration.includes("context-only") ? ["context-only"] : integration;
|
|
1229
|
-
return {
|
|
1230
|
-
enforcement,
|
|
1231
|
-
checks,
|
|
1232
|
-
integration: finalIntegration
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1235
|
-
function displayScanSummary(scanResult) {
|
|
1236
|
-
const { stack } = scanResult;
|
|
1237
|
-
const parts = [];
|
|
1238
|
-
if (stack.framework) parts.push(formatItem(stack.framework, FRAMEWORK_NAMES3));
|
|
1239
|
-
parts.push(formatItem(stack.language));
|
|
1240
|
-
if (stack.styling) parts.push(formatItem(stack.styling, STYLING_NAMES3));
|
|
1241
|
-
if (stack.backend) parts.push(formatItem(stack.backend, FRAMEWORK_NAMES3));
|
|
1242
|
-
p.log.info(`${chalk8.bold("Stack:")} ${parts.join(", ")}`);
|
|
1243
|
-
if (stack.linter || stack.formatter || stack.testRunner || stack.packageManager) {
|
|
1244
|
-
const tools = [];
|
|
1245
|
-
if (stack.linter) tools.push(formatItem(stack.linter));
|
|
1246
|
-
if (stack.formatter && stack.formatter !== stack.linter)
|
|
1247
|
-
tools.push(formatItem(stack.formatter));
|
|
1248
|
-
if (stack.testRunner) tools.push(formatItem(stack.testRunner));
|
|
1249
|
-
if (stack.packageManager) tools.push(formatItem(stack.packageManager));
|
|
1250
|
-
p.log.info(`${chalk8.bold("Tools:")} ${tools.join(", ")}`);
|
|
1251
|
-
}
|
|
1252
|
-
if (stack.libraries.length > 0) {
|
|
1253
|
-
const libs = stack.libraries.map((lib) => formatItem(lib, LIBRARY_NAMES2)).join(", ");
|
|
1254
|
-
p.log.info(`${chalk8.bold("Libraries:")} ${libs}`);
|
|
1255
|
-
}
|
|
1256
|
-
const groups = groupByRole(scanResult.structure.directories);
|
|
1257
|
-
if (groups.length > 0) {
|
|
1258
|
-
const structParts = groups.map((g) => formatRoleGroup(g));
|
|
1259
|
-
p.log.info(`${chalk8.bold("Structure:")} ${structParts.join(", ")}`);
|
|
1260
|
-
}
|
|
1261
|
-
const conventionEntries = Object.entries(scanResult.conventions).filter(
|
|
1262
|
-
([, c]) => c.confidence !== "low"
|
|
1263
|
-
);
|
|
1264
|
-
if (conventionEntries.length > 0) {
|
|
1265
|
-
const convParts = conventionEntries.map(([, c]) => {
|
|
1266
|
-
const pct = Math.round(c.consistency);
|
|
1267
|
-
return `${c.value} (${pct}%)`;
|
|
1268
|
-
});
|
|
1269
|
-
p.log.info(`${chalk8.bold("Conventions:")} ${convParts.join(", ")}`);
|
|
1270
|
-
}
|
|
1271
|
-
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1272
|
-
p.log.info(`${chalk8.bold("Summary:")} ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
1372
|
// src/commands/init.ts
|
|
1276
1373
|
var CONFIG_FILE4 = "viberails.config.json";
|
|
1277
1374
|
function filterHighConfidence(conventions) {
|
|
@@ -1286,13 +1383,6 @@ function filterHighConfidence(conventions) {
|
|
|
1286
1383
|
}
|
|
1287
1384
|
return filtered;
|
|
1288
1385
|
}
|
|
1289
|
-
function applyWizardResult(config, wizard) {
|
|
1290
|
-
config.enforcement = wizard.enforcement;
|
|
1291
|
-
if (!wizard.checks.fileSize) config.rules.maxFileLines = 0;
|
|
1292
|
-
config.rules.enforceNaming = wizard.checks.naming;
|
|
1293
|
-
config.rules.requireTests = wizard.checks.tests;
|
|
1294
|
-
config.rules.enforceBoundaries = wizard.checks.boundaries;
|
|
1295
|
-
}
|
|
1296
1386
|
async function initCommand(options, cwd) {
|
|
1297
1387
|
const startDir = cwd ?? process.cwd();
|
|
1298
1388
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1308,65 +1398,78 @@ async function initCommand(options, cwd) {
|
|
|
1308
1398
|
);
|
|
1309
1399
|
return;
|
|
1310
1400
|
}
|
|
1311
|
-
|
|
1312
|
-
const s = p2.spinner();
|
|
1313
|
-
s.start("Scanning project...");
|
|
1401
|
+
console.log(chalk9.dim("Scanning project..."));
|
|
1314
1402
|
const scanResult = await scan(projectRoot);
|
|
1315
|
-
s.stop("Scan complete");
|
|
1316
|
-
if (scanResult.statistics.totalFiles === 0) {
|
|
1317
|
-
p2.log.warn(
|
|
1318
|
-
`No source files detected. viberails will generate context with minimal content.
|
|
1319
|
-
Run ${chalk9.cyan("viberails sync")} after adding source files.`
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
let wizard;
|
|
1323
|
-
if (options.yes) {
|
|
1324
|
-
wizard = { ...DEFAULT_WIZARD_RESULT };
|
|
1325
|
-
} else {
|
|
1326
|
-
const result = await runWizard(scanResult);
|
|
1327
|
-
if (!result) return;
|
|
1328
|
-
wizard = result;
|
|
1329
|
-
}
|
|
1330
1403
|
const config = generateConfig(scanResult);
|
|
1331
1404
|
if (options.yes) {
|
|
1332
1405
|
config.conventions = filterHighConfidence(config.conventions);
|
|
1333
1406
|
}
|
|
1334
|
-
|
|
1335
|
-
if (
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
s.stop("No boundary rules could be inferred");
|
|
1347
|
-
config.rules.enforceBoundaries = false;
|
|
1407
|
+
displayScanResults(scanResult);
|
|
1408
|
+
if (scanResult.statistics.totalFiles === 0) {
|
|
1409
|
+
console.log(
|
|
1410
|
+
chalk9.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + chalk9.cyan("viberails sync") + " after adding source files.\n"
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
displayRulesPreview(config);
|
|
1414
|
+
if (!options.yes) {
|
|
1415
|
+
const accepted = await confirm("Proceed with these settings?");
|
|
1416
|
+
if (!accepted) {
|
|
1417
|
+
console.log("Aborted.");
|
|
1418
|
+
return;
|
|
1348
1419
|
}
|
|
1349
1420
|
}
|
|
1421
|
+
if (config.workspace && config.workspace.packages.length > 0) {
|
|
1422
|
+
let shouldInfer = options.yes;
|
|
1423
|
+
if (!options.yes) {
|
|
1424
|
+
console.log(chalk9.dim(" Scans imports between packages to suggest dependency rules"));
|
|
1425
|
+
shouldInfer = await confirm("Infer boundary rules from import patterns?");
|
|
1426
|
+
}
|
|
1427
|
+
if (shouldInfer) {
|
|
1428
|
+
console.log(chalk9.dim("Building import graph..."));
|
|
1429
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1430
|
+
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1431
|
+
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
1432
|
+
const inferred = inferBoundaries(graph);
|
|
1433
|
+
if (inferred.length > 0) {
|
|
1434
|
+
config.boundaries = inferred;
|
|
1435
|
+
config.rules.enforceBoundaries = true;
|
|
1436
|
+
console.log(` ${chalk9.green("\u2713")} Inferred ${inferred.length} boundary rules`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
const hookManager = detectHookManager(projectRoot);
|
|
1441
|
+
let integrations = { preCommitHook: true, claudeCodeHook: true };
|
|
1442
|
+
if (!options.yes) {
|
|
1443
|
+
console.log("");
|
|
1444
|
+
integrations = await selectIntegrations(hookManager);
|
|
1445
|
+
}
|
|
1350
1446
|
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1351
1447
|
`);
|
|
1352
1448
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1353
1449
|
updateGitignore(projectRoot);
|
|
1354
|
-
|
|
1450
|
+
console.log(`
|
|
1451
|
+
${chalk9.bold("Created:")}`);
|
|
1452
|
+
console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1453
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/context.md`);
|
|
1454
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json`);
|
|
1455
|
+
if (integrations.preCommitHook) {
|
|
1355
1456
|
setupPreCommitHook(projectRoot);
|
|
1356
1457
|
}
|
|
1357
|
-
if (
|
|
1458
|
+
if (integrations.claudeCodeHook) {
|
|
1358
1459
|
setupClaudeCodeHook(projectRoot);
|
|
1359
1460
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
);
|
|
1461
|
+
const filesToCommit = [
|
|
1462
|
+
`${chalk9.cyan("viberails.config.json")}`,
|
|
1463
|
+
chalk9.cyan(".viberails/context.md")
|
|
1464
|
+
];
|
|
1465
|
+
if (integrations.claudeCodeHook) {
|
|
1466
|
+
filesToCommit.push(chalk9.cyan(".claude/settings.json"));
|
|
1467
|
+
}
|
|
1468
|
+
console.log(`
|
|
1469
|
+
${chalk9.bold("Next steps:")}`);
|
|
1470
|
+
console.log(` 1. Review ${chalk9.cyan("viberails.config.json")} and adjust rules`);
|
|
1471
|
+
console.log(` 2. Commit ${filesToCommit.join(", ")}`);
|
|
1472
|
+
console.log(` 3. Run ${chalk9.cyan("viberails check")} to verify your project passes`);
|
|
1370
1473
|
}
|
|
1371
1474
|
function updateGitignore(projectRoot) {
|
|
1372
1475
|
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
@@ -1376,8 +1479,9 @@ function updateGitignore(projectRoot) {
|
|
|
1376
1479
|
}
|
|
1377
1480
|
if (!content.includes(".viberails/scan-result.json")) {
|
|
1378
1481
|
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
1379
|
-
|
|
1380
|
-
|
|
1482
|
+
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
1483
|
+
`;
|
|
1484
|
+
fs12.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
1381
1485
|
}
|
|
1382
1486
|
}
|
|
1383
1487
|
|
|
@@ -1401,18 +1505,30 @@ async function syncCommand(cwd) {
|
|
|
1401
1505
|
console.log(chalk10.dim("Scanning project..."));
|
|
1402
1506
|
const scanResult = await scan2(projectRoot);
|
|
1403
1507
|
const merged = mergeConfig(existing, scanResult);
|
|
1404
|
-
|
|
1508
|
+
const existingJson = JSON.stringify(existing, null, 2);
|
|
1509
|
+
const mergedJson = JSON.stringify(merged, null, 2);
|
|
1510
|
+
const configChanged = existingJson !== mergedJson;
|
|
1511
|
+
if (configChanged) {
|
|
1512
|
+
console.log(
|
|
1513
|
+
` ${chalk10.yellow("!")} Config updated \u2014 review ${chalk10.cyan(CONFIG_FILE5)} for changes`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
fs13.writeFileSync(configPath, `${mergedJson}
|
|
1405
1517
|
`);
|
|
1406
1518
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1407
1519
|
console.log(`
|
|
1408
1520
|
${chalk10.bold("Synced:")}`);
|
|
1409
|
-
|
|
1521
|
+
if (configChanged) {
|
|
1522
|
+
console.log(` ${chalk10.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
|
|
1523
|
+
} else {
|
|
1524
|
+
console.log(` ${chalk10.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
|
|
1525
|
+
}
|
|
1410
1526
|
console.log(` ${chalk10.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
1411
1527
|
console.log(` ${chalk10.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1412
1528
|
}
|
|
1413
1529
|
|
|
1414
1530
|
// src/index.ts
|
|
1415
|
-
var VERSION = "0.3.
|
|
1531
|
+
var VERSION = "0.3.1";
|
|
1416
1532
|
var program = new Command();
|
|
1417
1533
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1418
1534
|
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)").action(async (options) => {
|
|
@@ -1433,12 +1549,13 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1433
1549
|
process.exit(1);
|
|
1434
1550
|
}
|
|
1435
1551
|
});
|
|
1436
|
-
program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).action(
|
|
1552
|
+
program.command("check").description("Check files against enforced rules").option("--staged", "Check only staged files (for pre-commit hooks)").option("--files <files...>", "Check specific files").option("--no-boundaries", "Skip boundary checking").option("--quiet", "Show only summary counts, not individual violations").option("--limit <n>", "Maximum number of violations to display", Number.parseInt).option("--format <format>", "Output format: text (default) or json").action(
|
|
1437
1553
|
async (options) => {
|
|
1438
1554
|
try {
|
|
1439
1555
|
const exitCode = await checkCommand({
|
|
1440
1556
|
...options,
|
|
1441
|
-
noBoundaries: options.boundaries === false
|
|
1557
|
+
noBoundaries: options.boundaries === false,
|
|
1558
|
+
format: options.format === "json" ? "json" : "text"
|
|
1442
1559
|
});
|
|
1443
1560
|
process.exit(exitCode);
|
|
1444
1561
|
} catch (err) {
|