viberails 0.3.1 → 0.3.3
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 +682 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +693 -224
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -34,14 +34,14 @@ __export(index_exports, {
|
|
|
34
34
|
VERSION: () => VERSION
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
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_chalk = __toESM(require("chalk"), 1);
|
|
45
45
|
|
|
46
46
|
// src/utils/find-project-root.ts
|
|
47
47
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -61,43 +61,110 @@ function findProjectRoot(startDir) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// src/utils/prompt.ts
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
var clack = __toESM(require("@clack/prompts"), 1);
|
|
65
|
+
function assertNotCancelled(value) {
|
|
66
|
+
if (clack.isCancel(value)) {
|
|
67
|
+
clack.cancel("Setup cancelled.");
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function confirm2(message) {
|
|
72
|
+
const result = await clack.confirm({ message, initialValue: true });
|
|
73
|
+
assertNotCancelled(result);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
async function confirmDangerous(message) {
|
|
77
|
+
const result = await clack.confirm({ message, initialValue: false });
|
|
78
|
+
assertNotCancelled(result);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
async function promptInitDecision() {
|
|
82
|
+
const result = await clack.select({
|
|
83
|
+
message: "Accept these settings?",
|
|
84
|
+
options: [
|
|
85
|
+
{ value: "accept", label: "Yes, looks good", hint: "recommended" },
|
|
86
|
+
{ value: "customize", label: "Let me customize" }
|
|
87
|
+
]
|
|
77
88
|
});
|
|
89
|
+
assertNotCancelled(result);
|
|
90
|
+
return result;
|
|
78
91
|
}
|
|
79
|
-
async function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
async function promptRuleCustomization(defaults) {
|
|
93
|
+
const maxFileLinesResult = await clack.text({
|
|
94
|
+
message: "Maximum lines per source file?",
|
|
95
|
+
placeholder: String(defaults.maxFileLines),
|
|
96
|
+
initialValue: String(defaults.maxFileLines),
|
|
97
|
+
validate: (v) => {
|
|
98
|
+
const n = Number.parseInt(v, 10);
|
|
99
|
+
if (Number.isNaN(n) || n < 1) return "Enter a positive number";
|
|
100
|
+
}
|
|
86
101
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (trimmed === "") resolve4(defaultYes);
|
|
92
|
-
else resolve4(trimmed === "y" || trimmed === "yes");
|
|
93
|
-
});
|
|
102
|
+
assertNotCancelled(maxFileLinesResult);
|
|
103
|
+
const requireTestsResult = await clack.confirm({
|
|
104
|
+
message: "Require matching test files for source files?",
|
|
105
|
+
initialValue: defaults.requireTests
|
|
94
106
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
assertNotCancelled(requireTestsResult);
|
|
108
|
+
const namingLabel = defaults.fileNamingValue ? `Enforce file naming? (detected: ${defaults.fileNamingValue})` : "Enforce file naming?";
|
|
109
|
+
const enforceNamingResult = await clack.confirm({
|
|
110
|
+
message: namingLabel,
|
|
111
|
+
initialValue: defaults.enforceNaming
|
|
112
|
+
});
|
|
113
|
+
assertNotCancelled(enforceNamingResult);
|
|
114
|
+
const enforcementResult = await clack.select({
|
|
115
|
+
message: "Enforcement mode",
|
|
116
|
+
options: [
|
|
117
|
+
{
|
|
118
|
+
value: "warn",
|
|
119
|
+
label: "warn",
|
|
120
|
+
hint: "show violations but don't block commits (recommended)"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
value: "enforce",
|
|
124
|
+
label: "enforce",
|
|
125
|
+
hint: "block commits with violations"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
initialValue: defaults.enforcement
|
|
129
|
+
});
|
|
130
|
+
assertNotCancelled(enforcementResult);
|
|
131
|
+
return {
|
|
132
|
+
maxFileLines: Number.parseInt(maxFileLinesResult, 10),
|
|
133
|
+
requireTests: requireTestsResult,
|
|
134
|
+
enforceNaming: enforceNamingResult,
|
|
135
|
+
enforcement: enforcementResult
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function promptIntegrations(hookManager) {
|
|
139
|
+
const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook (git hook)";
|
|
140
|
+
const result = await clack.multiselect({
|
|
141
|
+
message: "Set up integrations?",
|
|
142
|
+
options: [
|
|
143
|
+
{
|
|
144
|
+
value: "preCommit",
|
|
145
|
+
label: hookLabel,
|
|
146
|
+
hint: "runs checks when you commit"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
value: "claude",
|
|
150
|
+
label: "Claude Code hook",
|
|
151
|
+
hint: "checks files when Claude edits them"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
value: "claudeMd",
|
|
155
|
+
label: "CLAUDE.md reference",
|
|
156
|
+
hint: "appends @.viberails/context.md so Claude loads rules automatically"
|
|
157
|
+
}
|
|
158
|
+
],
|
|
159
|
+
initialValues: ["preCommit", "claude", "claudeMd"],
|
|
160
|
+
required: false
|
|
161
|
+
});
|
|
162
|
+
assertNotCancelled(result);
|
|
163
|
+
return {
|
|
164
|
+
preCommitHook: result.includes("preCommit"),
|
|
165
|
+
claudeCodeHook: result.includes("claude"),
|
|
166
|
+
claudeMdRef: result.includes("claudeMd")
|
|
167
|
+
};
|
|
101
168
|
}
|
|
102
169
|
|
|
103
170
|
// src/utils/resolve-workspace-packages.ts
|
|
@@ -154,67 +221,65 @@ async function boundariesCommand(options, cwd) {
|
|
|
154
221
|
displayRules(config);
|
|
155
222
|
}
|
|
156
223
|
function displayRules(config) {
|
|
157
|
-
if (!config.boundaries || config.boundaries.length === 0) {
|
|
158
|
-
console.log(
|
|
159
|
-
console.log(`Run ${
|
|
224
|
+
if (!config.boundaries || Object.keys(config.boundaries.deny).length === 0) {
|
|
225
|
+
console.log(import_chalk.default.yellow("No boundary rules configured."));
|
|
226
|
+
console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
160
227
|
return;
|
|
161
228
|
}
|
|
162
|
-
const
|
|
163
|
-
const
|
|
229
|
+
const { deny } = config.boundaries;
|
|
230
|
+
const sources = Object.keys(deny).filter((k) => deny[k].length > 0);
|
|
231
|
+
const totalRules = sources.reduce((sum, k) => sum + deny[k].length, 0);
|
|
164
232
|
console.log(`
|
|
165
|
-
${
|
|
233
|
+
${import_chalk.default.bold(`Boundary rules (${totalRules} deny rules):`)}
|
|
166
234
|
`);
|
|
167
|
-
for (const
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
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}`);
|
|
235
|
+
for (const source of sources) {
|
|
236
|
+
for (const target of deny[source]) {
|
|
237
|
+
console.log(` ${import_chalk.default.red("\u2717")} ${source} \u2192 ${target}`);
|
|
238
|
+
}
|
|
173
239
|
}
|
|
174
240
|
console.log(
|
|
175
241
|
`
|
|
176
|
-
Enforcement: ${config.rules.enforceBoundaries ?
|
|
242
|
+
Enforcement: ${config.rules.enforceBoundaries ? import_chalk.default.green("on") : import_chalk.default.yellow("off")}`
|
|
177
243
|
);
|
|
178
244
|
}
|
|
179
245
|
async function inferAndDisplay(projectRoot, config, configPath) {
|
|
180
|
-
console.log(
|
|
246
|
+
console.log(import_chalk.default.dim("Analyzing imports..."));
|
|
181
247
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
182
248
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
183
249
|
const graph = await buildImportGraph(projectRoot, {
|
|
184
250
|
packages,
|
|
185
251
|
ignore: config.ignore
|
|
186
252
|
});
|
|
187
|
-
console.log(
|
|
253
|
+
console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
188
254
|
const inferred = inferBoundaries(graph);
|
|
189
|
-
|
|
190
|
-
|
|
255
|
+
const sources = Object.keys(inferred.deny).filter((k) => inferred.deny[k].length > 0);
|
|
256
|
+
const totalRules = sources.reduce((sum, k) => sum + inferred.deny[k].length, 0);
|
|
257
|
+
if (totalRules === 0) {
|
|
258
|
+
console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
|
|
191
259
|
return;
|
|
192
260
|
}
|
|
193
|
-
const allow = inferred.filter((r) => r.allow);
|
|
194
|
-
const deny = inferred.filter((r) => !r.allow);
|
|
195
261
|
console.log(`
|
|
196
|
-
${
|
|
262
|
+
${import_chalk.default.bold("Inferred boundary rules:")}
|
|
197
263
|
`);
|
|
198
|
-
for (const
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
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}`);
|
|
264
|
+
for (const source of sources) {
|
|
265
|
+
for (const target of inferred.deny[source]) {
|
|
266
|
+
console.log(` ${import_chalk.default.red("\u2717")} ${source} \u2192 ${target}`);
|
|
267
|
+
}
|
|
204
268
|
}
|
|
205
269
|
console.log(`
|
|
206
|
-
${
|
|
207
|
-
|
|
270
|
+
${totalRules} denied`);
|
|
271
|
+
console.log("");
|
|
272
|
+
const shouldSave = await confirm2("Save to viberails.config.json?");
|
|
208
273
|
if (shouldSave) {
|
|
209
274
|
config.boundaries = inferred;
|
|
210
275
|
config.rules.enforceBoundaries = true;
|
|
211
276
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
212
277
|
`);
|
|
213
|
-
console.log(`${
|
|
278
|
+
console.log(`${import_chalk.default.green("\u2713")} Saved ${totalRules} rules`);
|
|
214
279
|
}
|
|
215
280
|
}
|
|
216
281
|
async function showGraph(projectRoot, config) {
|
|
217
|
-
console.log(
|
|
282
|
+
console.log(import_chalk.default.dim("Building import graph..."));
|
|
218
283
|
const { buildImportGraph } = await import("@viberails/graph");
|
|
219
284
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
220
285
|
const graph = await buildImportGraph(projectRoot, {
|
|
@@ -222,20 +287,20 @@ async function showGraph(projectRoot, config) {
|
|
|
222
287
|
ignore: config.ignore
|
|
223
288
|
});
|
|
224
289
|
console.log(`
|
|
225
|
-
${
|
|
290
|
+
${import_chalk.default.bold("Import dependency graph:")}
|
|
226
291
|
`);
|
|
227
292
|
console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
|
|
228
293
|
`);
|
|
229
294
|
if (graph.packages.length > 0) {
|
|
230
295
|
for (const pkg of graph.packages) {
|
|
231
296
|
const deps = pkg.internalDeps.length > 0 ? `
|
|
232
|
-
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` :
|
|
297
|
+
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk.default.dim(" (no internal deps)");
|
|
233
298
|
console.log(` ${pkg.name}${deps}`);
|
|
234
299
|
}
|
|
235
300
|
}
|
|
236
301
|
if (graph.cycles.length > 0) {
|
|
237
302
|
console.log(`
|
|
238
|
-
${
|
|
303
|
+
${import_chalk.default.yellow("Cycles detected:")}`);
|
|
239
304
|
for (const cycle of graph.cycles) {
|
|
240
305
|
const paths = cycle.map((f) => path3.relative(projectRoot, f));
|
|
241
306
|
console.log(` ${paths.join(" \u2192 ")}`);
|
|
@@ -247,7 +312,7 @@ ${import_chalk2.default.yellow("Cycles detected:")}`);
|
|
|
247
312
|
var fs6 = __toESM(require("fs"), 1);
|
|
248
313
|
var path6 = __toESM(require("path"), 1);
|
|
249
314
|
var import_config2 = require("@viberails/config");
|
|
250
|
-
var
|
|
315
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
251
316
|
|
|
252
317
|
// src/commands/check-config.ts
|
|
253
318
|
function resolveConfigForFile(relPath, config) {
|
|
@@ -485,12 +550,12 @@ function printGroupedViolations(violations, limit) {
|
|
|
485
550
|
const toShow = group.slice(0, remaining);
|
|
486
551
|
const hidden = group.length - toShow.length;
|
|
487
552
|
for (const v of toShow) {
|
|
488
|
-
const icon = v.severity === "error" ?
|
|
489
|
-
console.log(`${icon} ${
|
|
553
|
+
const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
|
|
554
|
+
console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
490
555
|
}
|
|
491
556
|
totalShown += toShow.length;
|
|
492
557
|
if (hidden > 0) {
|
|
493
|
-
console.log(
|
|
558
|
+
console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
494
559
|
}
|
|
495
560
|
}
|
|
496
561
|
}
|
|
@@ -508,13 +573,13 @@ async function checkCommand(options, cwd) {
|
|
|
508
573
|
const startDir = cwd ?? process.cwd();
|
|
509
574
|
const projectRoot = findProjectRoot(startDir);
|
|
510
575
|
if (!projectRoot) {
|
|
511
|
-
console.error(`${
|
|
576
|
+
console.error(`${import_chalk2.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
512
577
|
return 1;
|
|
513
578
|
}
|
|
514
579
|
const configPath = path6.join(projectRoot, CONFIG_FILE2);
|
|
515
580
|
if (!fs6.existsSync(configPath)) {
|
|
516
581
|
console.error(
|
|
517
|
-
`${
|
|
582
|
+
`${import_chalk2.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
518
583
|
);
|
|
519
584
|
return 1;
|
|
520
585
|
}
|
|
@@ -528,7 +593,7 @@ async function checkCommand(options, cwd) {
|
|
|
528
593
|
filesToCheck = getAllSourceFiles(projectRoot, config);
|
|
529
594
|
}
|
|
530
595
|
if (filesToCheck.length === 0) {
|
|
531
|
-
console.log(`${
|
|
596
|
+
console.log(`${import_chalk2.default.green("\u2713")} No files to check.`);
|
|
532
597
|
return 0;
|
|
533
598
|
}
|
|
534
599
|
const violations = [];
|
|
@@ -569,7 +634,7 @@ async function checkCommand(options, cwd) {
|
|
|
569
634
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
570
635
|
violations.push(...testViolations);
|
|
571
636
|
}
|
|
572
|
-
if (config.rules.enforceBoundaries && config.boundaries && config.boundaries.length > 0 && !options.noBoundaries) {
|
|
637
|
+
if (config.rules.enforceBoundaries && config.boundaries && Object.keys(config.boundaries.deny).length > 0 && !options.noBoundaries) {
|
|
573
638
|
const startTime = Date.now();
|
|
574
639
|
const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
|
|
575
640
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
@@ -585,12 +650,12 @@ async function checkCommand(options, cwd) {
|
|
|
585
650
|
violations.push({
|
|
586
651
|
file: relFile,
|
|
587
652
|
rule: "boundary-violation",
|
|
588
|
-
message: `Imports "${bv.specifier}" violating boundary: ${bv.rule.from} \u2192 ${bv.rule.to}
|
|
653
|
+
message: `Imports "${bv.specifier}" violating boundary: ${bv.rule.from} \u2192 ${bv.rule.to}`,
|
|
589
654
|
severity
|
|
590
655
|
});
|
|
591
656
|
}
|
|
592
657
|
const elapsed = Date.now() - startTime;
|
|
593
|
-
console.log(
|
|
658
|
+
console.log(import_chalk2.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
|
|
594
659
|
}
|
|
595
660
|
if (options.format === "json") {
|
|
596
661
|
console.log(
|
|
@@ -603,7 +668,7 @@ async function checkCommand(options, cwd) {
|
|
|
603
668
|
return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
|
|
604
669
|
}
|
|
605
670
|
if (violations.length === 0) {
|
|
606
|
-
console.log(`${
|
|
671
|
+
console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
607
672
|
return 0;
|
|
608
673
|
}
|
|
609
674
|
if (!options.quiet) {
|
|
@@ -611,7 +676,7 @@ async function checkCommand(options, cwd) {
|
|
|
611
676
|
}
|
|
612
677
|
printSummary(violations);
|
|
613
678
|
if (config.enforcement === "enforce") {
|
|
614
|
-
console.log(
|
|
679
|
+
console.log(import_chalk2.default.red("Fix violations before committing."));
|
|
615
680
|
return 1;
|
|
616
681
|
}
|
|
617
682
|
return 0;
|
|
@@ -621,23 +686,22 @@ async function checkCommand(options, cwd) {
|
|
|
621
686
|
var fs9 = __toESM(require("fs"), 1);
|
|
622
687
|
var path10 = __toESM(require("path"), 1);
|
|
623
688
|
var import_config3 = require("@viberails/config");
|
|
624
|
-
var
|
|
689
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
625
690
|
|
|
626
691
|
// src/commands/fix-helpers.ts
|
|
627
692
|
var import_node_child_process2 = require("child_process");
|
|
628
|
-
var
|
|
629
|
-
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
693
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
630
694
|
function printPlan(renames, stubs) {
|
|
631
695
|
if (renames.length > 0) {
|
|
632
|
-
console.log(
|
|
696
|
+
console.log(import_chalk3.default.bold("\nFile renames:"));
|
|
633
697
|
for (const r of renames) {
|
|
634
|
-
console.log(` ${
|
|
698
|
+
console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
|
|
635
699
|
}
|
|
636
700
|
}
|
|
637
701
|
if (stubs.length > 0) {
|
|
638
|
-
console.log(
|
|
702
|
+
console.log(import_chalk3.default.bold("\nTest stubs to create:"));
|
|
639
703
|
for (const s of stubs) {
|
|
640
|
-
console.log(` ${
|
|
704
|
+
console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
|
|
641
705
|
}
|
|
642
706
|
}
|
|
643
707
|
}
|
|
@@ -659,15 +723,6 @@ function getConventionValue(convention) {
|
|
|
659
723
|
}
|
|
660
724
|
return void 0;
|
|
661
725
|
}
|
|
662
|
-
function promptConfirm(question) {
|
|
663
|
-
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
664
|
-
return new Promise((resolve4) => {
|
|
665
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
666
|
-
rl.close();
|
|
667
|
-
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
726
|
|
|
672
727
|
// src/commands/fix-imports.ts
|
|
673
728
|
var path7 = __toESM(require("path"), 1);
|
|
@@ -888,13 +943,13 @@ async function fixCommand(options, cwd) {
|
|
|
888
943
|
const startDir = cwd ?? process.cwd();
|
|
889
944
|
const projectRoot = findProjectRoot(startDir);
|
|
890
945
|
if (!projectRoot) {
|
|
891
|
-
console.error(`${
|
|
946
|
+
console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
892
947
|
return 1;
|
|
893
948
|
}
|
|
894
949
|
const configPath = path10.join(projectRoot, CONFIG_FILE3);
|
|
895
950
|
if (!fs9.existsSync(configPath)) {
|
|
896
951
|
console.error(
|
|
897
|
-
`${
|
|
952
|
+
`${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
898
953
|
);
|
|
899
954
|
return 1;
|
|
900
955
|
}
|
|
@@ -903,7 +958,7 @@ async function fixCommand(options, cwd) {
|
|
|
903
958
|
const isDirty = checkGitDirty(projectRoot);
|
|
904
959
|
if (isDirty) {
|
|
905
960
|
console.log(
|
|
906
|
-
|
|
961
|
+
import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
907
962
|
);
|
|
908
963
|
}
|
|
909
964
|
}
|
|
@@ -933,16 +988,16 @@ async function fixCommand(options, cwd) {
|
|
|
933
988
|
}
|
|
934
989
|
}
|
|
935
990
|
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
936
|
-
console.log(`${
|
|
991
|
+
console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
|
|
937
992
|
return 0;
|
|
938
993
|
}
|
|
939
994
|
printPlan(dedupedRenames, testStubs);
|
|
940
995
|
if (options.dryRun) {
|
|
941
|
-
console.log(
|
|
996
|
+
console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
|
|
942
997
|
return 0;
|
|
943
998
|
}
|
|
944
999
|
if (!options.yes) {
|
|
945
|
-
const confirmed = await
|
|
1000
|
+
const confirmed = await confirmDangerous("Apply these fixes?");
|
|
946
1001
|
if (!confirmed) {
|
|
947
1002
|
console.log("Aborted.");
|
|
948
1003
|
return 0;
|
|
@@ -969,15 +1024,15 @@ async function fixCommand(options, cwd) {
|
|
|
969
1024
|
}
|
|
970
1025
|
console.log("");
|
|
971
1026
|
if (renameCount > 0) {
|
|
972
|
-
console.log(`${
|
|
1027
|
+
console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
973
1028
|
}
|
|
974
1029
|
if (importUpdateCount > 0) {
|
|
975
1030
|
console.log(
|
|
976
|
-
`${
|
|
1031
|
+
`${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
977
1032
|
);
|
|
978
1033
|
}
|
|
979
1034
|
if (stubCount > 0) {
|
|
980
|
-
console.log(`${
|
|
1035
|
+
console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
981
1036
|
}
|
|
982
1037
|
return 0;
|
|
983
1038
|
}
|
|
@@ -985,13 +1040,13 @@ async function fixCommand(options, cwd) {
|
|
|
985
1040
|
// src/commands/init.ts
|
|
986
1041
|
var fs12 = __toESM(require("fs"), 1);
|
|
987
1042
|
var path13 = __toESM(require("path"), 1);
|
|
1043
|
+
var clack2 = __toESM(require("@clack/prompts"), 1);
|
|
988
1044
|
var import_config4 = require("@viberails/config");
|
|
989
1045
|
var import_scanner = require("@viberails/scanner");
|
|
990
|
-
var
|
|
1046
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
991
1047
|
|
|
992
|
-
// src/display.ts
|
|
993
|
-
var
|
|
994
|
-
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
1048
|
+
// src/display-text.ts
|
|
1049
|
+
var import_types4 = require("@viberails/types");
|
|
995
1050
|
|
|
996
1051
|
// src/display-helpers.ts
|
|
997
1052
|
var import_types = require("@viberails/types");
|
|
@@ -1042,9 +1097,13 @@ function formatRoleGroup(group) {
|
|
|
1042
1097
|
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1043
1098
|
}
|
|
1044
1099
|
|
|
1100
|
+
// src/display.ts
|
|
1101
|
+
var import_types3 = require("@viberails/types");
|
|
1102
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1103
|
+
|
|
1045
1104
|
// src/display-monorepo.ts
|
|
1046
1105
|
var import_types2 = require("@viberails/types");
|
|
1047
|
-
var
|
|
1106
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1048
1107
|
function formatPackageSummary(pkg) {
|
|
1049
1108
|
const parts = [];
|
|
1050
1109
|
if (pkg.stack.framework) {
|
|
@@ -1060,19 +1119,19 @@ function formatPackageSummary(pkg) {
|
|
|
1060
1119
|
function displayMonorepoResults(scanResult) {
|
|
1061
1120
|
const { stack, packages } = scanResult;
|
|
1062
1121
|
console.log(`
|
|
1063
|
-
${
|
|
1064
|
-
console.log(` ${
|
|
1122
|
+
${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1123
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1065
1124
|
if (stack.packageManager) {
|
|
1066
|
-
console.log(` ${
|
|
1125
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1067
1126
|
}
|
|
1068
1127
|
if (stack.linter) {
|
|
1069
|
-
console.log(` ${
|
|
1128
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1070
1129
|
}
|
|
1071
1130
|
if (stack.formatter) {
|
|
1072
|
-
console.log(` ${
|
|
1131
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1073
1132
|
}
|
|
1074
1133
|
if (stack.testRunner) {
|
|
1075
|
-
console.log(` ${
|
|
1134
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1076
1135
|
}
|
|
1077
1136
|
console.log("");
|
|
1078
1137
|
for (const pkg of packages) {
|
|
@@ -1083,13 +1142,13 @@ ${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`
|
|
|
1083
1142
|
);
|
|
1084
1143
|
if (packagesWithDirs.length > 0) {
|
|
1085
1144
|
console.log(`
|
|
1086
|
-
${
|
|
1145
|
+
${import_chalk5.default.bold("Structure:")}`);
|
|
1087
1146
|
for (const pkg of packagesWithDirs) {
|
|
1088
1147
|
const groups = groupByRole(pkg.structure.directories);
|
|
1089
1148
|
if (groups.length === 0) continue;
|
|
1090
1149
|
console.log(` ${pkg.relativePath}:`);
|
|
1091
1150
|
for (const group of groups) {
|
|
1092
|
-
console.log(` ${
|
|
1151
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1093
1152
|
}
|
|
1094
1153
|
}
|
|
1095
1154
|
}
|
|
@@ -1097,14 +1156,60 @@ ${import_chalk6.default.bold("Structure:")}`);
|
|
|
1097
1156
|
displaySummarySection(scanResult);
|
|
1098
1157
|
console.log("");
|
|
1099
1158
|
}
|
|
1159
|
+
function formatPackageSummaryPlain(pkg) {
|
|
1160
|
+
const parts = [];
|
|
1161
|
+
if (pkg.stack.framework) {
|
|
1162
|
+
parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
|
|
1163
|
+
}
|
|
1164
|
+
if (pkg.stack.styling) {
|
|
1165
|
+
parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
|
|
1166
|
+
}
|
|
1167
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1168
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1169
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1170
|
+
}
|
|
1171
|
+
function formatMonorepoResultsText(scanResult, config) {
|
|
1172
|
+
const lines = [];
|
|
1173
|
+
const { stack, packages } = scanResult;
|
|
1174
|
+
lines.push(`Detected: (monorepo, ${packages.length} packages)`);
|
|
1175
|
+
const sharedParts = [formatItem(stack.language)];
|
|
1176
|
+
if (stack.packageManager) sharedParts.push(formatItem(stack.packageManager));
|
|
1177
|
+
if (stack.linter) sharedParts.push(formatItem(stack.linter));
|
|
1178
|
+
if (stack.formatter) sharedParts.push(formatItem(stack.formatter));
|
|
1179
|
+
if (stack.testRunner) sharedParts.push(formatItem(stack.testRunner));
|
|
1180
|
+
lines.push(` \u2713 ${sharedParts.join(" \xB7 ")}`);
|
|
1181
|
+
lines.push("");
|
|
1182
|
+
for (const pkg of packages) {
|
|
1183
|
+
lines.push(formatPackageSummaryPlain(pkg));
|
|
1184
|
+
}
|
|
1185
|
+
const packagesWithDirs = packages.filter(
|
|
1186
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1187
|
+
);
|
|
1188
|
+
if (packagesWithDirs.length > 0) {
|
|
1189
|
+
lines.push("");
|
|
1190
|
+
lines.push("Structure:");
|
|
1191
|
+
for (const pkg of packagesWithDirs) {
|
|
1192
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1193
|
+
if (groups.length === 0) continue;
|
|
1194
|
+
lines.push(` ${pkg.relativePath}:`);
|
|
1195
|
+
for (const group of groups) {
|
|
1196
|
+
lines.push(` \u2713 ${formatRoleGroup(group)}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
lines.push(...formatConventionsText(scanResult));
|
|
1201
|
+
const pkgCount = packages.length > 1 ? packages.length : void 0;
|
|
1202
|
+
lines.push("");
|
|
1203
|
+
lines.push(formatSummary(scanResult.statistics, pkgCount));
|
|
1204
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1205
|
+
if (ext) {
|
|
1206
|
+
lines.push(ext);
|
|
1207
|
+
}
|
|
1208
|
+
lines.push(...formatRulesText(config));
|
|
1209
|
+
return lines.join("\n");
|
|
1210
|
+
}
|
|
1100
1211
|
|
|
1101
1212
|
// 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
1213
|
function formatItem(item, nameMap) {
|
|
1109
1214
|
const name = nameMap?.[item.name] ?? item.name;
|
|
1110
1215
|
return item.version ? `${name} ${item.version}` : name;
|
|
@@ -1120,27 +1225,27 @@ function displayConventions(scanResult) {
|
|
|
1120
1225
|
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1121
1226
|
if (conventionEntries.length === 0) return;
|
|
1122
1227
|
console.log(`
|
|
1123
|
-
${
|
|
1228
|
+
${import_chalk6.default.bold("Conventions:")}`);
|
|
1124
1229
|
for (const [key, convention] of conventionEntries) {
|
|
1125
1230
|
if (convention.confidence === "low") continue;
|
|
1126
|
-
const label = CONVENTION_LABELS[key] ?? key;
|
|
1231
|
+
const label = import_types3.CONVENTION_LABELS[key] ?? key;
|
|
1127
1232
|
if (scanResult.packages.length > 1) {
|
|
1128
1233
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1129
1234
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1130
1235
|
if (allSame || pkgValues.length <= 1) {
|
|
1131
|
-
const ind = convention.confidence === "high" ?
|
|
1132
|
-
const detail =
|
|
1236
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1237
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1133
1238
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1134
1239
|
} else {
|
|
1135
|
-
console.log(` ${
|
|
1240
|
+
console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
|
|
1136
1241
|
for (const pv of pkgValues) {
|
|
1137
1242
|
const pct = Math.round(pv.convention.consistency);
|
|
1138
1243
|
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1139
1244
|
}
|
|
1140
1245
|
}
|
|
1141
1246
|
} else {
|
|
1142
|
-
const ind = convention.confidence === "high" ?
|
|
1143
|
-
const detail =
|
|
1247
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1248
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1144
1249
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1145
1250
|
}
|
|
1146
1251
|
}
|
|
@@ -1148,7 +1253,7 @@ ${import_chalk7.default.bold("Conventions:")}`);
|
|
|
1148
1253
|
function displaySummarySection(scanResult) {
|
|
1149
1254
|
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1150
1255
|
console.log(`
|
|
1151
|
-
${
|
|
1256
|
+
${import_chalk6.default.bold("Summary:")}`);
|
|
1152
1257
|
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1153
1258
|
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1154
1259
|
if (ext) {
|
|
@@ -1162,43 +1267,43 @@ function displayScanResults(scanResult) {
|
|
|
1162
1267
|
}
|
|
1163
1268
|
const { stack } = scanResult;
|
|
1164
1269
|
console.log(`
|
|
1165
|
-
${
|
|
1270
|
+
${import_chalk6.default.bold("Detected:")}`);
|
|
1166
1271
|
if (stack.framework) {
|
|
1167
|
-
console.log(` ${
|
|
1272
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1168
1273
|
}
|
|
1169
|
-
console.log(` ${
|
|
1274
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1170
1275
|
if (stack.styling) {
|
|
1171
|
-
console.log(` ${
|
|
1276
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1172
1277
|
}
|
|
1173
1278
|
if (stack.backend) {
|
|
1174
|
-
console.log(` ${
|
|
1279
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1175
1280
|
}
|
|
1176
1281
|
if (stack.orm) {
|
|
1177
|
-
console.log(` ${
|
|
1282
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1178
1283
|
}
|
|
1179
1284
|
if (stack.linter) {
|
|
1180
|
-
console.log(` ${
|
|
1285
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1181
1286
|
}
|
|
1182
1287
|
if (stack.formatter) {
|
|
1183
|
-
console.log(` ${
|
|
1288
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1184
1289
|
}
|
|
1185
1290
|
if (stack.testRunner) {
|
|
1186
|
-
console.log(` ${
|
|
1291
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1187
1292
|
}
|
|
1188
1293
|
if (stack.packageManager) {
|
|
1189
|
-
console.log(` ${
|
|
1294
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1190
1295
|
}
|
|
1191
1296
|
if (stack.libraries.length > 0) {
|
|
1192
1297
|
for (const lib of stack.libraries) {
|
|
1193
|
-
console.log(` ${
|
|
1298
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1194
1299
|
}
|
|
1195
1300
|
}
|
|
1196
1301
|
const groups = groupByRole(scanResult.structure.directories);
|
|
1197
1302
|
if (groups.length > 0) {
|
|
1198
1303
|
console.log(`
|
|
1199
|
-
${
|
|
1304
|
+
${import_chalk6.default.bold("Structure:")}`);
|
|
1200
1305
|
for (const group of groups) {
|
|
1201
|
-
console.log(` ${
|
|
1306
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1202
1307
|
}
|
|
1203
1308
|
}
|
|
1204
1309
|
displayConventions(scanResult);
|
|
@@ -1209,38 +1314,151 @@ function getConventionStr(cv) {
|
|
|
1209
1314
|
return typeof cv === "string" ? cv : cv.value;
|
|
1210
1315
|
}
|
|
1211
1316
|
function displayRulesPreview(config) {
|
|
1212
|
-
console.log(`${
|
|
1213
|
-
console.log(` ${
|
|
1317
|
+
console.log(`${import_chalk6.default.bold("Rules:")}`);
|
|
1318
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1214
1319
|
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1215
1320
|
console.log(
|
|
1216
|
-
` ${
|
|
1321
|
+
` ${import_chalk6.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
|
|
1217
1322
|
);
|
|
1218
1323
|
} else if (config.rules.requireTests) {
|
|
1219
|
-
console.log(` ${
|
|
1324
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: yes`);
|
|
1220
1325
|
} else {
|
|
1221
|
-
console.log(` ${
|
|
1326
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: no`);
|
|
1222
1327
|
}
|
|
1223
1328
|
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1224
1329
|
console.log(
|
|
1225
|
-
` ${
|
|
1330
|
+
` ${import_chalk6.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
|
|
1226
1331
|
);
|
|
1227
1332
|
} else {
|
|
1228
|
-
console.log(` ${
|
|
1333
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
|
|
1229
1334
|
}
|
|
1230
1335
|
console.log(
|
|
1231
|
-
` ${
|
|
1336
|
+
` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1232
1337
|
);
|
|
1233
1338
|
console.log("");
|
|
1234
1339
|
if (config.enforcement === "enforce") {
|
|
1235
|
-
console.log(`${
|
|
1340
|
+
console.log(`${import_chalk6.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
|
|
1236
1341
|
} else {
|
|
1237
1342
|
console.log(
|
|
1238
|
-
`${
|
|
1343
|
+
`${import_chalk6.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
|
|
1239
1344
|
);
|
|
1240
1345
|
}
|
|
1241
1346
|
console.log("");
|
|
1242
1347
|
}
|
|
1243
1348
|
|
|
1349
|
+
// src/display-text.ts
|
|
1350
|
+
function getConventionStr2(cv) {
|
|
1351
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1352
|
+
}
|
|
1353
|
+
function plainConfidenceLabel(convention) {
|
|
1354
|
+
const pct = Math.round(convention.consistency);
|
|
1355
|
+
if (convention.confidence === "high") {
|
|
1356
|
+
return `${pct}%`;
|
|
1357
|
+
}
|
|
1358
|
+
return `${pct}%, suggested only`;
|
|
1359
|
+
}
|
|
1360
|
+
function formatConventionsText(scanResult) {
|
|
1361
|
+
const lines = [];
|
|
1362
|
+
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1363
|
+
if (conventionEntries.length === 0) return lines;
|
|
1364
|
+
lines.push("");
|
|
1365
|
+
lines.push("Conventions:");
|
|
1366
|
+
for (const [key, convention] of conventionEntries) {
|
|
1367
|
+
if (convention.confidence === "low") continue;
|
|
1368
|
+
const label = import_types4.CONVENTION_LABELS[key] ?? key;
|
|
1369
|
+
if (scanResult.packages.length > 1) {
|
|
1370
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1371
|
+
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1372
|
+
if (allSame || pkgValues.length <= 1) {
|
|
1373
|
+
const ind = convention.confidence === "high" ? "\u2713" : "~";
|
|
1374
|
+
lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
|
|
1375
|
+
} else {
|
|
1376
|
+
lines.push(` ~ ${label}: varies by package`);
|
|
1377
|
+
for (const pv of pkgValues) {
|
|
1378
|
+
const pct = Math.round(pv.convention.consistency);
|
|
1379
|
+
lines.push(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
} else {
|
|
1383
|
+
const ind = convention.confidence === "high" ? "\u2713" : "~";
|
|
1384
|
+
lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return lines;
|
|
1388
|
+
}
|
|
1389
|
+
function formatRulesText(config) {
|
|
1390
|
+
const lines = [];
|
|
1391
|
+
lines.push("");
|
|
1392
|
+
lines.push("Rules:");
|
|
1393
|
+
lines.push(` \u2022 Max file size: ${config.rules.maxFileLines} lines`);
|
|
1394
|
+
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1395
|
+
lines.push(` \u2022 Require test files: yes (${config.structure.testPattern})`);
|
|
1396
|
+
} else if (config.rules.requireTests) {
|
|
1397
|
+
lines.push(" \u2022 Require test files: yes");
|
|
1398
|
+
} else {
|
|
1399
|
+
lines.push(" \u2022 Require test files: no");
|
|
1400
|
+
}
|
|
1401
|
+
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1402
|
+
lines.push(` \u2022 Enforce file naming: ${getConventionStr2(config.conventions.fileNaming)}`);
|
|
1403
|
+
} else {
|
|
1404
|
+
lines.push(" \u2022 Enforce file naming: no");
|
|
1405
|
+
}
|
|
1406
|
+
lines.push(` \u2022 Enforcement mode: ${config.enforcement}`);
|
|
1407
|
+
return lines;
|
|
1408
|
+
}
|
|
1409
|
+
function formatScanResultsText(scanResult, config) {
|
|
1410
|
+
if (scanResult.packages.length > 1) {
|
|
1411
|
+
return formatMonorepoResultsText(scanResult, config);
|
|
1412
|
+
}
|
|
1413
|
+
const lines = [];
|
|
1414
|
+
const { stack } = scanResult;
|
|
1415
|
+
lines.push("Detected:");
|
|
1416
|
+
if (stack.framework) {
|
|
1417
|
+
lines.push(` \u2713 ${formatItem(stack.framework, import_types4.FRAMEWORK_NAMES)}`);
|
|
1418
|
+
}
|
|
1419
|
+
lines.push(` \u2713 ${formatItem(stack.language)}`);
|
|
1420
|
+
if (stack.styling) {
|
|
1421
|
+
lines.push(` \u2713 ${formatItem(stack.styling, import_types4.STYLING_NAMES)}`);
|
|
1422
|
+
}
|
|
1423
|
+
if (stack.backend) {
|
|
1424
|
+
lines.push(` \u2713 ${formatItem(stack.backend, import_types4.FRAMEWORK_NAMES)}`);
|
|
1425
|
+
}
|
|
1426
|
+
if (stack.orm) {
|
|
1427
|
+
lines.push(` \u2713 ${formatItem(stack.orm, import_types4.ORM_NAMES)}`);
|
|
1428
|
+
}
|
|
1429
|
+
const secondaryParts = [];
|
|
1430
|
+
if (stack.packageManager) secondaryParts.push(formatItem(stack.packageManager));
|
|
1431
|
+
if (stack.linter) secondaryParts.push(formatItem(stack.linter));
|
|
1432
|
+
if (stack.formatter) secondaryParts.push(formatItem(stack.formatter));
|
|
1433
|
+
if (stack.testRunner) secondaryParts.push(formatItem(stack.testRunner));
|
|
1434
|
+
if (secondaryParts.length > 0) {
|
|
1435
|
+
lines.push(` \u2713 ${secondaryParts.join(" \xB7 ")}`);
|
|
1436
|
+
}
|
|
1437
|
+
if (stack.libraries.length > 0) {
|
|
1438
|
+
for (const lib of stack.libraries) {
|
|
1439
|
+
lines.push(` \u2713 ${formatItem(lib, import_types4.LIBRARY_NAMES)}`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1443
|
+
if (groups.length > 0) {
|
|
1444
|
+
lines.push("");
|
|
1445
|
+
lines.push("Structure:");
|
|
1446
|
+
for (const group of groups) {
|
|
1447
|
+
lines.push(` \u2713 ${formatRoleGroup(group)}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
lines.push(...formatConventionsText(scanResult));
|
|
1451
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1452
|
+
lines.push("");
|
|
1453
|
+
lines.push(formatSummary(scanResult.statistics, pkgCount));
|
|
1454
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1455
|
+
if (ext) {
|
|
1456
|
+
lines.push(ext);
|
|
1457
|
+
}
|
|
1458
|
+
lines.push(...formatRulesText(config));
|
|
1459
|
+
return lines.join("\n");
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1244
1462
|
// src/utils/write-generated-files.ts
|
|
1245
1463
|
var fs10 = __toESM(require("fs"), 1);
|
|
1246
1464
|
var path11 = __toESM(require("path"), 1);
|
|
@@ -1270,18 +1488,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
1270
1488
|
// src/commands/init-hooks.ts
|
|
1271
1489
|
var fs11 = __toESM(require("fs"), 1);
|
|
1272
1490
|
var path12 = __toESM(require("path"), 1);
|
|
1273
|
-
var
|
|
1491
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
1274
1492
|
function setupPreCommitHook(projectRoot) {
|
|
1275
1493
|
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1276
1494
|
if (fs11.existsSync(lefthookPath)) {
|
|
1277
1495
|
addLefthookPreCommit(lefthookPath);
|
|
1278
|
-
console.log(` ${
|
|
1496
|
+
console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1279
1497
|
return;
|
|
1280
1498
|
}
|
|
1281
1499
|
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1282
1500
|
if (fs11.existsSync(huskyDir)) {
|
|
1283
1501
|
writeHuskyPreCommit(huskyDir);
|
|
1284
|
-
console.log(` ${
|
|
1502
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1285
1503
|
return;
|
|
1286
1504
|
}
|
|
1287
1505
|
const gitDir = path12.join(projectRoot, ".git");
|
|
@@ -1291,7 +1509,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
1291
1509
|
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1292
1510
|
}
|
|
1293
1511
|
writeGitHookPreCommit(hooksDir);
|
|
1294
|
-
console.log(` ${
|
|
1512
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
1295
1513
|
}
|
|
1296
1514
|
}
|
|
1297
1515
|
function writeGitHookPreCommit(hooksDir) {
|
|
@@ -1361,7 +1579,7 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1361
1579
|
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
1362
1580
|
} catch {
|
|
1363
1581
|
console.warn(
|
|
1364
|
-
` ${
|
|
1582
|
+
` ${import_chalk7.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
|
|
1365
1583
|
);
|
|
1366
1584
|
settings = {};
|
|
1367
1585
|
}
|
|
@@ -1370,7 +1588,17 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1370
1588
|
const existing = hooks.PostToolUse ?? [];
|
|
1371
1589
|
if (existing.some((h) => JSON.stringify(h).includes("viberails"))) return;
|
|
1372
1590
|
const extractFile = `node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input?.file_path??'')}catch{}"`;
|
|
1373
|
-
const
|
|
1591
|
+
const checkAndReport = [
|
|
1592
|
+
`FILE=$(${extractFile})`,
|
|
1593
|
+
'if [ -z "$FILE" ]; then exit 0; fi',
|
|
1594
|
+
'OUTPUT=$(npx viberails check --files "$FILE" --format json 2>&1)',
|
|
1595
|
+
`if echo "$OUTPUT" | node -e "process.exit(JSON.parse(require('fs').readFileSync(0,'utf8')).violations?.length?0:1)" 2>/dev/null; then`,
|
|
1596
|
+
' echo "$OUTPUT" >&2',
|
|
1597
|
+
" exit 2",
|
|
1598
|
+
"fi",
|
|
1599
|
+
"exit 0"
|
|
1600
|
+
].join("\n");
|
|
1601
|
+
const hookCommand = checkAndReport;
|
|
1374
1602
|
hooks.PostToolUse = [
|
|
1375
1603
|
...existing,
|
|
1376
1604
|
{
|
|
@@ -1386,7 +1614,19 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1386
1614
|
settings.hooks = hooks;
|
|
1387
1615
|
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1388
1616
|
`);
|
|
1389
|
-
console.log(` ${
|
|
1617
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1618
|
+
}
|
|
1619
|
+
function setupClaudeMdReference(projectRoot) {
|
|
1620
|
+
const claudeMdPath = path12.join(projectRoot, "CLAUDE.md");
|
|
1621
|
+
let content = "";
|
|
1622
|
+
if (fs11.existsSync(claudeMdPath)) {
|
|
1623
|
+
content = fs11.readFileSync(claudeMdPath, "utf-8");
|
|
1624
|
+
}
|
|
1625
|
+
if (content.includes("@.viberails/context.md")) return;
|
|
1626
|
+
const ref = "\n@.viberails/context.md\n";
|
|
1627
|
+
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
1628
|
+
fs11.writeFileSync(claudeMdPath, prefix + ref);
|
|
1629
|
+
console.log(` ${import_chalk7.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
1390
1630
|
}
|
|
1391
1631
|
function writeHuskyPreCommit(huskyDir) {
|
|
1392
1632
|
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
@@ -1416,6 +1656,14 @@ function filterHighConfidence(conventions) {
|
|
|
1416
1656
|
}
|
|
1417
1657
|
return filtered;
|
|
1418
1658
|
}
|
|
1659
|
+
function getConventionStr3(cv) {
|
|
1660
|
+
if (!cv) return void 0;
|
|
1661
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1662
|
+
}
|
|
1663
|
+
function hasConventionOverrides(config) {
|
|
1664
|
+
if (!config.packages || config.packages.length === 0) return false;
|
|
1665
|
+
return config.packages.some((pkg) => pkg.conventions && Object.keys(pkg.conventions).length > 0);
|
|
1666
|
+
}
|
|
1419
1667
|
async function initCommand(options, cwd) {
|
|
1420
1668
|
const startDir = cwd ?? process.cwd();
|
|
1421
1669
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1425,84 +1673,146 @@ async function initCommand(options, cwd) {
|
|
|
1425
1673
|
);
|
|
1426
1674
|
}
|
|
1427
1675
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
1428
|
-
if (fs12.existsSync(configPath)) {
|
|
1676
|
+
if (fs12.existsSync(configPath) && !options.force) {
|
|
1429
1677
|
console.log(
|
|
1430
|
-
|
|
1678
|
+
`${import_chalk8.default.yellow("!")} viberails is already initialized.
|
|
1679
|
+
Run ${import_chalk8.default.cyan("viberails sync")} to update, or ${import_chalk8.default.cyan("viberails init --force")} to start fresh.`
|
|
1431
1680
|
);
|
|
1432
1681
|
return;
|
|
1433
1682
|
}
|
|
1434
|
-
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1435
|
-
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
1436
|
-
const config = (0, import_config4.generateConfig)(scanResult);
|
|
1437
1683
|
if (options.yes) {
|
|
1438
|
-
|
|
1684
|
+
console.log(import_chalk8.default.dim("Scanning project..."));
|
|
1685
|
+
const scanResult2 = await (0, import_scanner.scan)(projectRoot);
|
|
1686
|
+
const config2 = (0, import_config4.generateConfig)(scanResult2);
|
|
1687
|
+
config2.conventions = filterHighConfidence(config2.conventions);
|
|
1688
|
+
displayScanResults(scanResult2);
|
|
1689
|
+
displayRulesPreview(config2);
|
|
1690
|
+
if (config2.workspace?.packages && config2.workspace.packages.length > 0) {
|
|
1691
|
+
console.log(import_chalk8.default.dim("Building import graph..."));
|
|
1692
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1693
|
+
const packages = resolveWorkspacePackages(projectRoot, config2.workspace);
|
|
1694
|
+
const graph = await buildImportGraph(projectRoot, {
|
|
1695
|
+
packages,
|
|
1696
|
+
ignore: config2.ignore
|
|
1697
|
+
});
|
|
1698
|
+
const inferred = inferBoundaries(graph);
|
|
1699
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
1700
|
+
if (denyCount > 0) {
|
|
1701
|
+
config2.boundaries = inferred;
|
|
1702
|
+
config2.rules.enforceBoundaries = true;
|
|
1703
|
+
console.log(` Inferred ${denyCount} boundary rules`);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
fs12.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
|
|
1707
|
+
`);
|
|
1708
|
+
writeGeneratedFiles(projectRoot, config2, scanResult2);
|
|
1709
|
+
updateGitignore(projectRoot);
|
|
1710
|
+
setupClaudeMdReference(projectRoot);
|
|
1711
|
+
console.log(`
|
|
1712
|
+
Created:`);
|
|
1713
|
+
console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1714
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
|
|
1715
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
|
|
1716
|
+
return;
|
|
1439
1717
|
}
|
|
1440
|
-
|
|
1718
|
+
clack2.intro("viberails");
|
|
1719
|
+
const s = clack2.spinner();
|
|
1720
|
+
s.start("Scanning project...");
|
|
1721
|
+
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
1722
|
+
const config = (0, import_config4.generateConfig)(scanResult);
|
|
1723
|
+
s.stop("Scan complete");
|
|
1441
1724
|
if (scanResult.statistics.totalFiles === 0) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1725
|
+
clack2.log.warn(
|
|
1726
|
+
"No source files detected. viberails will generate context\nwith minimal content. Run viberails sync after adding files."
|
|
1444
1727
|
);
|
|
1445
1728
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1729
|
+
const resultsText = formatScanResultsText(scanResult, config);
|
|
1730
|
+
clack2.note(resultsText, "Scan results");
|
|
1731
|
+
const decision = await promptInitDecision();
|
|
1732
|
+
if (decision === "customize") {
|
|
1733
|
+
clack2.note(
|
|
1734
|
+
"Rules control what viberails checks for.\nYou can change these later in viberails.config.json.",
|
|
1735
|
+
"Rules"
|
|
1736
|
+
);
|
|
1737
|
+
const overrides = await promptRuleCustomization({
|
|
1738
|
+
maxFileLines: config.rules.maxFileLines,
|
|
1739
|
+
requireTests: config.rules.requireTests,
|
|
1740
|
+
enforceNaming: config.rules.enforceNaming,
|
|
1741
|
+
enforcement: config.enforcement,
|
|
1742
|
+
fileNamingValue: getConventionStr3(config.conventions.fileNaming)
|
|
1743
|
+
});
|
|
1744
|
+
config.rules.maxFileLines = overrides.maxFileLines;
|
|
1745
|
+
config.rules.requireTests = overrides.requireTests;
|
|
1746
|
+
config.rules.enforceNaming = overrides.enforceNaming;
|
|
1747
|
+
config.enforcement = overrides.enforcement;
|
|
1748
|
+
if (config.workspace?.packages && config.workspace.packages.length > 0) {
|
|
1749
|
+
clack2.note(
|
|
1750
|
+
'These rules apply globally. To customize per package,\nedit the "packages" section in viberails.config.json.',
|
|
1751
|
+
"Per-package overrides"
|
|
1752
|
+
);
|
|
1452
1753
|
}
|
|
1453
1754
|
}
|
|
1454
|
-
if (config.workspace && config.workspace.packages.length > 0) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1755
|
+
if (config.workspace?.packages && config.workspace.packages.length > 0) {
|
|
1756
|
+
clack2.note(
|
|
1757
|
+
"Boundary rules prevent packages from importing where they\nshouldn't. viberails scans your existing imports and creates\nrules based on what's already working.",
|
|
1758
|
+
"Boundaries"
|
|
1759
|
+
);
|
|
1760
|
+
const shouldInfer = await confirm2("Infer boundary rules from import patterns?");
|
|
1460
1761
|
if (shouldInfer) {
|
|
1461
|
-
|
|
1762
|
+
const bs = clack2.spinner();
|
|
1763
|
+
bs.start("Building import graph...");
|
|
1462
1764
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1463
1765
|
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1464
|
-
const graph = await buildImportGraph(projectRoot, {
|
|
1766
|
+
const graph = await buildImportGraph(projectRoot, {
|
|
1767
|
+
packages,
|
|
1768
|
+
ignore: config.ignore
|
|
1769
|
+
});
|
|
1465
1770
|
const inferred = inferBoundaries(graph);
|
|
1466
|
-
|
|
1771
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
1772
|
+
if (denyCount > 0) {
|
|
1467
1773
|
config.boundaries = inferred;
|
|
1468
1774
|
config.rules.enforceBoundaries = true;
|
|
1469
|
-
|
|
1775
|
+
bs.stop(`Inferred ${denyCount} boundary rules`);
|
|
1776
|
+
} else {
|
|
1777
|
+
bs.stop("No boundary rules inferred");
|
|
1470
1778
|
}
|
|
1471
1779
|
}
|
|
1472
1780
|
}
|
|
1473
1781
|
const hookManager = detectHookManager(projectRoot);
|
|
1474
|
-
|
|
1475
|
-
if (
|
|
1476
|
-
|
|
1477
|
-
|
|
1782
|
+
const integrations = await promptIntegrations(hookManager);
|
|
1783
|
+
if (hasConventionOverrides(config)) {
|
|
1784
|
+
clack2.note(
|
|
1785
|
+
"Some packages use different conventions. Per-package\noverrides have been saved in viberails.config.json \u2014\nreview and adjust as needed.",
|
|
1786
|
+
"Per-package conventions"
|
|
1787
|
+
);
|
|
1478
1788
|
}
|
|
1479
1789
|
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1480
1790
|
`);
|
|
1481
1791
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1482
1792
|
updateGitignore(projectRoot);
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1793
|
+
const createdFiles = [
|
|
1794
|
+
CONFIG_FILE4,
|
|
1795
|
+
".viberails/context.md",
|
|
1796
|
+
".viberails/scan-result.json"
|
|
1797
|
+
];
|
|
1488
1798
|
if (integrations.preCommitHook) {
|
|
1489
1799
|
setupPreCommitHook(projectRoot);
|
|
1800
|
+
const hookMgr = detectHookManager(projectRoot);
|
|
1801
|
+
if (hookMgr) {
|
|
1802
|
+
createdFiles.push(`lefthook.yml \u2014 added viberails pre-commit`);
|
|
1803
|
+
}
|
|
1490
1804
|
}
|
|
1491
1805
|
if (integrations.claudeCodeHook) {
|
|
1492
1806
|
setupClaudeCodeHook(projectRoot);
|
|
1807
|
+
createdFiles.push(".claude/settings.json \u2014 added viberails hook");
|
|
1493
1808
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
];
|
|
1498
|
-
if (integrations.claudeCodeHook) {
|
|
1499
|
-
filesToCommit.push(import_chalk9.default.cyan(".claude/settings.json"));
|
|
1809
|
+
if (integrations.claudeMdRef) {
|
|
1810
|
+
setupClaudeMdReference(projectRoot);
|
|
1811
|
+
createdFiles.push("CLAUDE.md \u2014 added @.viberails/context.md reference");
|
|
1500
1812
|
}
|
|
1501
|
-
|
|
1502
|
-
${
|
|
1503
|
-
|
|
1504
|
-
console.log(` 2. Commit ${filesToCommit.join(", ")}`);
|
|
1505
|
-
console.log(` 3. Run ${import_chalk9.default.cyan("viberails check")} to verify your project passes`);
|
|
1813
|
+
clack2.log.success(`Created:
|
|
1814
|
+
${createdFiles.map((f) => ` ${f}`).join("\n")}`);
|
|
1815
|
+
clack2.outro("Done! Next: review viberails.config.json, then run viberails check");
|
|
1506
1816
|
}
|
|
1507
1817
|
function updateGitignore(projectRoot) {
|
|
1508
1818
|
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
@@ -1523,8 +1833,146 @@ var fs13 = __toESM(require("fs"), 1);
|
|
|
1523
1833
|
var path14 = __toESM(require("path"), 1);
|
|
1524
1834
|
var import_config5 = require("@viberails/config");
|
|
1525
1835
|
var import_scanner2 = require("@viberails/scanner");
|
|
1526
|
-
var
|
|
1836
|
+
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
1837
|
+
|
|
1838
|
+
// src/utils/diff-configs.ts
|
|
1839
|
+
var import_types5 = require("@viberails/types");
|
|
1840
|
+
function parseStackString(s) {
|
|
1841
|
+
const atIdx = s.indexOf("@");
|
|
1842
|
+
if (atIdx > 0) {
|
|
1843
|
+
return { name: s.slice(0, atIdx), version: s.slice(atIdx + 1) };
|
|
1844
|
+
}
|
|
1845
|
+
return { name: s };
|
|
1846
|
+
}
|
|
1847
|
+
function displayStackName(s) {
|
|
1848
|
+
const { name, version } = parseStackString(s);
|
|
1849
|
+
const allMaps = {
|
|
1850
|
+
...import_types5.FRAMEWORK_NAMES,
|
|
1851
|
+
...import_types5.STYLING_NAMES,
|
|
1852
|
+
...import_types5.ORM_NAMES
|
|
1853
|
+
};
|
|
1854
|
+
const display = allMaps[name] ?? name;
|
|
1855
|
+
return version ? `${display} ${version}` : display;
|
|
1856
|
+
}
|
|
1857
|
+
function conventionStr(cv) {
|
|
1858
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1859
|
+
}
|
|
1860
|
+
function isDetected(cv) {
|
|
1861
|
+
return typeof cv !== "string" && cv._detected === true;
|
|
1862
|
+
}
|
|
1863
|
+
var STACK_FIELDS = [
|
|
1864
|
+
"framework",
|
|
1865
|
+
"styling",
|
|
1866
|
+
"backend",
|
|
1867
|
+
"orm",
|
|
1868
|
+
"linter",
|
|
1869
|
+
"formatter",
|
|
1870
|
+
"testRunner"
|
|
1871
|
+
];
|
|
1872
|
+
var CONVENTION_KEYS = [
|
|
1873
|
+
"fileNaming",
|
|
1874
|
+
"componentNaming",
|
|
1875
|
+
"hookNaming",
|
|
1876
|
+
"importAlias"
|
|
1877
|
+
];
|
|
1878
|
+
var STRUCTURE_FIELDS = [
|
|
1879
|
+
{ key: "srcDir", label: "source directory" },
|
|
1880
|
+
{ key: "pages", label: "pages directory" },
|
|
1881
|
+
{ key: "components", label: "components directory" },
|
|
1882
|
+
{ key: "hooks", label: "hooks directory" },
|
|
1883
|
+
{ key: "utils", label: "utilities directory" },
|
|
1884
|
+
{ key: "types", label: "types directory" },
|
|
1885
|
+
{ key: "tests", label: "tests directory" },
|
|
1886
|
+
{ key: "testPattern", label: "test pattern" }
|
|
1887
|
+
];
|
|
1888
|
+
function diffConfigs(existing, merged) {
|
|
1889
|
+
const changes = [];
|
|
1890
|
+
for (const field of STACK_FIELDS) {
|
|
1891
|
+
const oldVal = existing.stack[field];
|
|
1892
|
+
const newVal = merged.stack[field];
|
|
1893
|
+
if (!oldVal && newVal) {
|
|
1894
|
+
changes.push({ type: "added", description: `Stack: added ${displayStackName(newVal)}` });
|
|
1895
|
+
} else if (oldVal && newVal && oldVal !== newVal) {
|
|
1896
|
+
changes.push({
|
|
1897
|
+
type: "changed",
|
|
1898
|
+
description: `Stack: ${displayStackName(oldVal)} \u2192 ${displayStackName(newVal)}`
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
for (const key of CONVENTION_KEYS) {
|
|
1903
|
+
const oldVal = existing.conventions[key];
|
|
1904
|
+
const newVal = merged.conventions[key];
|
|
1905
|
+
const label = import_types5.CONVENTION_LABELS[key] ?? key;
|
|
1906
|
+
if (!oldVal && newVal) {
|
|
1907
|
+
changes.push({
|
|
1908
|
+
type: "added",
|
|
1909
|
+
description: `New convention: ${label} (${conventionStr(newVal)})`
|
|
1910
|
+
});
|
|
1911
|
+
} else if (oldVal && newVal && isDetected(newVal)) {
|
|
1912
|
+
changes.push({
|
|
1913
|
+
type: "changed",
|
|
1914
|
+
description: `Convention updated: ${label} (${conventionStr(newVal)})`
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
for (const { key, label } of STRUCTURE_FIELDS) {
|
|
1919
|
+
const oldVal = existing.structure[key];
|
|
1920
|
+
const newVal = merged.structure[key];
|
|
1921
|
+
if (!oldVal && newVal) {
|
|
1922
|
+
changes.push({ type: "added", description: `Structure: detected ${label} (${newVal})` });
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
const existingPaths = new Set((existing.packages ?? []).map((p) => p.path));
|
|
1926
|
+
for (const pkg of merged.packages ?? []) {
|
|
1927
|
+
if (!existingPaths.has(pkg.path)) {
|
|
1928
|
+
changes.push({ type: "added", description: `New package: ${pkg.path}` });
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const existingWsPkgs = new Set(existing.workspace?.packages ?? []);
|
|
1932
|
+
const mergedWsPkgs = new Set(merged.workspace?.packages ?? []);
|
|
1933
|
+
for (const pkg of mergedWsPkgs) {
|
|
1934
|
+
if (!existingWsPkgs.has(pkg)) {
|
|
1935
|
+
changes.push({ type: "added", description: `Workspace: added ${pkg}` });
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
for (const pkg of existingWsPkgs) {
|
|
1939
|
+
if (!mergedWsPkgs.has(pkg)) {
|
|
1940
|
+
changes.push({ type: "removed", description: `Workspace: removed ${pkg}` });
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
return changes;
|
|
1944
|
+
}
|
|
1945
|
+
function formatStatsDelta(oldStats, newStats) {
|
|
1946
|
+
const fileDelta = newStats.totalFiles - oldStats.totalFiles;
|
|
1947
|
+
const lineDelta = newStats.totalLines - oldStats.totalLines;
|
|
1948
|
+
if (fileDelta === 0 && lineDelta === 0) return void 0;
|
|
1949
|
+
const parts = [];
|
|
1950
|
+
if (fileDelta !== 0) {
|
|
1951
|
+
const sign = fileDelta > 0 ? "+" : "";
|
|
1952
|
+
parts.push(`${sign}${fileDelta.toLocaleString()} files`);
|
|
1953
|
+
}
|
|
1954
|
+
if (lineDelta !== 0) {
|
|
1955
|
+
const sign = lineDelta > 0 ? "+" : "";
|
|
1956
|
+
parts.push(`${sign}${lineDelta.toLocaleString()} lines`);
|
|
1957
|
+
}
|
|
1958
|
+
return `${parts.join(", ")} since last sync`;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/commands/sync.ts
|
|
1527
1962
|
var CONFIG_FILE5 = "viberails.config.json";
|
|
1963
|
+
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
1964
|
+
function loadPreviousStats(projectRoot) {
|
|
1965
|
+
const scanResultPath = path14.join(projectRoot, SCAN_RESULT_FILE2);
|
|
1966
|
+
try {
|
|
1967
|
+
const raw = fs13.readFileSync(scanResultPath, "utf-8");
|
|
1968
|
+
const parsed = JSON.parse(raw);
|
|
1969
|
+
if (parsed?.statistics?.totalFiles !== void 0) {
|
|
1970
|
+
return parsed.statistics;
|
|
1971
|
+
}
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
return void 0;
|
|
1975
|
+
}
|
|
1528
1976
|
async function syncCommand(cwd) {
|
|
1529
1977
|
const startDir = cwd ?? process.cwd();
|
|
1530
1978
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1535,41 +1983,50 @@ async function syncCommand(cwd) {
|
|
|
1535
1983
|
}
|
|
1536
1984
|
const configPath = path14.join(projectRoot, CONFIG_FILE5);
|
|
1537
1985
|
const existing = await (0, import_config5.loadConfig)(configPath);
|
|
1538
|
-
|
|
1986
|
+
const previousStats = loadPreviousStats(projectRoot);
|
|
1987
|
+
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1539
1988
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
1540
1989
|
const merged = (0, import_config5.mergeConfig)(existing, scanResult);
|
|
1541
1990
|
const existingJson = JSON.stringify(existing, null, 2);
|
|
1542
1991
|
const mergedJson = JSON.stringify(merged, null, 2);
|
|
1543
1992
|
const configChanged = existingJson !== mergedJson;
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1993
|
+
const changes = configChanged ? diffConfigs(existing, merged) : [];
|
|
1994
|
+
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
1995
|
+
if (changes.length > 0 || statsDelta) {
|
|
1996
|
+
console.log(`
|
|
1997
|
+
${import_chalk9.default.bold("Changes:")}`);
|
|
1998
|
+
for (const change of changes) {
|
|
1999
|
+
const icon = change.type === "removed" ? import_chalk9.default.red("-") : import_chalk9.default.green("+");
|
|
2000
|
+
console.log(` ${icon} ${change.description}`);
|
|
2001
|
+
}
|
|
2002
|
+
if (statsDelta) {
|
|
2003
|
+
console.log(` ${import_chalk9.default.dim(statsDelta)}`);
|
|
2004
|
+
}
|
|
1548
2005
|
}
|
|
1549
2006
|
fs13.writeFileSync(configPath, `${mergedJson}
|
|
1550
2007
|
`);
|
|
1551
2008
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1552
2009
|
console.log(`
|
|
1553
|
-
${
|
|
2010
|
+
${import_chalk9.default.bold("Synced:")}`);
|
|
1554
2011
|
if (configChanged) {
|
|
1555
|
-
console.log(` ${
|
|
2012
|
+
console.log(` ${import_chalk9.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
|
|
1556
2013
|
} else {
|
|
1557
|
-
console.log(` ${
|
|
2014
|
+
console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
|
|
1558
2015
|
}
|
|
1559
|
-
console.log(` ${
|
|
1560
|
-
console.log(` ${
|
|
2016
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
2017
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1561
2018
|
}
|
|
1562
2019
|
|
|
1563
2020
|
// src/index.ts
|
|
1564
|
-
var VERSION = "0.3.
|
|
2021
|
+
var VERSION = "0.3.3";
|
|
1565
2022
|
var program = new import_commander.Command();
|
|
1566
2023
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
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) => {
|
|
2024
|
+
program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {
|
|
1568
2025
|
try {
|
|
1569
2026
|
await initCommand(options);
|
|
1570
2027
|
} catch (err) {
|
|
1571
2028
|
const message = err instanceof Error ? err.message : String(err);
|
|
1572
|
-
console.error(`${
|
|
2029
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1573
2030
|
process.exit(1);
|
|
1574
2031
|
}
|
|
1575
2032
|
});
|
|
@@ -1578,7 +2035,7 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1578
2035
|
await syncCommand();
|
|
1579
2036
|
} catch (err) {
|
|
1580
2037
|
const message = err instanceof Error ? err.message : String(err);
|
|
1581
|
-
console.error(`${
|
|
2038
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1582
2039
|
process.exit(1);
|
|
1583
2040
|
}
|
|
1584
2041
|
});
|
|
@@ -1593,7 +2050,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
1593
2050
|
process.exit(exitCode);
|
|
1594
2051
|
} catch (err) {
|
|
1595
2052
|
const message = err instanceof Error ? err.message : String(err);
|
|
1596
|
-
console.error(`${
|
|
2053
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1597
2054
|
process.exit(1);
|
|
1598
2055
|
}
|
|
1599
2056
|
}
|
|
@@ -1604,7 +2061,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
1604
2061
|
process.exit(exitCode);
|
|
1605
2062
|
} catch (err) {
|
|
1606
2063
|
const message = err instanceof Error ? err.message : String(err);
|
|
1607
|
-
console.error(`${
|
|
2064
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1608
2065
|
process.exit(1);
|
|
1609
2066
|
}
|
|
1610
2067
|
});
|
|
@@ -1613,7 +2070,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
1613
2070
|
await boundariesCommand(options);
|
|
1614
2071
|
} catch (err) {
|
|
1615
2072
|
const message = err instanceof Error ? err.message : String(err);
|
|
1616
|
-
console.error(`${
|
|
2073
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1617
2074
|
process.exit(1);
|
|
1618
2075
|
}
|
|
1619
2076
|
});
|