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.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var import_commander = require("commander");
|
|
|
41
41
|
var fs3 = __toESM(require("fs"), 1);
|
|
42
42
|
var path3 = __toESM(require("path"), 1);
|
|
43
43
|
var import_config = require("@viberails/config");
|
|
44
|
-
var
|
|
44
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
45
45
|
|
|
46
46
|
// src/utils/find-project-root.ts
|
|
47
47
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -62,6 +62,7 @@ function findProjectRoot(startDir) {
|
|
|
62
62
|
|
|
63
63
|
// src/utils/prompt.ts
|
|
64
64
|
var readline = __toESM(require("readline"), 1);
|
|
65
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
65
66
|
async function confirm(message) {
|
|
66
67
|
const rl = readline.createInterface({
|
|
67
68
|
input: process.stdin,
|
|
@@ -75,6 +76,29 @@ async function confirm(message) {
|
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
}
|
|
79
|
+
async function selectIntegrations(hookManager) {
|
|
80
|
+
const result = { preCommitHook: true, claudeCodeHook: true };
|
|
81
|
+
const hookLabel = hookManager ? `Pre-commit hook (detected: ${hookManager})` : "Pre-commit hook (git hook)";
|
|
82
|
+
console.log("Set up integrations:");
|
|
83
|
+
const rl = readline.createInterface({
|
|
84
|
+
input: process.stdin,
|
|
85
|
+
output: process.stdout
|
|
86
|
+
});
|
|
87
|
+
const askYn = (label, defaultYes) => new Promise((resolve4) => {
|
|
88
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
89
|
+
rl.question(` ${label}? (${hint}) `, (answer) => {
|
|
90
|
+
const trimmed = answer.trim().toLowerCase();
|
|
91
|
+
if (trimmed === "") resolve4(defaultYes);
|
|
92
|
+
else resolve4(trimmed === "y" || trimmed === "yes");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
console.log(` ${import_chalk.default.dim("Runs viberails check automatically when you commit")}`);
|
|
96
|
+
result.preCommitHook = await askYn(hookLabel, true);
|
|
97
|
+
console.log(` ${import_chalk.default.dim("Checks files against your rules when Claude edits them")}`);
|
|
98
|
+
result.claudeCodeHook = await askYn("Claude Code hook", true);
|
|
99
|
+
rl.close();
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
78
102
|
|
|
79
103
|
// src/utils/resolve-workspace-packages.ts
|
|
80
104
|
var fs2 = __toESM(require("fs"), 1);
|
|
@@ -99,7 +123,7 @@ function resolveWorkspacePackages(projectRoot, workspace) {
|
|
|
99
123
|
];
|
|
100
124
|
packages.push({ name, path: absPath, relativePath, internalDeps: allDeps });
|
|
101
125
|
}
|
|
102
|
-
const packageNames = new Set(packages.map((
|
|
126
|
+
const packageNames = new Set(packages.map((p) => p.name));
|
|
103
127
|
for (const pkg of packages) {
|
|
104
128
|
pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));
|
|
105
129
|
}
|
|
@@ -129,80 +153,68 @@ async function boundariesCommand(options, cwd) {
|
|
|
129
153
|
}
|
|
130
154
|
displayRules(config);
|
|
131
155
|
}
|
|
132
|
-
function countBoundaries(boundaries) {
|
|
133
|
-
if (!boundaries) return 0;
|
|
134
|
-
if (Array.isArray(boundaries)) return boundaries.length;
|
|
135
|
-
return Object.values(boundaries).reduce((sum, denied) => sum + denied.length, 0);
|
|
136
|
-
}
|
|
137
156
|
function displayRules(config) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
157
|
+
if (!config.boundaries || config.boundaries.length === 0) {
|
|
158
|
+
console.log(import_chalk2.default.yellow("No boundary rules configured."));
|
|
159
|
+
console.log(`Run ${import_chalk2.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
142
160
|
return;
|
|
143
161
|
}
|
|
162
|
+
const allowRules = config.boundaries.filter((r) => r.allow);
|
|
163
|
+
const denyRules = config.boundaries.filter((r) => !r.allow);
|
|
144
164
|
console.log(`
|
|
145
|
-
${
|
|
165
|
+
${import_chalk2.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
|
|
146
166
|
`);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
for (const r of denyRules) {
|
|
154
|
-
const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
|
|
155
|
-
console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
156
|
-
}
|
|
157
|
-
} else if (config.boundaries) {
|
|
158
|
-
for (const [from, denied] of Object.entries(config.boundaries)) {
|
|
159
|
-
for (const to of denied) {
|
|
160
|
-
console.log(` ${import_chalk.default.red("\u2717")} ${from} \u2192 ${to}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
167
|
+
for (const r of allowRules) {
|
|
168
|
+
console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
169
|
+
}
|
|
170
|
+
for (const r of denyRules) {
|
|
171
|
+
const reason = r.reason ? import_chalk2.default.dim(` (${r.reason})`) : "";
|
|
172
|
+
console.log(` ${import_chalk2.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
163
173
|
}
|
|
164
174
|
console.log(
|
|
165
175
|
`
|
|
166
|
-
Enforcement: ${config.rules.enforceBoundaries ?
|
|
176
|
+
Enforcement: ${config.rules.enforceBoundaries ? import_chalk2.default.green("on") : import_chalk2.default.yellow("off")}`
|
|
167
177
|
);
|
|
168
178
|
}
|
|
169
179
|
async function inferAndDisplay(projectRoot, config, configPath) {
|
|
170
|
-
console.log(
|
|
180
|
+
console.log(import_chalk2.default.dim("Analyzing imports..."));
|
|
171
181
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
172
182
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
173
183
|
const graph = await buildImportGraph(projectRoot, {
|
|
174
184
|
packages,
|
|
175
185
|
ignore: config.ignore
|
|
176
186
|
});
|
|
177
|
-
console.log(
|
|
187
|
+
console.log(import_chalk2.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
178
188
|
const inferred = inferBoundaries(graph);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
|
|
189
|
+
if (inferred.length === 0) {
|
|
190
|
+
console.log(import_chalk2.default.yellow("No boundary rules could be inferred."));
|
|
182
191
|
return;
|
|
183
192
|
}
|
|
184
|
-
const
|
|
193
|
+
const allow = inferred.filter((r) => r.allow);
|
|
194
|
+
const deny = inferred.filter((r) => !r.allow);
|
|
185
195
|
console.log(`
|
|
186
|
-
${
|
|
196
|
+
${import_chalk2.default.bold("Inferred boundary rules:")}
|
|
187
197
|
`);
|
|
188
|
-
for (const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
for (const r of allow) {
|
|
199
|
+
console.log(` ${import_chalk2.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
200
|
+
}
|
|
201
|
+
for (const r of deny) {
|
|
202
|
+
const reason = r.reason ? import_chalk2.default.dim(` (${r.reason})`) : "";
|
|
203
|
+
console.log(` ${import_chalk2.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
192
204
|
}
|
|
193
205
|
console.log(`
|
|
194
|
-
${
|
|
206
|
+
${allow.length} allowed, ${deny.length} denied`);
|
|
195
207
|
const shouldSave = await confirm("\nSave to viberails.config.json?");
|
|
196
208
|
if (shouldSave) {
|
|
197
209
|
config.boundaries = inferred;
|
|
198
210
|
config.rules.enforceBoundaries = true;
|
|
199
211
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
200
212
|
`);
|
|
201
|
-
console.log(`${
|
|
213
|
+
console.log(`${import_chalk2.default.green("\u2713")} Saved ${inferred.length} rules`);
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
async function showGraph(projectRoot, config) {
|
|
205
|
-
console.log(
|
|
217
|
+
console.log(import_chalk2.default.dim("Building import graph..."));
|
|
206
218
|
const { buildImportGraph } = await import("@viberails/graph");
|
|
207
219
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
208
220
|
const graph = await buildImportGraph(projectRoot, {
|
|
@@ -210,20 +222,20 @@ async function showGraph(projectRoot, config) {
|
|
|
210
222
|
ignore: config.ignore
|
|
211
223
|
});
|
|
212
224
|
console.log(`
|
|
213
|
-
${
|
|
225
|
+
${import_chalk2.default.bold("Import dependency graph:")}
|
|
214
226
|
`);
|
|
215
227
|
console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
|
|
216
228
|
`);
|
|
217
229
|
if (graph.packages.length > 0) {
|
|
218
230
|
for (const pkg of graph.packages) {
|
|
219
231
|
const deps = pkg.internalDeps.length > 0 ? `
|
|
220
|
-
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` :
|
|
232
|
+
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk2.default.dim(" (no internal deps)");
|
|
221
233
|
console.log(` ${pkg.name}${deps}`);
|
|
222
234
|
}
|
|
223
235
|
}
|
|
224
236
|
if (graph.cycles.length > 0) {
|
|
225
237
|
console.log(`
|
|
226
|
-
${
|
|
238
|
+
${import_chalk2.default.yellow("Cycles detected:")}`);
|
|
227
239
|
for (const cycle of graph.cycles) {
|
|
228
240
|
const paths = cycle.map((f) => path3.relative(projectRoot, f));
|
|
229
241
|
console.log(` ${paths.join(" \u2192 ")}`);
|
|
@@ -235,7 +247,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
|
|
|
235
247
|
var fs6 = __toESM(require("fs"), 1);
|
|
236
248
|
var path6 = __toESM(require("path"), 1);
|
|
237
249
|
var import_config2 = require("@viberails/config");
|
|
238
|
-
var
|
|
250
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
239
251
|
|
|
240
252
|
// src/commands/check-config.ts
|
|
241
253
|
function resolveConfigForFile(relPath, config) {
|
|
@@ -268,6 +280,7 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
268
280
|
var import_node_child_process = require("child_process");
|
|
269
281
|
var fs4 = __toESM(require("fs"), 1);
|
|
270
282
|
var path4 = __toESM(require("path"), 1);
|
|
283
|
+
var import_picomatch = __toESM(require("picomatch"), 1);
|
|
271
284
|
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
272
285
|
"node_modules",
|
|
273
286
|
".git",
|
|
@@ -303,25 +316,9 @@ var NAMING_PATTERNS = {
|
|
|
303
316
|
snake_case: /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/
|
|
304
317
|
};
|
|
305
318
|
function isIgnored(relPath, ignorePatterns) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (startsGlob && endsGlob) {
|
|
310
|
-
const middle = pattern.slice(3, -3);
|
|
311
|
-
if (relPath.startsWith(`${middle}/`) || relPath.includes(`/${middle}/`) || relPath === middle) {
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
} else if (endsGlob) {
|
|
315
|
-
const prefix = pattern.slice(0, -3);
|
|
316
|
-
if (relPath.startsWith(`${prefix}/`) || relPath === prefix) return true;
|
|
317
|
-
} else if (startsGlob) {
|
|
318
|
-
const suffix = pattern.slice(3);
|
|
319
|
-
if (relPath.endsWith(suffix) || relPath === suffix) return true;
|
|
320
|
-
} else if (relPath === pattern || relPath.startsWith(`${pattern}/`)) {
|
|
321
|
-
return true;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return false;
|
|
319
|
+
if (ignorePatterns.length === 0) return false;
|
|
320
|
+
const isMatch = (0, import_picomatch.default)(ignorePatterns, { dot: true });
|
|
321
|
+
return isMatch(relPath);
|
|
325
322
|
}
|
|
326
323
|
function countFileLines(filePath) {
|
|
327
324
|
try {
|
|
@@ -488,12 +485,12 @@ function printGroupedViolations(violations, limit) {
|
|
|
488
485
|
const toShow = group.slice(0, remaining);
|
|
489
486
|
const hidden = group.length - toShow.length;
|
|
490
487
|
for (const v of toShow) {
|
|
491
|
-
const icon = v.severity === "error" ?
|
|
492
|
-
console.log(`${icon} ${
|
|
488
|
+
const icon = v.severity === "error" ? import_chalk3.default.red("\u2717") : import_chalk3.default.yellow("!");
|
|
489
|
+
console.log(`${icon} ${import_chalk3.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
493
490
|
}
|
|
494
491
|
totalShown += toShow.length;
|
|
495
492
|
if (hidden > 0) {
|
|
496
|
-
console.log(
|
|
493
|
+
console.log(import_chalk3.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
497
494
|
}
|
|
498
495
|
}
|
|
499
496
|
}
|
|
@@ -511,13 +508,13 @@ async function checkCommand(options, cwd) {
|
|
|
511
508
|
const startDir = cwd ?? process.cwd();
|
|
512
509
|
const projectRoot = findProjectRoot(startDir);
|
|
513
510
|
if (!projectRoot) {
|
|
514
|
-
console.error(`${
|
|
511
|
+
console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
515
512
|
return 1;
|
|
516
513
|
}
|
|
517
514
|
const configPath = path6.join(projectRoot, CONFIG_FILE2);
|
|
518
515
|
if (!fs6.existsSync(configPath)) {
|
|
519
516
|
console.error(
|
|
520
|
-
`${
|
|
517
|
+
`${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
521
518
|
);
|
|
522
519
|
return 1;
|
|
523
520
|
}
|
|
@@ -531,7 +528,7 @@ async function checkCommand(options, cwd) {
|
|
|
531
528
|
filesToCheck = getAllSourceFiles(projectRoot, config);
|
|
532
529
|
}
|
|
533
530
|
if (filesToCheck.length === 0) {
|
|
534
|
-
console.log(`${
|
|
531
|
+
console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
|
|
535
532
|
return 0;
|
|
536
533
|
}
|
|
537
534
|
const violations = [];
|
|
@@ -572,8 +569,7 @@ async function checkCommand(options, cwd) {
|
|
|
572
569
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
573
570
|
violations.push(...testViolations);
|
|
574
571
|
}
|
|
575
|
-
|
|
576
|
-
if (config.rules.enforceBoundaries && hasBoundaries && !options.noBoundaries) {
|
|
572
|
+
if (config.rules.enforceBoundaries && config.boundaries && config.boundaries.length > 0 && !options.noBoundaries) {
|
|
577
573
|
const startTime = Date.now();
|
|
578
574
|
const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
|
|
579
575
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
@@ -594,10 +590,20 @@ async function checkCommand(options, cwd) {
|
|
|
594
590
|
});
|
|
595
591
|
}
|
|
596
592
|
const elapsed = Date.now() - startTime;
|
|
597
|
-
console.log(
|
|
593
|
+
console.log(import_chalk3.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
|
|
594
|
+
}
|
|
595
|
+
if (options.format === "json") {
|
|
596
|
+
console.log(
|
|
597
|
+
JSON.stringify({
|
|
598
|
+
violations,
|
|
599
|
+
checkedFiles: filesToCheck.length,
|
|
600
|
+
enforcement: config.enforcement
|
|
601
|
+
})
|
|
602
|
+
);
|
|
603
|
+
return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
|
|
598
604
|
}
|
|
599
605
|
if (violations.length === 0) {
|
|
600
|
-
console.log(`${
|
|
606
|
+
console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
601
607
|
return 0;
|
|
602
608
|
}
|
|
603
609
|
if (!options.quiet) {
|
|
@@ -605,7 +611,7 @@ async function checkCommand(options, cwd) {
|
|
|
605
611
|
}
|
|
606
612
|
printSummary(violations);
|
|
607
613
|
if (config.enforcement === "enforce") {
|
|
608
|
-
console.log(
|
|
614
|
+
console.log(import_chalk3.default.red("Fix violations before committing."));
|
|
609
615
|
return 1;
|
|
610
616
|
}
|
|
611
617
|
return 0;
|
|
@@ -615,23 +621,23 @@ async function checkCommand(options, cwd) {
|
|
|
615
621
|
var fs9 = __toESM(require("fs"), 1);
|
|
616
622
|
var path10 = __toESM(require("path"), 1);
|
|
617
623
|
var import_config3 = require("@viberails/config");
|
|
618
|
-
var
|
|
624
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
619
625
|
|
|
620
626
|
// src/commands/fix-helpers.ts
|
|
621
627
|
var import_node_child_process2 = require("child_process");
|
|
622
628
|
var import_node_readline = require("readline");
|
|
623
|
-
var
|
|
629
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
624
630
|
function printPlan(renames, stubs) {
|
|
625
631
|
if (renames.length > 0) {
|
|
626
|
-
console.log(
|
|
632
|
+
console.log(import_chalk4.default.bold("\nFile renames:"));
|
|
627
633
|
for (const r of renames) {
|
|
628
|
-
console.log(` ${
|
|
634
|
+
console.log(` ${import_chalk4.default.red(r.oldPath)} \u2192 ${import_chalk4.default.green(r.newPath)}`);
|
|
629
635
|
}
|
|
630
636
|
}
|
|
631
637
|
if (stubs.length > 0) {
|
|
632
|
-
console.log(
|
|
638
|
+
console.log(import_chalk4.default.bold("\nTest stubs to create:"));
|
|
633
639
|
for (const s of stubs) {
|
|
634
|
-
console.log(` ${
|
|
640
|
+
console.log(` ${import_chalk4.default.green("+")} ${s.path}`);
|
|
635
641
|
}
|
|
636
642
|
}
|
|
637
643
|
}
|
|
@@ -695,7 +701,8 @@ async function updateImportsAfterRenames(renames, projectRoot) {
|
|
|
695
701
|
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
696
702
|
for (const sourceFile of project.getSourceFiles()) {
|
|
697
703
|
const filePath = sourceFile.getFilePath();
|
|
698
|
-
|
|
704
|
+
const segments = filePath.split(path7.sep);
|
|
705
|
+
if (segments.includes("node_modules") || segments.includes("dist")) continue;
|
|
699
706
|
const fileDir = path7.dirname(filePath);
|
|
700
707
|
for (const decl of sourceFile.getImportDeclarations()) {
|
|
701
708
|
const specifier = decl.getModuleSpecifierValue();
|
|
@@ -881,13 +888,13 @@ async function fixCommand(options, cwd) {
|
|
|
881
888
|
const startDir = cwd ?? process.cwd();
|
|
882
889
|
const projectRoot = findProjectRoot(startDir);
|
|
883
890
|
if (!projectRoot) {
|
|
884
|
-
console.error(`${
|
|
891
|
+
console.error(`${import_chalk5.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
885
892
|
return 1;
|
|
886
893
|
}
|
|
887
894
|
const configPath = path10.join(projectRoot, CONFIG_FILE3);
|
|
888
895
|
if (!fs9.existsSync(configPath)) {
|
|
889
896
|
console.error(
|
|
890
|
-
`${
|
|
897
|
+
`${import_chalk5.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
891
898
|
);
|
|
892
899
|
return 1;
|
|
893
900
|
}
|
|
@@ -896,7 +903,7 @@ async function fixCommand(options, cwd) {
|
|
|
896
903
|
const isDirty = checkGitDirty(projectRoot);
|
|
897
904
|
if (isDirty) {
|
|
898
905
|
console.log(
|
|
899
|
-
|
|
906
|
+
import_chalk5.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
900
907
|
);
|
|
901
908
|
}
|
|
902
909
|
}
|
|
@@ -926,12 +933,12 @@ async function fixCommand(options, cwd) {
|
|
|
926
933
|
}
|
|
927
934
|
}
|
|
928
935
|
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
929
|
-
console.log(`${
|
|
936
|
+
console.log(`${import_chalk5.default.green("\u2713")} No fixable violations found.`);
|
|
930
937
|
return 0;
|
|
931
938
|
}
|
|
932
939
|
printPlan(dedupedRenames, testStubs);
|
|
933
940
|
if (options.dryRun) {
|
|
934
|
-
console.log(
|
|
941
|
+
console.log(import_chalk5.default.dim("\nDry run \u2014 no changes applied."));
|
|
935
942
|
return 0;
|
|
936
943
|
}
|
|
937
944
|
if (!options.yes) {
|
|
@@ -962,15 +969,15 @@ async function fixCommand(options, cwd) {
|
|
|
962
969
|
}
|
|
963
970
|
console.log("");
|
|
964
971
|
if (renameCount > 0) {
|
|
965
|
-
console.log(`${
|
|
972
|
+
console.log(`${import_chalk5.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
966
973
|
}
|
|
967
974
|
if (importUpdateCount > 0) {
|
|
968
975
|
console.log(
|
|
969
|
-
`${
|
|
976
|
+
`${import_chalk5.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
970
977
|
);
|
|
971
978
|
}
|
|
972
979
|
if (stubCount > 0) {
|
|
973
|
-
console.log(`${
|
|
980
|
+
console.log(`${import_chalk5.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
974
981
|
}
|
|
975
982
|
return 0;
|
|
976
983
|
}
|
|
@@ -978,11 +985,262 @@ async function fixCommand(options, cwd) {
|
|
|
978
985
|
// src/commands/init.ts
|
|
979
986
|
var fs12 = __toESM(require("fs"), 1);
|
|
980
987
|
var path13 = __toESM(require("path"), 1);
|
|
981
|
-
var p2 = __toESM(require("@clack/prompts"), 1);
|
|
982
988
|
var import_config4 = require("@viberails/config");
|
|
983
989
|
var import_scanner = require("@viberails/scanner");
|
|
984
990
|
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
985
991
|
|
|
992
|
+
// src/display.ts
|
|
993
|
+
var import_types3 = require("@viberails/types");
|
|
994
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
995
|
+
|
|
996
|
+
// src/display-helpers.ts
|
|
997
|
+
var import_types = require("@viberails/types");
|
|
998
|
+
function groupByRole(directories) {
|
|
999
|
+
const map = /* @__PURE__ */ new Map();
|
|
1000
|
+
for (const dir of directories) {
|
|
1001
|
+
if (dir.role === "unknown") continue;
|
|
1002
|
+
const existing = map.get(dir.role);
|
|
1003
|
+
if (existing) {
|
|
1004
|
+
existing.dirs.push(dir);
|
|
1005
|
+
} else {
|
|
1006
|
+
map.set(dir.role, { dirs: [dir] });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const groups = [];
|
|
1010
|
+
for (const [role, { dirs }] of map) {
|
|
1011
|
+
const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
|
|
1012
|
+
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
1013
|
+
groups.push({
|
|
1014
|
+
role,
|
|
1015
|
+
label,
|
|
1016
|
+
dirCount: dirs.length,
|
|
1017
|
+
totalFiles,
|
|
1018
|
+
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
return groups;
|
|
1022
|
+
}
|
|
1023
|
+
function formatSummary(stats, packageCount) {
|
|
1024
|
+
const parts = [];
|
|
1025
|
+
if (packageCount && packageCount > 1) {
|
|
1026
|
+
parts.push(`${packageCount} packages`);
|
|
1027
|
+
}
|
|
1028
|
+
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
1029
|
+
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
1030
|
+
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
1031
|
+
return parts.join(" \xB7 ");
|
|
1032
|
+
}
|
|
1033
|
+
function formatExtensions(filesByExtension, maxEntries = 4) {
|
|
1034
|
+
return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
|
|
1035
|
+
}
|
|
1036
|
+
function formatRoleGroup(group) {
|
|
1037
|
+
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1038
|
+
if (group.singlePath) {
|
|
1039
|
+
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1040
|
+
}
|
|
1041
|
+
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1042
|
+
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/display-monorepo.ts
|
|
1046
|
+
var import_types2 = require("@viberails/types");
|
|
1047
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1048
|
+
function formatPackageSummary(pkg) {
|
|
1049
|
+
const parts = [];
|
|
1050
|
+
if (pkg.stack.framework) {
|
|
1051
|
+
parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
|
|
1052
|
+
}
|
|
1053
|
+
if (pkg.stack.styling) {
|
|
1054
|
+
parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
|
|
1055
|
+
}
|
|
1056
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1057
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1058
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1059
|
+
}
|
|
1060
|
+
function displayMonorepoResults(scanResult) {
|
|
1061
|
+
const { stack, packages } = scanResult;
|
|
1062
|
+
console.log(`
|
|
1063
|
+
${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1064
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1065
|
+
if (stack.packageManager) {
|
|
1066
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1067
|
+
}
|
|
1068
|
+
if (stack.linter) {
|
|
1069
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1070
|
+
}
|
|
1071
|
+
if (stack.formatter) {
|
|
1072
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1073
|
+
}
|
|
1074
|
+
if (stack.testRunner) {
|
|
1075
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1076
|
+
}
|
|
1077
|
+
console.log("");
|
|
1078
|
+
for (const pkg of packages) {
|
|
1079
|
+
console.log(formatPackageSummary(pkg));
|
|
1080
|
+
}
|
|
1081
|
+
const packagesWithDirs = packages.filter(
|
|
1082
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1083
|
+
);
|
|
1084
|
+
if (packagesWithDirs.length > 0) {
|
|
1085
|
+
console.log(`
|
|
1086
|
+
${import_chalk6.default.bold("Structure:")}`);
|
|
1087
|
+
for (const pkg of packagesWithDirs) {
|
|
1088
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1089
|
+
if (groups.length === 0) continue;
|
|
1090
|
+
console.log(` ${pkg.relativePath}:`);
|
|
1091
|
+
for (const group of groups) {
|
|
1092
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
displayConventions(scanResult);
|
|
1097
|
+
displaySummarySection(scanResult);
|
|
1098
|
+
console.log("");
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/display.ts
|
|
1102
|
+
var CONVENTION_LABELS = {
|
|
1103
|
+
fileNaming: "File naming",
|
|
1104
|
+
componentNaming: "Component naming",
|
|
1105
|
+
hookNaming: "Hook naming",
|
|
1106
|
+
importAlias: "Import alias"
|
|
1107
|
+
};
|
|
1108
|
+
function formatItem(item, nameMap) {
|
|
1109
|
+
const name = nameMap?.[item.name] ?? item.name;
|
|
1110
|
+
return item.version ? `${name} ${item.version}` : name;
|
|
1111
|
+
}
|
|
1112
|
+
function confidenceLabel(convention) {
|
|
1113
|
+
const pct = Math.round(convention.consistency);
|
|
1114
|
+
if (convention.confidence === "high") {
|
|
1115
|
+
return `${pct}% \u2014 high confidence, will enforce`;
|
|
1116
|
+
}
|
|
1117
|
+
return `${pct}% \u2014 medium confidence, suggested only`;
|
|
1118
|
+
}
|
|
1119
|
+
function displayConventions(scanResult) {
|
|
1120
|
+
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1121
|
+
if (conventionEntries.length === 0) return;
|
|
1122
|
+
console.log(`
|
|
1123
|
+
${import_chalk7.default.bold("Conventions:")}`);
|
|
1124
|
+
for (const [key, convention] of conventionEntries) {
|
|
1125
|
+
if (convention.confidence === "low") continue;
|
|
1126
|
+
const label = CONVENTION_LABELS[key] ?? key;
|
|
1127
|
+
if (scanResult.packages.length > 1) {
|
|
1128
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1129
|
+
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1130
|
+
if (allSame || pkgValues.length <= 1) {
|
|
1131
|
+
const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
|
|
1132
|
+
const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
|
|
1133
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1134
|
+
} else {
|
|
1135
|
+
console.log(` ${import_chalk7.default.yellow("~")} ${label}: varies by package`);
|
|
1136
|
+
for (const pv of pkgValues) {
|
|
1137
|
+
const pct = Math.round(pv.convention.consistency);
|
|
1138
|
+
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
const ind = convention.confidence === "high" ? import_chalk7.default.green("\u2713") : import_chalk7.default.yellow("~");
|
|
1143
|
+
const detail = import_chalk7.default.dim(`(${confidenceLabel(convention)})`);
|
|
1144
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function displaySummarySection(scanResult) {
|
|
1149
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1150
|
+
console.log(`
|
|
1151
|
+
${import_chalk7.default.bold("Summary:")}`);
|
|
1152
|
+
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1153
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1154
|
+
if (ext) {
|
|
1155
|
+
console.log(` ${ext}`);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function displayScanResults(scanResult) {
|
|
1159
|
+
if (scanResult.packages.length > 1) {
|
|
1160
|
+
displayMonorepoResults(scanResult);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const { stack } = scanResult;
|
|
1164
|
+
console.log(`
|
|
1165
|
+
${import_chalk7.default.bold("Detected:")}`);
|
|
1166
|
+
if (stack.framework) {
|
|
1167
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1168
|
+
}
|
|
1169
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1170
|
+
if (stack.styling) {
|
|
1171
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1172
|
+
}
|
|
1173
|
+
if (stack.backend) {
|
|
1174
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (stack.orm) {
|
|
1177
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1178
|
+
}
|
|
1179
|
+
if (stack.linter) {
|
|
1180
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1181
|
+
}
|
|
1182
|
+
if (stack.formatter) {
|
|
1183
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1184
|
+
}
|
|
1185
|
+
if (stack.testRunner) {
|
|
1186
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1187
|
+
}
|
|
1188
|
+
if (stack.packageManager) {
|
|
1189
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1190
|
+
}
|
|
1191
|
+
if (stack.libraries.length > 0) {
|
|
1192
|
+
for (const lib of stack.libraries) {
|
|
1193
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1197
|
+
if (groups.length > 0) {
|
|
1198
|
+
console.log(`
|
|
1199
|
+
${import_chalk7.default.bold("Structure:")}`);
|
|
1200
|
+
for (const group of groups) {
|
|
1201
|
+
console.log(` ${import_chalk7.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
displayConventions(scanResult);
|
|
1205
|
+
displaySummarySection(scanResult);
|
|
1206
|
+
console.log("");
|
|
1207
|
+
}
|
|
1208
|
+
function getConventionStr(cv) {
|
|
1209
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1210
|
+
}
|
|
1211
|
+
function displayRulesPreview(config) {
|
|
1212
|
+
console.log(`${import_chalk7.default.bold("Rules:")}`);
|
|
1213
|
+
console.log(` ${import_chalk7.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1214
|
+
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1215
|
+
console.log(
|
|
1216
|
+
` ${import_chalk7.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
|
|
1217
|
+
);
|
|
1218
|
+
} else if (config.rules.requireTests) {
|
|
1219
|
+
console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: yes`);
|
|
1220
|
+
} else {
|
|
1221
|
+
console.log(` ${import_chalk7.default.dim("\u2022")} Require test files: no`);
|
|
1222
|
+
}
|
|
1223
|
+
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1224
|
+
console.log(
|
|
1225
|
+
` ${import_chalk7.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
|
|
1226
|
+
);
|
|
1227
|
+
} else {
|
|
1228
|
+
console.log(` ${import_chalk7.default.dim("\u2022")} Enforce file naming: no`);
|
|
1229
|
+
}
|
|
1230
|
+
console.log(
|
|
1231
|
+
` ${import_chalk7.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1232
|
+
);
|
|
1233
|
+
console.log("");
|
|
1234
|
+
if (config.enforcement === "enforce") {
|
|
1235
|
+
console.log(`${import_chalk7.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
|
|
1236
|
+
} else {
|
|
1237
|
+
console.log(
|
|
1238
|
+
`${import_chalk7.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
console.log("");
|
|
1242
|
+
}
|
|
1243
|
+
|
|
986
1244
|
// src/utils/write-generated-files.ts
|
|
987
1245
|
var fs10 = __toESM(require("fs"), 1);
|
|
988
1246
|
var path11 = __toESM(require("path"), 1);
|
|
@@ -1012,60 +1270,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
1012
1270
|
// src/commands/init-hooks.ts
|
|
1013
1271
|
var fs11 = __toESM(require("fs"), 1);
|
|
1014
1272
|
var path12 = __toESM(require("path"), 1);
|
|
1015
|
-
var
|
|
1016
|
-
function setupClaudeCodeHook(projectRoot) {
|
|
1017
|
-
const claudeDir = path12.join(projectRoot, ".claude");
|
|
1018
|
-
if (!fs11.existsSync(claudeDir)) {
|
|
1019
|
-
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
1020
|
-
}
|
|
1021
|
-
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
1022
|
-
let settings = {};
|
|
1023
|
-
if (fs11.existsSync(settingsPath)) {
|
|
1024
|
-
try {
|
|
1025
|
-
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
1026
|
-
} catch {
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
const hooks = settings.hooks ?? {};
|
|
1030
|
-
const postToolUse = hooks.PostToolUse;
|
|
1031
|
-
if (Array.isArray(postToolUse)) {
|
|
1032
|
-
const hasViberails = postToolUse.some(
|
|
1033
|
-
(entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
|
|
1034
|
-
(h) => typeof h === "object" && h !== null && typeof h.command === "string" && h.command.includes("viberails")
|
|
1035
|
-
)
|
|
1036
|
-
);
|
|
1037
|
-
if (hasViberails) return;
|
|
1038
|
-
}
|
|
1039
|
-
const viberailsHook = {
|
|
1040
|
-
matcher: "Edit|Write",
|
|
1041
|
-
hooks: [
|
|
1042
|
-
{
|
|
1043
|
-
type: "command",
|
|
1044
|
-
command: "jq -r '.tool_input.file_path' | xargs npx viberails check --files"
|
|
1045
|
-
}
|
|
1046
|
-
]
|
|
1047
|
-
};
|
|
1048
|
-
if (!hooks.PostToolUse) {
|
|
1049
|
-
hooks.PostToolUse = [viberailsHook];
|
|
1050
|
-
} else if (Array.isArray(hooks.PostToolUse)) {
|
|
1051
|
-
hooks.PostToolUse.push(viberailsHook);
|
|
1052
|
-
}
|
|
1053
|
-
settings.hooks = hooks;
|
|
1054
|
-
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1055
|
-
`);
|
|
1056
|
-
console.log(` ${import_chalk5.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1057
|
-
}
|
|
1273
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
1058
1274
|
function setupPreCommitHook(projectRoot) {
|
|
1059
1275
|
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1060
1276
|
if (fs11.existsSync(lefthookPath)) {
|
|
1061
1277
|
addLefthookPreCommit(lefthookPath);
|
|
1062
|
-
console.log(` ${
|
|
1278
|
+
console.log(` ${import_chalk8.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1063
1279
|
return;
|
|
1064
1280
|
}
|
|
1065
1281
|
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1066
1282
|
if (fs11.existsSync(huskyDir)) {
|
|
1067
1283
|
writeHuskyPreCommit(huskyDir);
|
|
1068
|
-
console.log(` ${
|
|
1284
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1069
1285
|
return;
|
|
1070
1286
|
}
|
|
1071
1287
|
const gitDir = path12.join(projectRoot, ".git");
|
|
@@ -1075,7 +1291,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
1075
1291
|
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1076
1292
|
}
|
|
1077
1293
|
writeGitHookPreCommit(hooksDir);
|
|
1078
|
-
console.log(` ${
|
|
1294
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
1079
1295
|
}
|
|
1080
1296
|
}
|
|
1081
1297
|
function writeGitHookPreCommit(hooksDir) {
|
|
@@ -1105,10 +1321,72 @@ npx viberails check --staged
|
|
|
1105
1321
|
function addLefthookPreCommit(lefthookPath) {
|
|
1106
1322
|
const content = fs11.readFileSync(lefthookPath, "utf-8");
|
|
1107
1323
|
if (content.includes("viberails")) return;
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1324
|
+
const hasPreCommit = /^pre-commit:/m.test(content);
|
|
1325
|
+
if (hasPreCommit) {
|
|
1326
|
+
const commandBlock = ["", " viberails:", " run: npx viberails check --staged"].join(
|
|
1327
|
+
"\n"
|
|
1328
|
+
);
|
|
1329
|
+
const updated = `${content.trimEnd()}
|
|
1330
|
+
${commandBlock}
|
|
1331
|
+
`;
|
|
1332
|
+
fs11.writeFileSync(lefthookPath, updated);
|
|
1333
|
+
} else {
|
|
1334
|
+
const section = [
|
|
1335
|
+
"",
|
|
1336
|
+
"pre-commit:",
|
|
1337
|
+
" commands:",
|
|
1338
|
+
" viberails:",
|
|
1339
|
+
" run: npx viberails check --staged"
|
|
1340
|
+
].join("\n");
|
|
1341
|
+
fs11.writeFileSync(lefthookPath, `${content.trimEnd()}
|
|
1342
|
+
${section}
|
|
1343
|
+
`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
function detectHookManager(projectRoot) {
|
|
1347
|
+
if (fs11.existsSync(path12.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
1348
|
+
if (fs11.existsSync(path12.join(projectRoot, ".husky"))) return "Husky";
|
|
1349
|
+
if (fs11.existsSync(path12.join(projectRoot, ".git"))) return "git hook";
|
|
1350
|
+
return void 0;
|
|
1351
|
+
}
|
|
1352
|
+
function setupClaudeCodeHook(projectRoot) {
|
|
1353
|
+
const claudeDir = path12.join(projectRoot, ".claude");
|
|
1354
|
+
if (!fs11.existsSync(claudeDir)) {
|
|
1355
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
1356
|
+
}
|
|
1357
|
+
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
1358
|
+
let settings = {};
|
|
1359
|
+
if (fs11.existsSync(settingsPath)) {
|
|
1360
|
+
try {
|
|
1361
|
+
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
1362
|
+
} catch {
|
|
1363
|
+
console.warn(
|
|
1364
|
+
` ${import_chalk8.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
|
|
1365
|
+
);
|
|
1366
|
+
settings = {};
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
const hooks = settings.hooks ?? {};
|
|
1370
|
+
const existing = hooks.PostToolUse ?? [];
|
|
1371
|
+
if (existing.some((h) => JSON.stringify(h).includes("viberails"))) return;
|
|
1372
|
+
const extractFile = `node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input?.file_path??'')}catch{}"`;
|
|
1373
|
+
const hookCommand = `FILE=$(${extractFile}) && [ -n "$FILE" ] && npx viberails check --files "$FILE" --format json; exit 0`;
|
|
1374
|
+
hooks.PostToolUse = [
|
|
1375
|
+
...existing,
|
|
1376
|
+
{
|
|
1377
|
+
matcher: "Edit|Write",
|
|
1378
|
+
hooks: [
|
|
1379
|
+
{
|
|
1380
|
+
type: "command",
|
|
1381
|
+
command: hookCommand
|
|
1382
|
+
}
|
|
1383
|
+
]
|
|
1384
|
+
}
|
|
1385
|
+
];
|
|
1386
|
+
settings.hooks = hooks;
|
|
1387
|
+
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1111
1388
|
`);
|
|
1389
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1112
1390
|
}
|
|
1113
1391
|
function writeHuskyPreCommit(huskyDir) {
|
|
1114
1392
|
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
@@ -1124,187 +1402,6 @@ npx viberails check --staged
|
|
|
1124
1402
|
fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
1125
1403
|
}
|
|
1126
1404
|
|
|
1127
|
-
// src/commands/init-wizard.ts
|
|
1128
|
-
var p = __toESM(require("@clack/prompts"), 1);
|
|
1129
|
-
var import_types4 = require("@viberails/types");
|
|
1130
|
-
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
1131
|
-
|
|
1132
|
-
// src/display.ts
|
|
1133
|
-
var import_types3 = require("@viberails/types");
|
|
1134
|
-
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
1135
|
-
|
|
1136
|
-
// src/display-helpers.ts
|
|
1137
|
-
var import_types = require("@viberails/types");
|
|
1138
|
-
function groupByRole(directories) {
|
|
1139
|
-
const map = /* @__PURE__ */ new Map();
|
|
1140
|
-
for (const dir of directories) {
|
|
1141
|
-
if (dir.role === "unknown") continue;
|
|
1142
|
-
const existing = map.get(dir.role);
|
|
1143
|
-
if (existing) {
|
|
1144
|
-
existing.dirs.push(dir);
|
|
1145
|
-
} else {
|
|
1146
|
-
map.set(dir.role, { dirs: [dir] });
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
const groups = [];
|
|
1150
|
-
for (const [role, { dirs }] of map) {
|
|
1151
|
-
const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
|
|
1152
|
-
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
1153
|
-
groups.push({
|
|
1154
|
-
role,
|
|
1155
|
-
label,
|
|
1156
|
-
dirCount: dirs.length,
|
|
1157
|
-
totalFiles,
|
|
1158
|
-
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
return groups;
|
|
1162
|
-
}
|
|
1163
|
-
function formatSummary(stats, packageCount) {
|
|
1164
|
-
const parts = [];
|
|
1165
|
-
if (packageCount && packageCount > 1) {
|
|
1166
|
-
parts.push(`${packageCount} packages`);
|
|
1167
|
-
}
|
|
1168
|
-
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
1169
|
-
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
1170
|
-
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
1171
|
-
return parts.join(" \xB7 ");
|
|
1172
|
-
}
|
|
1173
|
-
function formatRoleGroup(group) {
|
|
1174
|
-
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1175
|
-
if (group.singlePath) {
|
|
1176
|
-
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1177
|
-
}
|
|
1178
|
-
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1179
|
-
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// src/display-monorepo.ts
|
|
1183
|
-
var import_types2 = require("@viberails/types");
|
|
1184
|
-
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1185
|
-
|
|
1186
|
-
// src/display.ts
|
|
1187
|
-
function formatItem(item, nameMap) {
|
|
1188
|
-
const name = nameMap?.[item.name] ?? item.name;
|
|
1189
|
-
return item.version ? `${name} ${item.version}` : name;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// src/commands/init-wizard.ts
|
|
1193
|
-
var DEFAULT_WIZARD_RESULT = {
|
|
1194
|
-
enforcement: "warn",
|
|
1195
|
-
checks: {
|
|
1196
|
-
fileSize: true,
|
|
1197
|
-
naming: true,
|
|
1198
|
-
tests: true,
|
|
1199
|
-
boundaries: false
|
|
1200
|
-
},
|
|
1201
|
-
integration: ["pre-commit"]
|
|
1202
|
-
};
|
|
1203
|
-
async function runWizard(scanResult) {
|
|
1204
|
-
const isMonorepo = scanResult.packages.length > 1;
|
|
1205
|
-
displayScanSummary(scanResult);
|
|
1206
|
-
const enforcement = await p.select({
|
|
1207
|
-
message: "How strict should viberails be?",
|
|
1208
|
-
initialValue: "warn",
|
|
1209
|
-
options: [
|
|
1210
|
-
{ value: "warn", label: "Warn", hint: "show issues, never block commits" },
|
|
1211
|
-
{ value: "enforce", label: "Enforce", hint: "block commits with violations" }
|
|
1212
|
-
]
|
|
1213
|
-
});
|
|
1214
|
-
if (p.isCancel(enforcement)) {
|
|
1215
|
-
p.cancel("Setup cancelled.");
|
|
1216
|
-
return null;
|
|
1217
|
-
}
|
|
1218
|
-
const checkOptions = [
|
|
1219
|
-
{ value: "fileSize", label: "File size limit (300 lines)" },
|
|
1220
|
-
{ value: "naming", label: "File naming conventions" },
|
|
1221
|
-
{ value: "tests", label: "Missing test files" }
|
|
1222
|
-
];
|
|
1223
|
-
if (isMonorepo) {
|
|
1224
|
-
checkOptions.push({ value: "boundaries", label: "Import boundaries" });
|
|
1225
|
-
}
|
|
1226
|
-
const enabledChecks = await p.multiselect({
|
|
1227
|
-
message: "Which checks should viberails run?",
|
|
1228
|
-
options: checkOptions,
|
|
1229
|
-
initialValues: ["fileSize", "naming", "tests"],
|
|
1230
|
-
required: false
|
|
1231
|
-
});
|
|
1232
|
-
if (p.isCancel(enabledChecks)) {
|
|
1233
|
-
p.cancel("Setup cancelled.");
|
|
1234
|
-
return null;
|
|
1235
|
-
}
|
|
1236
|
-
const checks = {
|
|
1237
|
-
fileSize: enabledChecks.includes("fileSize"),
|
|
1238
|
-
naming: enabledChecks.includes("naming"),
|
|
1239
|
-
tests: enabledChecks.includes("tests"),
|
|
1240
|
-
boundaries: enabledChecks.includes("boundaries")
|
|
1241
|
-
};
|
|
1242
|
-
const integrationOptions = [
|
|
1243
|
-
{ value: "pre-commit", label: "Git pre-commit hook", hint: "runs on every commit" },
|
|
1244
|
-
{
|
|
1245
|
-
value: "claude-hook",
|
|
1246
|
-
label: "Claude Code hook",
|
|
1247
|
-
hint: "checks files as Claude edits them"
|
|
1248
|
-
},
|
|
1249
|
-
{ value: "context-only", label: "Context files only", hint: "no hooks" }
|
|
1250
|
-
];
|
|
1251
|
-
const integration = await p.multiselect({
|
|
1252
|
-
message: "Where should checks run?",
|
|
1253
|
-
options: integrationOptions,
|
|
1254
|
-
initialValues: ["pre-commit"],
|
|
1255
|
-
required: true
|
|
1256
|
-
});
|
|
1257
|
-
if (p.isCancel(integration)) {
|
|
1258
|
-
p.cancel("Setup cancelled.");
|
|
1259
|
-
return null;
|
|
1260
|
-
}
|
|
1261
|
-
const finalIntegration = integration.includes("context-only") ? ["context-only"] : integration;
|
|
1262
|
-
return {
|
|
1263
|
-
enforcement,
|
|
1264
|
-
checks,
|
|
1265
|
-
integration: finalIntegration
|
|
1266
|
-
};
|
|
1267
|
-
}
|
|
1268
|
-
function displayScanSummary(scanResult) {
|
|
1269
|
-
const { stack } = scanResult;
|
|
1270
|
-
const parts = [];
|
|
1271
|
-
if (stack.framework) parts.push(formatItem(stack.framework, import_types4.FRAMEWORK_NAMES));
|
|
1272
|
-
parts.push(formatItem(stack.language));
|
|
1273
|
-
if (stack.styling) parts.push(formatItem(stack.styling, import_types4.STYLING_NAMES));
|
|
1274
|
-
if (stack.backend) parts.push(formatItem(stack.backend, import_types4.FRAMEWORK_NAMES));
|
|
1275
|
-
p.log.info(`${import_chalk8.default.bold("Stack:")} ${parts.join(", ")}`);
|
|
1276
|
-
if (stack.linter || stack.formatter || stack.testRunner || stack.packageManager) {
|
|
1277
|
-
const tools = [];
|
|
1278
|
-
if (stack.linter) tools.push(formatItem(stack.linter));
|
|
1279
|
-
if (stack.formatter && stack.formatter !== stack.linter)
|
|
1280
|
-
tools.push(formatItem(stack.formatter));
|
|
1281
|
-
if (stack.testRunner) tools.push(formatItem(stack.testRunner));
|
|
1282
|
-
if (stack.packageManager) tools.push(formatItem(stack.packageManager));
|
|
1283
|
-
p.log.info(`${import_chalk8.default.bold("Tools:")} ${tools.join(", ")}`);
|
|
1284
|
-
}
|
|
1285
|
-
if (stack.libraries.length > 0) {
|
|
1286
|
-
const libs = stack.libraries.map((lib) => formatItem(lib, import_types4.LIBRARY_NAMES)).join(", ");
|
|
1287
|
-
p.log.info(`${import_chalk8.default.bold("Libraries:")} ${libs}`);
|
|
1288
|
-
}
|
|
1289
|
-
const groups = groupByRole(scanResult.structure.directories);
|
|
1290
|
-
if (groups.length > 0) {
|
|
1291
|
-
const structParts = groups.map((g) => formatRoleGroup(g));
|
|
1292
|
-
p.log.info(`${import_chalk8.default.bold("Structure:")} ${structParts.join(", ")}`);
|
|
1293
|
-
}
|
|
1294
|
-
const conventionEntries = Object.entries(scanResult.conventions).filter(
|
|
1295
|
-
([, c]) => c.confidence !== "low"
|
|
1296
|
-
);
|
|
1297
|
-
if (conventionEntries.length > 0) {
|
|
1298
|
-
const convParts = conventionEntries.map(([, c]) => {
|
|
1299
|
-
const pct = Math.round(c.consistency);
|
|
1300
|
-
return `${c.value} (${pct}%)`;
|
|
1301
|
-
});
|
|
1302
|
-
p.log.info(`${import_chalk8.default.bold("Conventions:")} ${convParts.join(", ")}`);
|
|
1303
|
-
}
|
|
1304
|
-
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1305
|
-
p.log.info(`${import_chalk8.default.bold("Summary:")} ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
1405
|
// src/commands/init.ts
|
|
1309
1406
|
var CONFIG_FILE4 = "viberails.config.json";
|
|
1310
1407
|
function filterHighConfidence(conventions) {
|
|
@@ -1319,13 +1416,6 @@ function filterHighConfidence(conventions) {
|
|
|
1319
1416
|
}
|
|
1320
1417
|
return filtered;
|
|
1321
1418
|
}
|
|
1322
|
-
function applyWizardResult(config, wizard) {
|
|
1323
|
-
config.enforcement = wizard.enforcement;
|
|
1324
|
-
if (!wizard.checks.fileSize) config.rules.maxFileLines = 0;
|
|
1325
|
-
config.rules.enforceNaming = wizard.checks.naming;
|
|
1326
|
-
config.rules.requireTests = wizard.checks.tests;
|
|
1327
|
-
config.rules.enforceBoundaries = wizard.checks.boundaries;
|
|
1328
|
-
}
|
|
1329
1419
|
async function initCommand(options, cwd) {
|
|
1330
1420
|
const startDir = cwd ?? process.cwd();
|
|
1331
1421
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1341,65 +1431,78 @@ async function initCommand(options, cwd) {
|
|
|
1341
1431
|
);
|
|
1342
1432
|
return;
|
|
1343
1433
|
}
|
|
1344
|
-
|
|
1345
|
-
const s = p2.spinner();
|
|
1346
|
-
s.start("Scanning project...");
|
|
1434
|
+
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1347
1435
|
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
1348
|
-
s.stop("Scan complete");
|
|
1349
|
-
if (scanResult.statistics.totalFiles === 0) {
|
|
1350
|
-
p2.log.warn(
|
|
1351
|
-
`No source files detected. viberails will generate context with minimal content.
|
|
1352
|
-
Run ${import_chalk9.default.cyan("viberails sync")} after adding source files.`
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1355
|
-
let wizard;
|
|
1356
|
-
if (options.yes) {
|
|
1357
|
-
wizard = { ...DEFAULT_WIZARD_RESULT };
|
|
1358
|
-
} else {
|
|
1359
|
-
const result = await runWizard(scanResult);
|
|
1360
|
-
if (!result) return;
|
|
1361
|
-
wizard = result;
|
|
1362
|
-
}
|
|
1363
1436
|
const config = (0, import_config4.generateConfig)(scanResult);
|
|
1364
1437
|
if (options.yes) {
|
|
1365
1438
|
config.conventions = filterHighConfidence(config.conventions);
|
|
1366
1439
|
}
|
|
1367
|
-
|
|
1368
|
-
if (
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
s.stop("No boundary rules could be inferred");
|
|
1380
|
-
config.rules.enforceBoundaries = false;
|
|
1440
|
+
displayScanResults(scanResult);
|
|
1441
|
+
if (scanResult.statistics.totalFiles === 0) {
|
|
1442
|
+
console.log(
|
|
1443
|
+
import_chalk9.default.yellow("!") + " No source files detected. viberails will generate context with minimal content.\n Run " + import_chalk9.default.cyan("viberails sync") + " after adding source files.\n"
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
displayRulesPreview(config);
|
|
1447
|
+
if (!options.yes) {
|
|
1448
|
+
const accepted = await confirm("Proceed with these settings?");
|
|
1449
|
+
if (!accepted) {
|
|
1450
|
+
console.log("Aborted.");
|
|
1451
|
+
return;
|
|
1381
1452
|
}
|
|
1382
1453
|
}
|
|
1454
|
+
if (config.workspace && config.workspace.packages.length > 0) {
|
|
1455
|
+
let shouldInfer = options.yes;
|
|
1456
|
+
if (!options.yes) {
|
|
1457
|
+
console.log(import_chalk9.default.dim(" Scans imports between packages to suggest dependency rules"));
|
|
1458
|
+
shouldInfer = await confirm("Infer boundary rules from import patterns?");
|
|
1459
|
+
}
|
|
1460
|
+
if (shouldInfer) {
|
|
1461
|
+
console.log(import_chalk9.default.dim("Building import graph..."));
|
|
1462
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1463
|
+
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1464
|
+
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
1465
|
+
const inferred = inferBoundaries(graph);
|
|
1466
|
+
if (inferred.length > 0) {
|
|
1467
|
+
config.boundaries = inferred;
|
|
1468
|
+
config.rules.enforceBoundaries = true;
|
|
1469
|
+
console.log(` ${import_chalk9.default.green("\u2713")} Inferred ${inferred.length} boundary rules`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const hookManager = detectHookManager(projectRoot);
|
|
1474
|
+
let integrations = { preCommitHook: true, claudeCodeHook: true };
|
|
1475
|
+
if (!options.yes) {
|
|
1476
|
+
console.log("");
|
|
1477
|
+
integrations = await selectIntegrations(hookManager);
|
|
1478
|
+
}
|
|
1383
1479
|
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1384
1480
|
`);
|
|
1385
1481
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1386
1482
|
updateGitignore(projectRoot);
|
|
1387
|
-
|
|
1483
|
+
console.log(`
|
|
1484
|
+
${import_chalk9.default.bold("Created:")}`);
|
|
1485
|
+
console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1486
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md`);
|
|
1487
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json`);
|
|
1488
|
+
if (integrations.preCommitHook) {
|
|
1388
1489
|
setupPreCommitHook(projectRoot);
|
|
1389
1490
|
}
|
|
1390
|
-
if (
|
|
1491
|
+
if (integrations.claudeCodeHook) {
|
|
1391
1492
|
setupClaudeCodeHook(projectRoot);
|
|
1392
1493
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
);
|
|
1494
|
+
const filesToCommit = [
|
|
1495
|
+
`${import_chalk9.default.cyan("viberails.config.json")}`,
|
|
1496
|
+
import_chalk9.default.cyan(".viberails/context.md")
|
|
1497
|
+
];
|
|
1498
|
+
if (integrations.claudeCodeHook) {
|
|
1499
|
+
filesToCommit.push(import_chalk9.default.cyan(".claude/settings.json"));
|
|
1500
|
+
}
|
|
1501
|
+
console.log(`
|
|
1502
|
+
${import_chalk9.default.bold("Next steps:")}`);
|
|
1503
|
+
console.log(` 1. Review ${import_chalk9.default.cyan("viberails.config.json")} and adjust rules`);
|
|
1504
|
+
console.log(` 2. Commit ${filesToCommit.join(", ")}`);
|
|
1505
|
+
console.log(` 3. Run ${import_chalk9.default.cyan("viberails check")} to verify your project passes`);
|
|
1403
1506
|
}
|
|
1404
1507
|
function updateGitignore(projectRoot) {
|
|
1405
1508
|
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
@@ -1409,8 +1512,9 @@ function updateGitignore(projectRoot) {
|
|
|
1409
1512
|
}
|
|
1410
1513
|
if (!content.includes(".viberails/scan-result.json")) {
|
|
1411
1514
|
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
1412
|
-
|
|
1413
|
-
|
|
1515
|
+
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
1516
|
+
`;
|
|
1517
|
+
fs12.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
1414
1518
|
}
|
|
1415
1519
|
}
|
|
1416
1520
|
|
|
@@ -1434,18 +1538,30 @@ async function syncCommand(cwd) {
|
|
|
1434
1538
|
console.log(import_chalk10.default.dim("Scanning project..."));
|
|
1435
1539
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
1436
1540
|
const merged = (0, import_config5.mergeConfig)(existing, scanResult);
|
|
1437
|
-
|
|
1541
|
+
const existingJson = JSON.stringify(existing, null, 2);
|
|
1542
|
+
const mergedJson = JSON.stringify(merged, null, 2);
|
|
1543
|
+
const configChanged = existingJson !== mergedJson;
|
|
1544
|
+
if (configChanged) {
|
|
1545
|
+
console.log(
|
|
1546
|
+
` ${import_chalk10.default.yellow("!")} Config updated \u2014 review ${import_chalk10.default.cyan(CONFIG_FILE5)} for changes`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
fs13.writeFileSync(configPath, `${mergedJson}
|
|
1438
1550
|
`);
|
|
1439
1551
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1440
1552
|
console.log(`
|
|
1441
1553
|
${import_chalk10.default.bold("Synced:")}`);
|
|
1442
|
-
|
|
1554
|
+
if (configChanged) {
|
|
1555
|
+
console.log(` ${import_chalk10.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
|
|
1556
|
+
} else {
|
|
1557
|
+
console.log(` ${import_chalk10.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
|
|
1558
|
+
}
|
|
1443
1559
|
console.log(` ${import_chalk10.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
1444
1560
|
console.log(` ${import_chalk10.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1445
1561
|
}
|
|
1446
1562
|
|
|
1447
1563
|
// src/index.ts
|
|
1448
|
-
var VERSION = "0.3.
|
|
1564
|
+
var VERSION = "0.3.1";
|
|
1449
1565
|
var program = new import_commander.Command();
|
|
1450
1566
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1451
1567
|
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) => {
|
|
@@ -1466,12 +1582,13 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1466
1582
|
process.exit(1);
|
|
1467
1583
|
}
|
|
1468
1584
|
});
|
|
1469
|
-
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(
|
|
1585
|
+
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(
|
|
1470
1586
|
async (options) => {
|
|
1471
1587
|
try {
|
|
1472
1588
|
const exitCode = await checkCommand({
|
|
1473
1589
|
...options,
|
|
1474
|
-
noBoundaries: options.boundaries === false
|
|
1590
|
+
noBoundaries: options.boundaries === false,
|
|
1591
|
+
format: options.format === "json" ? "json" : "text"
|
|
1475
1592
|
});
|
|
1476
1593
|
process.exit(exitCode);
|
|
1477
1594
|
} catch (err) {
|