viberails 0.3.2 → 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 +238 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +249 -56
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -149,15 +149,21 @@ async function promptIntegrations(hookManager) {
|
|
|
149
149
|
value: "claude",
|
|
150
150
|
label: "Claude Code hook",
|
|
151
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"
|
|
152
157
|
}
|
|
153
158
|
],
|
|
154
|
-
initialValues: ["preCommit", "claude"],
|
|
159
|
+
initialValues: ["preCommit", "claude", "claudeMd"],
|
|
155
160
|
required: false
|
|
156
161
|
});
|
|
157
162
|
assertNotCancelled(result);
|
|
158
163
|
return {
|
|
159
164
|
preCommitHook: result.includes("preCommit"),
|
|
160
|
-
claudeCodeHook: result.includes("claude")
|
|
165
|
+
claudeCodeHook: result.includes("claude"),
|
|
166
|
+
claudeMdRef: result.includes("claudeMd")
|
|
161
167
|
};
|
|
162
168
|
}
|
|
163
169
|
|
|
@@ -215,22 +221,21 @@ async function boundariesCommand(options, cwd) {
|
|
|
215
221
|
displayRules(config);
|
|
216
222
|
}
|
|
217
223
|
function displayRules(config) {
|
|
218
|
-
if (!config.boundaries || config.boundaries.length === 0) {
|
|
224
|
+
if (!config.boundaries || Object.keys(config.boundaries.deny).length === 0) {
|
|
219
225
|
console.log(import_chalk.default.yellow("No boundary rules configured."));
|
|
220
226
|
console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
221
227
|
return;
|
|
222
228
|
}
|
|
223
|
-
const
|
|
224
|
-
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);
|
|
225
232
|
console.log(`
|
|
226
|
-
${import_chalk.default.bold(`Boundary rules (${
|
|
233
|
+
${import_chalk.default.bold(`Boundary rules (${totalRules} deny rules):`)}
|
|
227
234
|
`);
|
|
228
|
-
for (const
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
|
|
233
|
-
console.log(` ${import_chalk.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
|
+
}
|
|
234
239
|
}
|
|
235
240
|
console.log(
|
|
236
241
|
`
|
|
@@ -247,24 +252,22 @@ async function inferAndDisplay(projectRoot, config, configPath) {
|
|
|
247
252
|
});
|
|
248
253
|
console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
249
254
|
const inferred = inferBoundaries(graph);
|
|
250
|
-
|
|
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) {
|
|
251
258
|
console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
|
|
252
259
|
return;
|
|
253
260
|
}
|
|
254
|
-
const allow = inferred.filter((r) => r.allow);
|
|
255
|
-
const deny = inferred.filter((r) => !r.allow);
|
|
256
261
|
console.log(`
|
|
257
262
|
${import_chalk.default.bold("Inferred boundary rules:")}
|
|
258
263
|
`);
|
|
259
|
-
for (const
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
|
|
264
|
-
console.log(` ${import_chalk.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
|
+
}
|
|
265
268
|
}
|
|
266
269
|
console.log(`
|
|
267
|
-
${
|
|
270
|
+
${totalRules} denied`);
|
|
268
271
|
console.log("");
|
|
269
272
|
const shouldSave = await confirm2("Save to viberails.config.json?");
|
|
270
273
|
if (shouldSave) {
|
|
@@ -272,7 +275,7 @@ ${import_chalk.default.bold("Inferred boundary rules:")}
|
|
|
272
275
|
config.rules.enforceBoundaries = true;
|
|
273
276
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
274
277
|
`);
|
|
275
|
-
console.log(`${import_chalk.default.green("\u2713")} Saved ${
|
|
278
|
+
console.log(`${import_chalk.default.green("\u2713")} Saved ${totalRules} rules`);
|
|
276
279
|
}
|
|
277
280
|
}
|
|
278
281
|
async function showGraph(projectRoot, config) {
|
|
@@ -631,7 +634,7 @@ async function checkCommand(options, cwd) {
|
|
|
631
634
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
632
635
|
violations.push(...testViolations);
|
|
633
636
|
}
|
|
634
|
-
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) {
|
|
635
638
|
const startTime = Date.now();
|
|
636
639
|
const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
|
|
637
640
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
@@ -647,7 +650,7 @@ async function checkCommand(options, cwd) {
|
|
|
647
650
|
violations.push({
|
|
648
651
|
file: relFile,
|
|
649
652
|
rule: "boundary-violation",
|
|
650
|
-
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}`,
|
|
651
654
|
severity
|
|
652
655
|
});
|
|
653
656
|
}
|
|
@@ -1042,9 +1045,8 @@ var import_config4 = require("@viberails/config");
|
|
|
1042
1045
|
var import_scanner = require("@viberails/scanner");
|
|
1043
1046
|
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
1044
1047
|
|
|
1045
|
-
// src/display.ts
|
|
1046
|
-
var
|
|
1047
|
-
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1048
|
+
// src/display-text.ts
|
|
1049
|
+
var import_types4 = require("@viberails/types");
|
|
1048
1050
|
|
|
1049
1051
|
// src/display-helpers.ts
|
|
1050
1052
|
var import_types = require("@viberails/types");
|
|
@@ -1095,6 +1097,10 @@ function formatRoleGroup(group) {
|
|
|
1095
1097
|
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1096
1098
|
}
|
|
1097
1099
|
|
|
1100
|
+
// src/display.ts
|
|
1101
|
+
var import_types3 = require("@viberails/types");
|
|
1102
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1103
|
+
|
|
1098
1104
|
// src/display-monorepo.ts
|
|
1099
1105
|
var import_types2 = require("@viberails/types");
|
|
1100
1106
|
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
@@ -1204,12 +1210,6 @@ function formatMonorepoResultsText(scanResult, config) {
|
|
|
1204
1210
|
}
|
|
1205
1211
|
|
|
1206
1212
|
// src/display.ts
|
|
1207
|
-
var CONVENTION_LABELS = {
|
|
1208
|
-
fileNaming: "File naming",
|
|
1209
|
-
componentNaming: "Component naming",
|
|
1210
|
-
hookNaming: "Hook naming",
|
|
1211
|
-
importAlias: "Import alias"
|
|
1212
|
-
};
|
|
1213
1213
|
function formatItem(item, nameMap) {
|
|
1214
1214
|
const name = nameMap?.[item.name] ?? item.name;
|
|
1215
1215
|
return item.version ? `${name} ${item.version}` : name;
|
|
@@ -1228,7 +1228,7 @@ function displayConventions(scanResult) {
|
|
|
1228
1228
|
${import_chalk6.default.bold("Conventions:")}`);
|
|
1229
1229
|
for (const [key, convention] of conventionEntries) {
|
|
1230
1230
|
if (convention.confidence === "low") continue;
|
|
1231
|
-
const label = CONVENTION_LABELS[key] ?? key;
|
|
1231
|
+
const label = import_types3.CONVENTION_LABELS[key] ?? key;
|
|
1232
1232
|
if (scanResult.packages.length > 1) {
|
|
1233
1233
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1234
1234
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
@@ -1345,6 +1345,11 @@ function displayRulesPreview(config) {
|
|
|
1345
1345
|
}
|
|
1346
1346
|
console.log("");
|
|
1347
1347
|
}
|
|
1348
|
+
|
|
1349
|
+
// src/display-text.ts
|
|
1350
|
+
function getConventionStr2(cv) {
|
|
1351
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1352
|
+
}
|
|
1348
1353
|
function plainConfidenceLabel(convention) {
|
|
1349
1354
|
const pct = Math.round(convention.consistency);
|
|
1350
1355
|
if (convention.confidence === "high") {
|
|
@@ -1360,7 +1365,7 @@ function formatConventionsText(scanResult) {
|
|
|
1360
1365
|
lines.push("Conventions:");
|
|
1361
1366
|
for (const [key, convention] of conventionEntries) {
|
|
1362
1367
|
if (convention.confidence === "low") continue;
|
|
1363
|
-
const label = CONVENTION_LABELS[key] ?? key;
|
|
1368
|
+
const label = import_types4.CONVENTION_LABELS[key] ?? key;
|
|
1364
1369
|
if (scanResult.packages.length > 1) {
|
|
1365
1370
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1366
1371
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
@@ -1394,7 +1399,7 @@ function formatRulesText(config) {
|
|
|
1394
1399
|
lines.push(" \u2022 Require test files: no");
|
|
1395
1400
|
}
|
|
1396
1401
|
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1397
|
-
lines.push(` \u2022 Enforce file naming: ${
|
|
1402
|
+
lines.push(` \u2022 Enforce file naming: ${getConventionStr2(config.conventions.fileNaming)}`);
|
|
1398
1403
|
} else {
|
|
1399
1404
|
lines.push(" \u2022 Enforce file naming: no");
|
|
1400
1405
|
}
|
|
@@ -1409,17 +1414,17 @@ function formatScanResultsText(scanResult, config) {
|
|
|
1409
1414
|
const { stack } = scanResult;
|
|
1410
1415
|
lines.push("Detected:");
|
|
1411
1416
|
if (stack.framework) {
|
|
1412
|
-
lines.push(` \u2713 ${formatItem(stack.framework,
|
|
1417
|
+
lines.push(` \u2713 ${formatItem(stack.framework, import_types4.FRAMEWORK_NAMES)}`);
|
|
1413
1418
|
}
|
|
1414
1419
|
lines.push(` \u2713 ${formatItem(stack.language)}`);
|
|
1415
1420
|
if (stack.styling) {
|
|
1416
|
-
lines.push(` \u2713 ${formatItem(stack.styling,
|
|
1421
|
+
lines.push(` \u2713 ${formatItem(stack.styling, import_types4.STYLING_NAMES)}`);
|
|
1417
1422
|
}
|
|
1418
1423
|
if (stack.backend) {
|
|
1419
|
-
lines.push(` \u2713 ${formatItem(stack.backend,
|
|
1424
|
+
lines.push(` \u2713 ${formatItem(stack.backend, import_types4.FRAMEWORK_NAMES)}`);
|
|
1420
1425
|
}
|
|
1421
1426
|
if (stack.orm) {
|
|
1422
|
-
lines.push(` \u2713 ${formatItem(stack.orm,
|
|
1427
|
+
lines.push(` \u2713 ${formatItem(stack.orm, import_types4.ORM_NAMES)}`);
|
|
1423
1428
|
}
|
|
1424
1429
|
const secondaryParts = [];
|
|
1425
1430
|
if (stack.packageManager) secondaryParts.push(formatItem(stack.packageManager));
|
|
@@ -1431,7 +1436,7 @@ function formatScanResultsText(scanResult, config) {
|
|
|
1431
1436
|
}
|
|
1432
1437
|
if (stack.libraries.length > 0) {
|
|
1433
1438
|
for (const lib of stack.libraries) {
|
|
1434
|
-
lines.push(` \u2713 ${formatItem(lib,
|
|
1439
|
+
lines.push(` \u2713 ${formatItem(lib, import_types4.LIBRARY_NAMES)}`);
|
|
1435
1440
|
}
|
|
1436
1441
|
}
|
|
1437
1442
|
const groups = groupByRole(scanResult.structure.directories);
|
|
@@ -1583,7 +1588,17 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1583
1588
|
const existing = hooks.PostToolUse ?? [];
|
|
1584
1589
|
if (existing.some((h) => JSON.stringify(h).includes("viberails"))) return;
|
|
1585
1590
|
const extractFile = `node -e "try{process.stdout.write(JSON.parse(require('fs').readFileSync(0,'utf8')).tool_input?.file_path??'')}catch{}"`;
|
|
1586
|
-
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;
|
|
1587
1602
|
hooks.PostToolUse = [
|
|
1588
1603
|
...existing,
|
|
1589
1604
|
{
|
|
@@ -1601,6 +1616,18 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1601
1616
|
`);
|
|
1602
1617
|
console.log(` ${import_chalk7.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1603
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`);
|
|
1630
|
+
}
|
|
1604
1631
|
function writeHuskyPreCommit(huskyDir) {
|
|
1605
1632
|
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
1606
1633
|
if (fs11.existsSync(hookPath)) {
|
|
@@ -1629,7 +1656,7 @@ function filterHighConfidence(conventions) {
|
|
|
1629
1656
|
}
|
|
1630
1657
|
return filtered;
|
|
1631
1658
|
}
|
|
1632
|
-
function
|
|
1659
|
+
function getConventionStr3(cv) {
|
|
1633
1660
|
if (!cv) return void 0;
|
|
1634
1661
|
return typeof cv === "string" ? cv : cv.value;
|
|
1635
1662
|
}
|
|
@@ -1646,10 +1673,10 @@ async function initCommand(options, cwd) {
|
|
|
1646
1673
|
);
|
|
1647
1674
|
}
|
|
1648
1675
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
1649
|
-
if (fs12.existsSync(configPath)) {
|
|
1676
|
+
if (fs12.existsSync(configPath) && !options.force) {
|
|
1650
1677
|
console.log(
|
|
1651
1678
|
`${import_chalk8.default.yellow("!")} viberails is already initialized.
|
|
1652
|
-
Run ${import_chalk8.default.cyan("viberails sync")} to update, or
|
|
1679
|
+
Run ${import_chalk8.default.cyan("viberails sync")} to update, or ${import_chalk8.default.cyan("viberails init --force")} to start fresh.`
|
|
1653
1680
|
);
|
|
1654
1681
|
return;
|
|
1655
1682
|
}
|
|
@@ -1669,16 +1696,18 @@ async function initCommand(options, cwd) {
|
|
|
1669
1696
|
ignore: config2.ignore
|
|
1670
1697
|
});
|
|
1671
1698
|
const inferred = inferBoundaries(graph);
|
|
1672
|
-
|
|
1699
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
1700
|
+
if (denyCount > 0) {
|
|
1673
1701
|
config2.boundaries = inferred;
|
|
1674
1702
|
config2.rules.enforceBoundaries = true;
|
|
1675
|
-
console.log(` Inferred ${
|
|
1703
|
+
console.log(` Inferred ${denyCount} boundary rules`);
|
|
1676
1704
|
}
|
|
1677
1705
|
}
|
|
1678
1706
|
fs12.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
|
|
1679
1707
|
`);
|
|
1680
1708
|
writeGeneratedFiles(projectRoot, config2, scanResult2);
|
|
1681
1709
|
updateGitignore(projectRoot);
|
|
1710
|
+
setupClaudeMdReference(projectRoot);
|
|
1682
1711
|
console.log(`
|
|
1683
1712
|
Created:`);
|
|
1684
1713
|
console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
|
|
@@ -1710,7 +1739,7 @@ Created:`);
|
|
|
1710
1739
|
requireTests: config.rules.requireTests,
|
|
1711
1740
|
enforceNaming: config.rules.enforceNaming,
|
|
1712
1741
|
enforcement: config.enforcement,
|
|
1713
|
-
fileNamingValue:
|
|
1742
|
+
fileNamingValue: getConventionStr3(config.conventions.fileNaming)
|
|
1714
1743
|
});
|
|
1715
1744
|
config.rules.maxFileLines = overrides.maxFileLines;
|
|
1716
1745
|
config.rules.requireTests = overrides.requireTests;
|
|
@@ -1739,10 +1768,11 @@ Created:`);
|
|
|
1739
1768
|
ignore: config.ignore
|
|
1740
1769
|
});
|
|
1741
1770
|
const inferred = inferBoundaries(graph);
|
|
1742
|
-
|
|
1771
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
1772
|
+
if (denyCount > 0) {
|
|
1743
1773
|
config.boundaries = inferred;
|
|
1744
1774
|
config.rules.enforceBoundaries = true;
|
|
1745
|
-
bs.stop(`Inferred ${
|
|
1775
|
+
bs.stop(`Inferred ${denyCount} boundary rules`);
|
|
1746
1776
|
} else {
|
|
1747
1777
|
bs.stop("No boundary rules inferred");
|
|
1748
1778
|
}
|
|
@@ -1776,6 +1806,10 @@ Created:`);
|
|
|
1776
1806
|
setupClaudeCodeHook(projectRoot);
|
|
1777
1807
|
createdFiles.push(".claude/settings.json \u2014 added viberails hook");
|
|
1778
1808
|
}
|
|
1809
|
+
if (integrations.claudeMdRef) {
|
|
1810
|
+
setupClaudeMdReference(projectRoot);
|
|
1811
|
+
createdFiles.push("CLAUDE.md \u2014 added @.viberails/context.md reference");
|
|
1812
|
+
}
|
|
1779
1813
|
clack2.log.success(`Created:
|
|
1780
1814
|
${createdFiles.map((f) => ` ${f}`).join("\n")}`);
|
|
1781
1815
|
clack2.outro("Done! Next: review viberails.config.json, then run viberails check");
|
|
@@ -1800,7 +1834,145 @@ var path14 = __toESM(require("path"), 1);
|
|
|
1800
1834
|
var import_config5 = require("@viberails/config");
|
|
1801
1835
|
var import_scanner2 = require("@viberails/scanner");
|
|
1802
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
|
|
1803
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
|
+
}
|
|
1804
1976
|
async function syncCommand(cwd) {
|
|
1805
1977
|
const startDir = cwd ?? process.cwd();
|
|
1806
1978
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1811,16 +1983,25 @@ async function syncCommand(cwd) {
|
|
|
1811
1983
|
}
|
|
1812
1984
|
const configPath = path14.join(projectRoot, CONFIG_FILE5);
|
|
1813
1985
|
const existing = await (0, import_config5.loadConfig)(configPath);
|
|
1986
|
+
const previousStats = loadPreviousStats(projectRoot);
|
|
1814
1987
|
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1815
1988
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
1816
1989
|
const merged = (0, import_config5.mergeConfig)(existing, scanResult);
|
|
1817
1990
|
const existingJson = JSON.stringify(existing, null, 2);
|
|
1818
1991
|
const mergedJson = JSON.stringify(merged, null, 2);
|
|
1819
1992
|
const configChanged = existingJson !== mergedJson;
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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
|
+
}
|
|
1824
2005
|
}
|
|
1825
2006
|
fs13.writeFileSync(configPath, `${mergedJson}
|
|
1826
2007
|
`);
|
|
@@ -1837,10 +2018,10 @@ ${import_chalk9.default.bold("Synced:")}`);
|
|
|
1837
2018
|
}
|
|
1838
2019
|
|
|
1839
2020
|
// src/index.ts
|
|
1840
|
-
var VERSION = "0.3.
|
|
2021
|
+
var VERSION = "0.3.3";
|
|
1841
2022
|
var program = new import_commander.Command();
|
|
1842
2023
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1843
|
-
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) => {
|
|
1844
2025
|
try {
|
|
1845
2026
|
await initCommand(options);
|
|
1846
2027
|
} catch (err) {
|