@viberails/config 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +165 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +165 -7
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,70 @@ var DEFAULT_IGNORE = ["**/*.d.ts", "dist/**", "node_modules/**"];
|
|
|
53
53
|
|
|
54
54
|
// src/generate-config.ts
|
|
55
55
|
var path = __toESM(require("path"), 1);
|
|
56
|
+
|
|
57
|
+
// src/generate-overrides.ts
|
|
58
|
+
function conventionsDiffer(pkgConventions, globalConventions) {
|
|
59
|
+
const overrides = {};
|
|
60
|
+
let hasDiff = false;
|
|
61
|
+
for (const key of CONVENTION_KEYS) {
|
|
62
|
+
const detected = pkgConventions[key];
|
|
63
|
+
if (!detected) continue;
|
|
64
|
+
const mapped = mapConvention(detected);
|
|
65
|
+
if (mapped === void 0) continue;
|
|
66
|
+
const globalValue = globalConventions[key];
|
|
67
|
+
const globalStr = typeof globalValue === "string" ? globalValue : globalValue?.value;
|
|
68
|
+
if (detected.value !== globalStr) {
|
|
69
|
+
overrides[key] = mapped;
|
|
70
|
+
hasDiff = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return hasDiff ? overrides : void 0;
|
|
74
|
+
}
|
|
75
|
+
function generatePackageOverrides(scanResult, globalConfig) {
|
|
76
|
+
if (!scanResult.packages || scanResult.packages.length <= 1) return void 0;
|
|
77
|
+
const overrides = [];
|
|
78
|
+
for (const pkg of scanResult.packages) {
|
|
79
|
+
const override = {
|
|
80
|
+
name: pkg.name,
|
|
81
|
+
path: pkg.relativePath
|
|
82
|
+
};
|
|
83
|
+
let hasDiff = false;
|
|
84
|
+
const stackOverride = {};
|
|
85
|
+
let hasStackDiff = false;
|
|
86
|
+
const optionalStackFields = [
|
|
87
|
+
"framework",
|
|
88
|
+
"styling",
|
|
89
|
+
"backend",
|
|
90
|
+
"linter",
|
|
91
|
+
"formatter",
|
|
92
|
+
"testRunner"
|
|
93
|
+
];
|
|
94
|
+
for (const field of optionalStackFields) {
|
|
95
|
+
const pkgItem = pkg.stack[field];
|
|
96
|
+
if (!pkgItem) continue;
|
|
97
|
+
const pkgValue = formatStackItem(pkgItem);
|
|
98
|
+
if (pkgValue !== globalConfig.stack[field]) {
|
|
99
|
+
stackOverride[field] = pkgValue;
|
|
100
|
+
hasStackDiff = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (hasStackDiff) {
|
|
104
|
+
override.stack = stackOverride;
|
|
105
|
+
hasDiff = true;
|
|
106
|
+
}
|
|
107
|
+
const conventionOverrides = conventionsDiffer(pkg.conventions, globalConfig.conventions);
|
|
108
|
+
if (conventionOverrides) {
|
|
109
|
+
override.conventions = conventionOverrides;
|
|
110
|
+
hasDiff = true;
|
|
111
|
+
}
|
|
112
|
+
if (hasDiff) {
|
|
113
|
+
overrides.push(override);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return overrides.length > 0 ? overrides : void 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/generate-config.ts
|
|
56
120
|
function formatStackItem(item) {
|
|
57
121
|
return item.version ? `${item.name}@${item.version}` : item.name;
|
|
58
122
|
}
|
|
@@ -66,6 +130,7 @@ function mapStack(scanResult) {
|
|
|
66
130
|
if (stack.styling) config.styling = formatStackItem(stack.styling);
|
|
67
131
|
if (stack.backend) config.backend = formatStackItem(stack.backend);
|
|
68
132
|
if (stack.linter) config.linter = formatStackItem(stack.linter);
|
|
133
|
+
if (stack.formatter) config.formatter = formatStackItem(stack.formatter);
|
|
69
134
|
if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);
|
|
70
135
|
return config;
|
|
71
136
|
}
|
|
@@ -142,12 +207,17 @@ function generateConfig(scanResult) {
|
|
|
142
207
|
};
|
|
143
208
|
config.boundaries = [];
|
|
144
209
|
}
|
|
210
|
+
const packageOverrides = generatePackageOverrides(scanResult, config);
|
|
211
|
+
if (packageOverrides) {
|
|
212
|
+
config.packages = packageOverrides;
|
|
213
|
+
}
|
|
145
214
|
return config;
|
|
146
215
|
}
|
|
147
216
|
|
|
148
217
|
// src/load-config.ts
|
|
149
218
|
var fs = __toESM(require("fs/promises"), 1);
|
|
150
219
|
function validateConfig(parsed, configPath) {
|
|
220
|
+
const errors = [];
|
|
151
221
|
const required = ["version", "name", "stack", "rules"];
|
|
152
222
|
const missing = required.filter((field) => parsed[field] === void 0);
|
|
153
223
|
if (missing.length > 0) {
|
|
@@ -155,6 +225,40 @@ function validateConfig(parsed, configPath) {
|
|
|
155
225
|
`Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(", ")}`
|
|
156
226
|
);
|
|
157
227
|
}
|
|
228
|
+
if (typeof parsed.version !== "number") errors.push('"version" must be a number');
|
|
229
|
+
if (typeof parsed.name !== "string") errors.push('"name" must be a string');
|
|
230
|
+
if (parsed.enforcement !== void 0 && parsed.enforcement !== "warn" && parsed.enforcement !== "enforce") {
|
|
231
|
+
errors.push('"enforcement" must be "warn" or "enforce"');
|
|
232
|
+
}
|
|
233
|
+
if (typeof parsed.stack !== "object" || parsed.stack === null) {
|
|
234
|
+
errors.push('"stack" must be an object');
|
|
235
|
+
} else {
|
|
236
|
+
const stack = parsed.stack;
|
|
237
|
+
if (typeof stack.language !== "string") errors.push('"stack.language" must be a string');
|
|
238
|
+
if (typeof stack.packageManager !== "string")
|
|
239
|
+
errors.push('"stack.packageManager" must be a string');
|
|
240
|
+
}
|
|
241
|
+
if (typeof parsed.rules !== "object" || parsed.rules === null) {
|
|
242
|
+
errors.push('"rules" must be an object');
|
|
243
|
+
} else {
|
|
244
|
+
const rules = parsed.rules;
|
|
245
|
+
if (typeof rules.maxFileLines !== "number")
|
|
246
|
+
errors.push('"rules.maxFileLines" must be a number');
|
|
247
|
+
if (typeof rules.maxFunctionLines !== "number")
|
|
248
|
+
errors.push('"rules.maxFunctionLines" must be a number');
|
|
249
|
+
if (typeof rules.requireTests !== "boolean")
|
|
250
|
+
errors.push('"rules.requireTests" must be a boolean');
|
|
251
|
+
if (typeof rules.enforceNaming !== "boolean")
|
|
252
|
+
errors.push('"rules.enforceNaming" must be a boolean');
|
|
253
|
+
if (typeof rules.enforceBoundaries !== "boolean")
|
|
254
|
+
errors.push('"rules.enforceBoundaries" must be a boolean');
|
|
255
|
+
}
|
|
256
|
+
if (parsed.ignore !== void 0 && !Array.isArray(parsed.ignore)) {
|
|
257
|
+
errors.push('"ignore" must be an array');
|
|
258
|
+
}
|
|
259
|
+
if (errors.length > 0) {
|
|
260
|
+
throw new Error(`Invalid viberails config at ${configPath}: ${errors.join("; ")}`);
|
|
261
|
+
}
|
|
158
262
|
}
|
|
159
263
|
async function loadConfig(configPath) {
|
|
160
264
|
let raw;
|
|
@@ -193,6 +297,7 @@ function mergeStack(existing, fresh) {
|
|
|
193
297
|
styling: existing.styling ?? fresh.styling,
|
|
194
298
|
backend: existing.backend ?? fresh.backend,
|
|
195
299
|
linter: existing.linter ?? fresh.linter,
|
|
300
|
+
formatter: existing.formatter ?? fresh.formatter,
|
|
196
301
|
testRunner: existing.testRunner ?? fresh.testRunner
|
|
197
302
|
};
|
|
198
303
|
}
|
|
@@ -217,15 +322,9 @@ function markAsDetected(value) {
|
|
|
217
322
|
}
|
|
218
323
|
return { ...value, _detected: true };
|
|
219
324
|
}
|
|
220
|
-
var CONVENTION_KEYS2 = [
|
|
221
|
-
"fileNaming",
|
|
222
|
-
"componentNaming",
|
|
223
|
-
"hookNaming",
|
|
224
|
-
"importAlias"
|
|
225
|
-
];
|
|
226
325
|
function mergeConventions(existing, fresh) {
|
|
227
326
|
const merged = { ...existing };
|
|
228
|
-
for (const key of
|
|
327
|
+
for (const key of CONVENTION_KEYS) {
|
|
229
328
|
if (!hasConvention(existing, key) && fresh[key] !== void 0) {
|
|
230
329
|
merged[key] = markAsDetected(fresh[key]);
|
|
231
330
|
}
|
|
@@ -253,8 +352,23 @@ function mergeConfig(existing, scanResult) {
|
|
|
253
352
|
} else if (fresh.boundaries) {
|
|
254
353
|
merged.boundaries = [...fresh.boundaries];
|
|
255
354
|
}
|
|
355
|
+
if (existing.packages || fresh.packages) {
|
|
356
|
+
merged.packages = mergePackageOverrides(existing.packages, fresh.packages);
|
|
357
|
+
}
|
|
256
358
|
return merged;
|
|
257
359
|
}
|
|
360
|
+
function mergePackageOverrides(existing, fresh) {
|
|
361
|
+
if (!fresh || fresh.length === 0) return existing;
|
|
362
|
+
if (!existing || existing.length === 0) return fresh;
|
|
363
|
+
const existingByPath = new Map(existing.map((p) => [p.path, p]));
|
|
364
|
+
const merged = [...existing];
|
|
365
|
+
for (const freshPkg of fresh) {
|
|
366
|
+
if (!existingByPath.has(freshPkg.path)) {
|
|
367
|
+
merged.push(freshPkg);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return merged.length > 0 ? merged : void 0;
|
|
371
|
+
}
|
|
258
372
|
|
|
259
373
|
// src/schema.ts
|
|
260
374
|
var configSchema = {
|
|
@@ -312,6 +426,10 @@ var configSchema = {
|
|
|
312
426
|
type: "string",
|
|
313
427
|
description: 'Linter (e.g. "eslint@9", "biome").'
|
|
314
428
|
},
|
|
429
|
+
formatter: {
|
|
430
|
+
type: "string",
|
|
431
|
+
description: 'Formatter (e.g. "prettier", "biome").'
|
|
432
|
+
},
|
|
315
433
|
testRunner: {
|
|
316
434
|
type: "string",
|
|
317
435
|
description: 'Test runner (e.g. "vitest", "jest").'
|
|
@@ -457,6 +575,46 @@ var configSchema = {
|
|
|
457
575
|
},
|
|
458
576
|
additionalProperties: false,
|
|
459
577
|
description: "Workspace configuration for monorepo projects."
|
|
578
|
+
},
|
|
579
|
+
packages: {
|
|
580
|
+
type: "array",
|
|
581
|
+
items: {
|
|
582
|
+
type: "object",
|
|
583
|
+
required: ["name", "path"],
|
|
584
|
+
properties: {
|
|
585
|
+
name: { type: "string", description: "Package name from package.json." },
|
|
586
|
+
path: { type: "string", description: "Relative path to the package." },
|
|
587
|
+
stack: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
framework: { type: "string" },
|
|
591
|
+
language: { type: "string" },
|
|
592
|
+
styling: { type: "string" },
|
|
593
|
+
backend: { type: "string" },
|
|
594
|
+
packageManager: { type: "string" },
|
|
595
|
+
linter: { type: "string" },
|
|
596
|
+
formatter: { type: "string" },
|
|
597
|
+
testRunner: { type: "string" }
|
|
598
|
+
},
|
|
599
|
+
additionalProperties: false
|
|
600
|
+
},
|
|
601
|
+
conventions: { $ref: "#/properties/conventions" },
|
|
602
|
+
rules: {
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {
|
|
605
|
+
maxFileLines: { type: "number" },
|
|
606
|
+
maxFunctionLines: { type: "number" },
|
|
607
|
+
requireTests: { type: "boolean" },
|
|
608
|
+
enforceNaming: { type: "boolean" },
|
|
609
|
+
enforceBoundaries: { type: "boolean" }
|
|
610
|
+
},
|
|
611
|
+
additionalProperties: false
|
|
612
|
+
},
|
|
613
|
+
ignore: { type: "array", items: { type: "string" } }
|
|
614
|
+
},
|
|
615
|
+
additionalProperties: false
|
|
616
|
+
},
|
|
617
|
+
description: "Per-package overrides for monorepo projects."
|
|
460
618
|
}
|
|
461
619
|
},
|
|
462
620
|
additionalProperties: false,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/defaults.ts","../src/generate-config.ts","../src/load-config.ts","../src/merge-config.ts","../src/schema.ts"],"sourcesContent":["export const VERSION = '0.1.0';\n\nexport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nexport { generateConfig } from './generate-config.js';\nexport { loadConfig, loadConfigSafe } from './load-config.js';\nexport { mergeConfig } from './merge-config.js';\nexport { configSchema } from './schema.js';\n","import type { ConfigRules } from '@viberails/types';\n\n/**\n * Default rule thresholds and toggles for a new viberails config.\n * These values are intentionally conservative to build trust on first run.\n */\nexport const DEFAULT_RULES: ConfigRules = {\n maxFileLines: 300,\n maxFunctionLines: 50,\n requireTests: true,\n enforceNaming: true,\n enforceBoundaries: false,\n};\n\n/**\n * Default glob patterns for files and directories to ignore.\n */\nexport const DEFAULT_IGNORE: string[] = ['**/*.d.ts', 'dist/**', 'node_modules/**'];\n","import * as path from 'node:path';\nimport type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n DetectedConvention,\n DirectoryRole,\n ScanResult,\n StackItem,\n ViberailsConfig,\n} from '@viberails/types';\nimport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\n\n/**\n * Format a StackItem as a config string: `\"name@version\"` or `\"name\"`.\n */\nfunction formatStackItem(item: StackItem): string {\n return item.version ? `${item.name}@${item.version}` : item.name;\n}\n\n/**\n * Map DetectedStack → ConfigStack by formatting each StackItem.\n */\nfunction mapStack(scanResult: ScanResult): ConfigStack {\n const { stack } = scanResult;\n const config: ConfigStack = {\n language: formatStackItem(stack.language),\n packageManager: formatStackItem(stack.packageManager),\n };\n\n if (stack.framework) config.framework = formatStackItem(stack.framework);\n if (stack.styling) config.styling = formatStackItem(stack.styling);\n if (stack.backend) config.backend = formatStackItem(stack.backend);\n if (stack.linter) config.linter = formatStackItem(stack.linter);\n if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);\n\n return config;\n}\n\n/** Directory roles that map to ConfigStructure fields. */\nconst ROLE_TO_FIELD: Partial<Record<DirectoryRole, keyof ConfigStructure>> = {\n pages: 'pages',\n components: 'components',\n hooks: 'hooks',\n utils: 'utils',\n types: 'types',\n tests: 'tests',\n};\n\n/**\n * Map DetectedStructure → ConfigStructure by finding the first directory\n * for each known role.\n */\nfunction mapStructure(scanResult: ScanResult): ConfigStructure {\n const { structure } = scanResult;\n const config: ConfigStructure = {};\n\n if (structure.srcDir) {\n config.srcDir = structure.srcDir;\n }\n\n for (const dir of structure.directories) {\n const field = ROLE_TO_FIELD[dir.role];\n if (field && config[field] === undefined) {\n (config as Record<string, string>)[field] = dir.path;\n }\n }\n\n if (structure.testPattern) {\n config.testPattern = structure.testPattern.value;\n }\n\n return config;\n}\n\n/**\n * Convert a DetectedConvention to a ConventionValue with metadata.\n * Returns undefined for low-confidence conventions (they are omitted).\n */\nfunction mapConvention(convention: DetectedConvention): ConventionValue | undefined {\n if (convention.confidence === 'low') {\n return undefined;\n }\n\n return {\n value: convention.value,\n _confidence: convention.confidence,\n _consistency: convention.consistency,\n };\n}\n\n/** Convention keys from ScanResult that map to ConfigConventions fields. */\nconst CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Map scanner conventions → ConfigConventions, omitting low-confidence entries.\n */\nfunction mapConventions(scanResult: ScanResult): ConfigConventions {\n const config: ConfigConventions = {};\n\n for (const key of CONVENTION_KEYS) {\n const detected = scanResult.conventions[key];\n if (detected) {\n const value = mapConvention(detected);\n if (value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n return config;\n}\n\n/**\n * Generate a ViberailsConfig from scan results.\n *\n * Maps the scanner's DetectedStack, DetectedStructure, and conventions\n * into the config format with smart defaults. Low-confidence conventions\n * are omitted. The project name is derived from the root directory basename.\n *\n * @param scanResult - The output of scanning a project\n * @returns A complete ViberailsConfig ready to be written as JSON\n */\nexport function generateConfig(scanResult: ScanResult): ViberailsConfig {\n const config: ViberailsConfig = {\n $schema: 'https://viberails.sh/schema/v1.json',\n version: 1,\n name: path.basename(scanResult.root),\n enforcement: 'warn',\n stack: mapStack(scanResult),\n structure: mapStructure(scanResult),\n conventions: mapConventions(scanResult),\n rules: { ...DEFAULT_RULES },\n ignore: [...DEFAULT_IGNORE],\n };\n\n if (scanResult.workspace) {\n config.workspace = {\n packages: scanResult.workspace.packages.map((p) => p.relativePath),\n isMonorepo: true,\n };\n config.boundaries = [];\n }\n\n return config;\n}\n","import * as fs from 'node:fs/promises';\nimport type { ViberailsConfig } from '@viberails/types';\n\n/**\n * Validate that a parsed object has the required ViberailsConfig fields.\n * Throws a descriptive error if any required field is missing.\n */\nfunction validateConfig(parsed: Record<string, unknown>, configPath: string): void {\n const required = ['version', 'name', 'stack', 'rules'] as const;\n const missing = required.filter((field) => parsed[field] === undefined);\n\n if (missing.length > 0) {\n throw new Error(\n `Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(', ')}`,\n );\n }\n}\n\n/**\n * Load and parse a viberails config file.\n *\n * Reads the JSON file at the given path, validates that it contains\n * the required fields (version, name, stack, rules), and returns\n * the parsed config.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig\n * @throws If the file doesn't exist, contains invalid JSON, or is missing required fields\n */\nexport async function loadConfig(configPath: string): Promise<ViberailsConfig> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new Error(`Config file not found: ${configPath}. Run \"npx viberails\" to generate one.`);\n }\n throw new Error(`Failed to read config file at ${configPath}: ${(err as Error).message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in config file at ${configPath}. Check for syntax errors.`);\n }\n\n validateConfig(parsed, configPath);\n\n return parsed as unknown as ViberailsConfig;\n}\n\n/**\n * Safely load a viberails config file, returning null on any error.\n *\n * Used by the CLI for \"does config already exist?\" checks where\n * failure is an expected case, not an error.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig, or null if loading fails\n */\nexport async function loadConfigSafe(configPath: string): Promise<ViberailsConfig | null> {\n try {\n return await loadConfig(configPath);\n } catch {\n return null;\n }\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { generateConfig } from './generate-config.js';\n\n/**\n * Merge stack: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStack(existing: ConfigStack, fresh: ConfigStack): ConfigStack {\n return {\n language: existing.language,\n packageManager: existing.packageManager,\n framework: existing.framework ?? fresh.framework,\n styling: existing.styling ?? fresh.styling,\n backend: existing.backend ?? fresh.backend,\n linter: existing.linter ?? fresh.linter,\n testRunner: existing.testRunner ?? fresh.testRunner,\n };\n}\n\n/**\n * Merge structure: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStructure(existing: ConfigStructure, fresh: ConfigStructure): ConfigStructure {\n return {\n srcDir: existing.srcDir ?? fresh.srcDir,\n pages: existing.pages ?? fresh.pages,\n components: existing.components ?? fresh.components,\n hooks: existing.hooks ?? fresh.hooks,\n utils: existing.utils ?? fresh.utils,\n types: existing.types ?? fresh.types,\n tests: existing.tests ?? fresh.tests,\n testPattern: existing.testPattern ?? fresh.testPattern,\n };\n}\n\n/**\n * Check if a convention key exists in the existing config\n * (either as a string or as an object with a value).\n */\nfunction hasConvention(conventions: ConfigConventions, key: keyof ConfigConventions): boolean {\n return conventions[key] !== undefined;\n}\n\n/**\n * Mark a ConventionValue as newly detected by adding `_detected: true`.\n * Only applies to object-form values (not plain strings).\n */\nfunction markAsDetected(value: ConventionValue): ConventionValue {\n if (typeof value === 'string') {\n return { value, _confidence: 'high', _consistency: 100, _detected: true };\n }\n return { ...value, _detected: true };\n}\n\n/** Convention keys to iterate during merge. */\nconst CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Merge conventions: keep all existing values, add new detections with `_detected: true`.\n */\nfunction mergeConventions(\n existing: ConfigConventions,\n fresh: ConfigConventions,\n): ConfigConventions {\n const merged: ConfigConventions = { ...existing };\n\n for (const key of CONVENTION_KEYS) {\n if (!hasConvention(existing, key) && fresh[key] !== undefined) {\n merged[key] = markAsDetected(fresh[key]!);\n }\n }\n\n return merged;\n}\n\n/**\n * Merge a new scan result into an existing config for `viberails sync`.\n *\n * Preserves all developer-confirmed values from the existing config.\n * Adds newly detected conventions with a `_detected: true` annotation\n * so the developer can review them. Never removes rules or values\n * the developer has set.\n *\n * @param existing - The current ViberailsConfig (from viberails.config.json)\n * @param scanResult - Fresh scan results from re-scanning the project\n * @returns A merged config that preserves existing values and adds new detections\n */\nexport function mergeConfig(existing: ViberailsConfig, scanResult: ScanResult): ViberailsConfig {\n const fresh = generateConfig(scanResult);\n\n const merged: ViberailsConfig = {\n $schema: existing.$schema ?? fresh.$schema,\n version: existing.version,\n name: existing.name,\n enforcement: existing.enforcement,\n stack: mergeStack(existing.stack, fresh.stack),\n structure: mergeStructure(existing.structure, fresh.structure),\n conventions: mergeConventions(existing.conventions, fresh.conventions),\n rules: { ...existing.rules },\n ignore: [...existing.ignore],\n };\n\n // Workspace: always take fresh scan (structure can change)\n if (fresh.workspace) {\n merged.workspace = fresh.workspace;\n }\n\n // Boundaries: preserve existing rules (user may have adjusted)\n if (existing.boundaries) {\n merged.boundaries = [...existing.boundaries];\n } else if (fresh.boundaries) {\n merged.boundaries = [...fresh.boundaries];\n }\n\n return merged;\n}\n","/**\n * JSON Schema (draft-07) definition for viberails.config.json.\n *\n * This schema will eventually be hosted at https://viberails.sh/schema/v1.json.\n * For now it is exported as a TypeScript object that can be serialized to JSON.\n */\nexport const configSchema = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://viberails.sh/schema/v1.json',\n title: 'viberails configuration',\n description: 'Configuration file for viberails — guardrails for vibe coding.',\n type: 'object',\n required: ['version', 'name', 'stack', 'rules'],\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema URL for editor validation.',\n },\n version: {\n type: 'number',\n const: 1,\n description: 'Config format version. Always 1 for V1.0.',\n },\n name: {\n type: 'string',\n description: 'Project name, typically from package.json.',\n },\n enforcement: {\n type: 'string',\n enum: ['warn', 'enforce'],\n default: 'warn',\n description: 'Whether conventions are warned about or enforced as errors.',\n },\n stack: {\n type: 'object',\n required: ['language', 'packageManager'],\n properties: {\n framework: {\n type: 'string',\n description: 'Primary framework identifier (e.g. \"nextjs@15\", \"remix@2\").',\n },\n language: {\n type: 'string',\n description: 'Primary language (e.g. \"typescript\", \"javascript\").',\n },\n styling: {\n type: 'string',\n description: 'Styling solution (e.g. \"tailwindcss@4\", \"css-modules\").',\n },\n backend: {\n type: 'string',\n description: 'Backend framework (e.g. \"express@5\", \"fastify\").',\n },\n packageManager: {\n type: 'string',\n description: 'Package manager (e.g. \"pnpm\", \"npm\", \"yarn\").',\n },\n linter: {\n type: 'string',\n description: 'Linter (e.g. \"eslint@9\", \"biome\").',\n },\n testRunner: {\n type: 'string',\n description: 'Test runner (e.g. \"vitest\", \"jest\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured technology stack.',\n },\n structure: {\n type: 'object',\n properties: {\n srcDir: {\n type: 'string',\n description: 'Source directory (e.g. \"src\"), or omit for flat structure.',\n },\n pages: {\n type: 'string',\n description: 'Pages or routes directory (e.g. \"src/app\").',\n },\n components: {\n type: 'string',\n description: 'Components directory (e.g. \"src/components\").',\n },\n hooks: {\n type: 'string',\n description: 'Hooks directory (e.g. \"src/hooks\").',\n },\n utils: {\n type: 'string',\n description: 'Utilities directory (e.g. \"src/utils\", \"src/lib\").',\n },\n types: {\n type: 'string',\n description: 'Type definitions directory (e.g. \"src/types\").',\n },\n tests: {\n type: 'string',\n description: 'Tests directory (e.g. \"tests\", \"__tests__\").',\n },\n testPattern: {\n type: 'string',\n description: 'Test file naming pattern (e.g. \"*.test.ts\", \"*.spec.ts\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured directory structure.',\n },\n conventions: {\n type: 'object',\n properties: {\n fileNaming: { $ref: '#/definitions/conventionValue' },\n componentNaming: { $ref: '#/definitions/conventionValue' },\n hookNaming: { $ref: '#/definitions/conventionValue' },\n importAlias: { $ref: '#/definitions/conventionValue' },\n },\n additionalProperties: false,\n description: 'Detected or configured coding conventions.',\n },\n rules: {\n type: 'object',\n required: [\n 'maxFileLines',\n 'maxFunctionLines',\n 'requireTests',\n 'enforceNaming',\n 'enforceBoundaries',\n ],\n properties: {\n maxFileLines: {\n type: 'number',\n default: 300,\n description: 'Maximum number of lines allowed per file.',\n },\n maxFunctionLines: {\n type: 'number',\n default: 50,\n description: 'Maximum number of lines allowed per function.',\n },\n requireTests: {\n type: 'boolean',\n default: true,\n description: 'Whether to require test files for source modules.',\n },\n enforceNaming: {\n type: 'boolean',\n default: true,\n description: 'Whether to enforce detected file naming conventions.',\n },\n enforceBoundaries: {\n type: 'boolean',\n default: false,\n description: 'Whether to enforce module boundary rules.',\n },\n },\n additionalProperties: false,\n description: 'Rule thresholds and toggles for enforcement.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Glob patterns for files and directories to ignore.',\n },\n boundaries: {\n type: 'array',\n items: {\n type: 'object',\n required: ['from', 'to', 'allow'],\n properties: {\n from: {\n type: 'string',\n description: 'Source package or directory pattern.',\n },\n to: {\n type: 'string',\n description: 'Target package or directory pattern.',\n },\n allow: {\n type: 'boolean',\n description: 'Whether this import direction is allowed or disallowed.',\n },\n reason: {\n type: 'string',\n description: 'Human-readable explanation of why this boundary exists.',\n },\n },\n additionalProperties: false,\n },\n description: 'Module boundary rules for import enforcement.',\n },\n workspace: {\n type: 'object',\n required: ['packages', 'isMonorepo'],\n properties: {\n packages: {\n type: 'array',\n items: { type: 'string' },\n description: 'Relative paths to workspace packages.',\n },\n isMonorepo: {\n type: 'boolean',\n description: 'Whether this project is a monorepo with multiple packages.',\n },\n },\n additionalProperties: false,\n description: 'Workspace configuration for monorepo projects.',\n },\n },\n additionalProperties: false,\n definitions: {\n conventionValue: {\n description:\n 'A convention value — either a plain string (user-confirmed) or an object with scanner metadata.',\n oneOf: [\n { type: 'string' },\n {\n type: 'object',\n required: ['value', '_confidence', '_consistency'],\n properties: {\n value: {\n type: 'string',\n description: 'The convention value.',\n },\n _confidence: {\n type: 'string',\n enum: ['high', 'medium', 'low'],\n description: 'Scanner confidence level.',\n },\n _consistency: {\n type: 'number',\n minimum: 0,\n maximum: 100,\n description: 'Scanner consistency percentage.',\n },\n _detected: {\n type: 'boolean',\n description: 'Set by mergeConfig when a convention is newly detected during sync.',\n },\n },\n additionalProperties: false,\n },\n ],\n },\n },\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMO,IAAM,gBAA6B;AAAA,EACxC,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,mBAAmB;AACrB;AAKO,IAAM,iBAA2B,CAAC,aAAa,WAAW,iBAAiB;;;ACjBlF,WAAsB;AAiBtB,SAAS,gBAAgB,MAAyB;AAChD,SAAO,KAAK,UAAU,GAAG,KAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK;AAC9D;AAKA,SAAS,SAAS,YAAqC;AACrD,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,SAAsB;AAAA,IAC1B,UAAU,gBAAgB,MAAM,QAAQ;AAAA,IACxC,gBAAgB,gBAAgB,MAAM,cAAc;AAAA,EACtD;AAEA,MAAI,MAAM,UAAW,QAAO,YAAY,gBAAgB,MAAM,SAAS;AACvE,MAAI,MAAM,QAAS,QAAO,UAAU,gBAAgB,MAAM,OAAO;AACjE,MAAI,MAAM,QAAS,QAAO,UAAU,gBAAgB,MAAM,OAAO;AACjE,MAAI,MAAM,OAAQ,QAAO,SAAS,gBAAgB,MAAM,MAAM;AAC9D,MAAI,MAAM,WAAY,QAAO,aAAa,gBAAgB,MAAM,UAAU;AAE1E,SAAO;AACT;AAGA,IAAM,gBAAuE;AAAA,EAC3E,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAMA,SAAS,aAAa,YAAyC;AAC7D,QAAM,EAAE,UAAU,IAAI;AACtB,QAAM,SAA0B,CAAC;AAEjC,MAAI,UAAU,QAAQ;AACpB,WAAO,SAAS,UAAU;AAAA,EAC5B;AAEA,aAAW,OAAO,UAAU,aAAa;AACvC,UAAM,QAAQ,cAAc,IAAI,IAAI;AACpC,QAAI,SAAS,OAAO,KAAK,MAAM,QAAW;AACxC,MAAC,OAAkC,KAAK,IAAI,IAAI;AAAA,IAClD;AAAA,EACF;AAEA,MAAI,UAAU,aAAa;AACzB,WAAO,cAAc,UAAU,YAAY;AAAA,EAC7C;AAEA,SAAO;AACT;AAMA,SAAS,cAAc,YAA6D;AAClF,MAAI,WAAW,eAAe,OAAO;AACnC,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,OAAO,WAAW;AAAA,IAClB,aAAa,WAAW;AAAA,IACxB,cAAc,WAAW;AAAA,EAC3B;AACF;AAGA,IAAM,kBAA+C;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,SAAS,eAAe,YAA2C;AACjE,QAAM,SAA4B,CAAC;AAEnC,aAAW,OAAO,iBAAiB;AACjC,UAAM,WAAW,WAAW,YAAY,GAAG;AAC3C,QAAI,UAAU;AACZ,YAAM,QAAQ,cAAc,QAAQ;AACpC,UAAI,UAAU,QAAW;AACvB,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,eAAe,YAAyC;AACtE,QAAM,SAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,MAAW,cAAS,WAAW,IAAI;AAAA,IACnC,aAAa;AAAA,IACb,OAAO,SAAS,UAAU;AAAA,IAC1B,WAAW,aAAa,UAAU;AAAA,IAClC,aAAa,eAAe,UAAU;AAAA,IACtC,OAAO,EAAE,GAAG,cAAc;AAAA,IAC1B,QAAQ,CAAC,GAAG,cAAc;AAAA,EAC5B;AAEA,MAAI,WAAW,WAAW;AACxB,WAAO,YAAY;AAAA,MACjB,UAAU,WAAW,UAAU,SAAS,IAAI,CAAC,MAAM,EAAE,YAAY;AAAA,MACjE,YAAY;AAAA,IACd;AACA,WAAO,aAAa,CAAC;AAAA,EACvB;AAEA,SAAO;AACT;;;ACvJA,SAAoB;AAOpB,SAAS,eAAe,QAAiC,YAA0B;AACjF,QAAM,WAAW,CAAC,WAAW,QAAQ,SAAS,OAAO;AACrD,QAAM,UAAU,SAAS,OAAO,CAAC,UAAU,OAAO,KAAK,MAAM,MAAS;AAEtE,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,+BAA+B,UAAU,gCAAgC,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7F;AAAA,EACF;AACF;AAaA,eAAsB,WAAW,YAA8C;AAC7E,MAAI;AACJ,MAAI;AACF,UAAM,MAAS,YAAS,YAAY,OAAO;AAAA,EAC7C,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAC5C,QAAI,SAAS,UAAU;AACrB,YAAM,IAAI,MAAM,0BAA0B,UAAU,wCAAwC;AAAA,IAC9F;AACA,UAAM,IAAI,MAAM,iCAAiC,UAAU,KAAM,IAAc,OAAO,EAAE;AAAA,EAC1F;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI,MAAM,kCAAkC,UAAU,4BAA4B;AAAA,EAC1F;AAEA,iBAAe,QAAQ,UAAU;AAEjC,SAAO;AACT;AAWA,eAAsB,eAAe,YAAqD;AACxF,MAAI;AACF,WAAO,MAAM,WAAW,UAAU;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACvDA,SAAS,WAAW,UAAuB,OAAiC;AAC1E,SAAO;AAAA,IACL,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,IACzB,WAAW,SAAS,aAAa,MAAM;AAAA,IACvC,SAAS,SAAS,WAAW,MAAM;AAAA,IACnC,SAAS,SAAS,WAAW,MAAM;AAAA,IACnC,QAAQ,SAAS,UAAU,MAAM;AAAA,IACjC,YAAY,SAAS,cAAc,MAAM;AAAA,EAC3C;AACF;AAKA,SAAS,eAAe,UAA2B,OAAyC;AAC1F,SAAO;AAAA,IACL,QAAQ,SAAS,UAAU,MAAM;AAAA,IACjC,OAAO,SAAS,SAAS,MAAM;AAAA,IAC/B,YAAY,SAAS,cAAc,MAAM;AAAA,IACzC,OAAO,SAAS,SAAS,MAAM;AAAA,IAC/B,OAAO,SAAS,SAAS,MAAM;AAAA,IAC/B,OAAO,SAAS,SAAS,MAAM;AAAA,IAC/B,OAAO,SAAS,SAAS,MAAM;AAAA,IAC/B,aAAa,SAAS,eAAe,MAAM;AAAA,EAC7C;AACF;AAMA,SAAS,cAAc,aAAgC,KAAuC;AAC5F,SAAO,YAAY,GAAG,MAAM;AAC9B;AAMA,SAAS,eAAe,OAAyC;AAC/D,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,OAAO,aAAa,QAAQ,cAAc,KAAK,WAAW,KAAK;AAAA,EAC1E;AACA,SAAO,EAAE,GAAG,OAAO,WAAW,KAAK;AACrC;AAGA,IAAMA,mBAA+C;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,SAAS,iBACP,UACA,OACmB;AACnB,QAAM,SAA4B,EAAE,GAAG,SAAS;AAEhD,aAAW,OAAOA,kBAAiB;AACjC,QAAI,CAAC,cAAc,UAAU,GAAG,KAAK,MAAM,GAAG,MAAM,QAAW;AAC7D,aAAO,GAAG,IAAI,eAAe,MAAM,GAAG,CAAE;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO;AACT;AAcO,SAAS,YAAY,UAA2B,YAAyC;AAC9F,QAAM,QAAQ,eAAe,UAAU;AAEvC,QAAM,SAA0B;AAAA,IAC9B,SAAS,SAAS,WAAW,MAAM;AAAA,IACnC,SAAS,SAAS;AAAA,IAClB,MAAM,SAAS;AAAA,IACf,aAAa,SAAS;AAAA,IACtB,OAAO,WAAW,SAAS,OAAO,MAAM,KAAK;AAAA,IAC7C,WAAW,eAAe,SAAS,WAAW,MAAM,SAAS;AAAA,IAC7D,aAAa,iBAAiB,SAAS,aAAa,MAAM,WAAW;AAAA,IACrE,OAAO,EAAE,GAAG,SAAS,MAAM;AAAA,IAC3B,QAAQ,CAAC,GAAG,SAAS,MAAM;AAAA,EAC7B;AAGA,MAAI,MAAM,WAAW;AACnB,WAAO,YAAY,MAAM;AAAA,EAC3B;AAGA,MAAI,SAAS,YAAY;AACvB,WAAO,aAAa,CAAC,GAAG,SAAS,UAAU;AAAA,EAC7C,WAAW,MAAM,YAAY;AAC3B,WAAO,aAAa,CAAC,GAAG,MAAM,UAAU;AAAA,EAC1C;AAEA,SAAO;AACT;;;ACxHO,IAAM,eAAe;AAAA,EAC1B,SAAS;AAAA,EACT,KAAK;AAAA,EACL,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU,CAAC,WAAW,QAAQ,SAAS,OAAO;AAAA,EAC9C,YAAY;AAAA,IACV,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,MAAM,CAAC,QAAQ,SAAS;AAAA,MACxB,SAAS;AAAA,MACT,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,CAAC,YAAY,gBAAgB;AAAA,MACvC,YAAY;AAAA,QACV,WAAW;AAAA,UACT,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,UAAU;AAAA,UACR,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,SAAS;AAAA,UACP,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,SAAS;AAAA,UACP,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,gBAAgB;AAAA,UACd,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,YAAY;AAAA,UACV,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,YAAY;AAAA,UACV,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,aAAa;AAAA,UACX,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,YAAY,EAAE,MAAM,gCAAgC;AAAA,QACpD,iBAAiB,EAAE,MAAM,gCAAgC;AAAA,QACzD,YAAY,EAAE,MAAM,gCAAgC;AAAA,QACpD,aAAa,EAAE,MAAM,gCAAgC;AAAA,MACvD;AAAA,MACA,sBAAsB;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,YAAY;AAAA,QACV,cAAc;AAAA,UACZ,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,QACA,kBAAkB;AAAA,UAChB,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,QACA,cAAc;AAAA,UACZ,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,QACA,eAAe;AAAA,UACb,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,QACA,mBAAmB;AAAA,UACjB,MAAM;AAAA,UACN,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,SAAS;AAAA,MACxB,aAAa;AAAA,IACf;AAAA,IACA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,OAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,CAAC,QAAQ,MAAM,OAAO;AAAA,QAChC,YAAY;AAAA,UACV,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,IAAI;AAAA,YACF,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,OAAO;AAAA,YACL,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,QAAQ;AAAA,YACN,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,QACF;AAAA,QACA,sBAAsB;AAAA,MACxB;AAAA,MACA,aAAa;AAAA,IACf;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,UAAU,CAAC,YAAY,YAAY;AAAA,MACnC,YAAY;AAAA,QACV,UAAU;AAAA,UACR,MAAM;AAAA,UACN,OAAO,EAAE,MAAM,SAAS;AAAA,UACxB,aAAa;AAAA,QACf;AAAA,QACA,YAAY;AAAA,UACV,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,MACtB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,EACtB,aAAa;AAAA,IACX,iBAAiB;AAAA,MACf,aACE;AAAA,MACF,OAAO;AAAA,QACL,EAAE,MAAM,SAAS;AAAA,QACjB;AAAA,UACE,MAAM;AAAA,UACN,UAAU,CAAC,SAAS,eAAe,cAAc;AAAA,UACjD,YAAY;AAAA,YACV,OAAO;AAAA,cACL,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,aAAa;AAAA,cACX,MAAM;AAAA,cACN,MAAM,CAAC,QAAQ,UAAU,KAAK;AAAA,cAC9B,aAAa;AAAA,YACf;AAAA,YACA,cAAc;AAAA,cACZ,MAAM;AAAA,cACN,SAAS;AAAA,cACT,SAAS;AAAA,cACT,aAAa;AAAA,YACf;AAAA,YACA,WAAW;AAAA,cACT,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,UACF;AAAA,UACA,sBAAsB;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ALpPO,IAAM,UAAU;","names":["CONVENTION_KEYS"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/defaults.ts","../src/generate-config.ts","../src/generate-overrides.ts","../src/load-config.ts","../src/merge-config.ts","../src/schema.ts"],"sourcesContent":["export const VERSION = '0.1.0';\n\nexport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nexport { generateConfig } from './generate-config.js';\nexport { loadConfig, loadConfigSafe } from './load-config.js';\nexport { mergeConfig } from './merge-config.js';\nexport { configSchema } from './schema.js';\n","import type { ConfigRules } from '@viberails/types';\n\n/**\n * Default rule thresholds and toggles for a new viberails config.\n * These values are intentionally conservative to build trust on first run.\n */\nexport const DEFAULT_RULES: ConfigRules = {\n maxFileLines: 300,\n maxFunctionLines: 50,\n requireTests: true,\n enforceNaming: true,\n enforceBoundaries: false,\n};\n\n/**\n * Default glob patterns for files and directories to ignore.\n */\nexport const DEFAULT_IGNORE: string[] = ['**/*.d.ts', 'dist/**', 'node_modules/**'];\n","import * as path from 'node:path';\nimport type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n DetectedConvention,\n DirectoryRole,\n ScanResult,\n StackItem,\n ViberailsConfig,\n} from '@viberails/types';\nimport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nimport { generatePackageOverrides } from './generate-overrides.js';\n\n/**\n * Format a StackItem as a config string: `\"name@version\"` or `\"name\"`.\n */\nexport function formatStackItem(item: StackItem): string {\n return item.version ? `${item.name}@${item.version}` : item.name;\n}\n\n/**\n * Map DetectedStack → ConfigStack by formatting each StackItem.\n */\nfunction mapStack(scanResult: ScanResult): ConfigStack {\n const { stack } = scanResult;\n const config: ConfigStack = {\n language: formatStackItem(stack.language),\n packageManager: formatStackItem(stack.packageManager),\n };\n\n if (stack.framework) config.framework = formatStackItem(stack.framework);\n if (stack.styling) config.styling = formatStackItem(stack.styling);\n if (stack.backend) config.backend = formatStackItem(stack.backend);\n if (stack.linter) config.linter = formatStackItem(stack.linter);\n if (stack.formatter) config.formatter = formatStackItem(stack.formatter);\n if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);\n\n return config;\n}\n\n/** Directory roles that map to ConfigStructure fields. */\nconst ROLE_TO_FIELD: Partial<Record<DirectoryRole, keyof ConfigStructure>> = {\n pages: 'pages',\n components: 'components',\n hooks: 'hooks',\n utils: 'utils',\n types: 'types',\n tests: 'tests',\n};\n\n/**\n * Map DetectedStructure → ConfigStructure by finding the first directory\n * for each known role.\n */\nfunction mapStructure(scanResult: ScanResult): ConfigStructure {\n const { structure } = scanResult;\n const config: ConfigStructure = {};\n\n if (structure.srcDir) {\n config.srcDir = structure.srcDir;\n }\n\n for (const dir of structure.directories) {\n const field = ROLE_TO_FIELD[dir.role];\n if (field && config[field] === undefined) {\n (config as Record<string, string>)[field] = dir.path;\n }\n }\n\n if (structure.testPattern) {\n config.testPattern = structure.testPattern.value;\n }\n\n return config;\n}\n\n/**\n * Convert a DetectedConvention to a ConventionValue with metadata.\n * Returns undefined for low-confidence conventions (they are omitted).\n */\nexport function mapConvention(convention: DetectedConvention): ConventionValue | undefined {\n if (convention.confidence === 'low') {\n return undefined;\n }\n\n return {\n value: convention.value,\n _confidence: convention.confidence,\n _consistency: convention.consistency,\n };\n}\n\n/** Convention keys from ScanResult that map to ConfigConventions fields. */\nexport const CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Map scanner conventions → ConfigConventions, omitting low-confidence entries.\n */\nfunction mapConventions(scanResult: ScanResult): ConfigConventions {\n const config: ConfigConventions = {};\n\n for (const key of CONVENTION_KEYS) {\n const detected = scanResult.conventions[key];\n if (detected) {\n const value = mapConvention(detected);\n if (value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n return config;\n}\n\n/**\n * Generate a ViberailsConfig from scan results.\n *\n * Maps the scanner's DetectedStack, DetectedStructure, and conventions\n * into the config format with smart defaults. Low-confidence conventions\n * are omitted. The project name is derived from the root directory basename.\n *\n * @param scanResult - The output of scanning a project\n * @returns A complete ViberailsConfig ready to be written as JSON\n */\nexport function generateConfig(scanResult: ScanResult): ViberailsConfig {\n const config: ViberailsConfig = {\n $schema: 'https://viberails.sh/schema/v1.json',\n version: 1,\n name: path.basename(scanResult.root),\n enforcement: 'warn',\n stack: mapStack(scanResult),\n structure: mapStructure(scanResult),\n conventions: mapConventions(scanResult),\n rules: { ...DEFAULT_RULES },\n ignore: [...DEFAULT_IGNORE],\n };\n\n if (scanResult.workspace) {\n config.workspace = {\n packages: scanResult.workspace.packages.map((p) => p.relativePath),\n isMonorepo: true,\n };\n config.boundaries = [];\n }\n\n const packageOverrides = generatePackageOverrides(scanResult, config);\n if (packageOverrides) {\n config.packages = packageOverrides;\n }\n\n return config;\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n DetectedConvention,\n PackageConfigOverrides,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { CONVENTION_KEYS, formatStackItem, mapConvention } from './generate-config.js';\n\n/**\n * Compare a package's conventions against the global config conventions.\n * Returns only the differing conventions, or undefined if all match.\n */\nexport function conventionsDiffer(\n pkgConventions: Record<string, DetectedConvention>,\n globalConventions: ConfigConventions,\n): Partial<ConfigConventions> | undefined {\n const overrides: Partial<ConfigConventions> = {};\n let hasDiff = false;\n\n for (const key of CONVENTION_KEYS) {\n const detected = pkgConventions[key];\n if (!detected) continue;\n\n const mapped = mapConvention(detected);\n if (mapped === undefined) continue;\n\n const globalValue = globalConventions[key];\n const globalStr = typeof globalValue === 'string' ? globalValue : globalValue?.value;\n if (detected.value !== globalStr) {\n overrides[key] = mapped;\n hasDiff = true;\n }\n }\n\n return hasDiff ? overrides : undefined;\n}\n\n/**\n * Generate per-package config overrides for packages whose stack or\n * conventions differ from the aggregate global config.\n */\nexport function generatePackageOverrides(\n scanResult: ScanResult,\n globalConfig: ViberailsConfig,\n): PackageConfigOverrides[] | undefined {\n if (!scanResult.packages || scanResult.packages.length <= 1) return undefined;\n\n const overrides: PackageConfigOverrides[] = [];\n\n for (const pkg of scanResult.packages) {\n const override: PackageConfigOverrides = {\n name: pkg.name,\n path: pkg.relativePath,\n };\n let hasDiff = false;\n\n // Compare stack fields — only include overrides when the package has a\n // value that differs from the global, not when the package simply lacks the field\n const stackOverride: Partial<ConfigStack> = {};\n let hasStackDiff = false;\n\n const optionalStackFields = [\n 'framework',\n 'styling',\n 'backend',\n 'linter',\n 'formatter',\n 'testRunner',\n ] as const;\n for (const field of optionalStackFields) {\n const pkgItem = pkg.stack[field];\n if (!pkgItem) continue;\n const pkgValue = formatStackItem(pkgItem);\n if (pkgValue !== globalConfig.stack[field]) {\n stackOverride[field] = pkgValue;\n hasStackDiff = true;\n }\n }\n\n if (hasStackDiff) {\n override.stack = stackOverride;\n hasDiff = true;\n }\n\n // Compare conventions\n const conventionOverrides = conventionsDiffer(pkg.conventions, globalConfig.conventions);\n if (conventionOverrides) {\n override.conventions = conventionOverrides;\n hasDiff = true;\n }\n\n if (hasDiff) {\n overrides.push(override);\n }\n }\n\n return overrides.length > 0 ? overrides : undefined;\n}\n","import * as fs from 'node:fs/promises';\nimport type { ViberailsConfig } from '@viberails/types';\n\n/**\n * Validate that a parsed object has the required ViberailsConfig fields\n * and that their types are correct.\n * Throws a descriptive error if validation fails.\n */\nfunction validateConfig(parsed: Record<string, unknown>, configPath: string): void {\n const errors: string[] = [];\n\n // Required top-level fields\n const required = ['version', 'name', 'stack', 'rules'] as const;\n const missing = required.filter((field) => parsed[field] === undefined);\n if (missing.length > 0) {\n throw new Error(\n `Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(', ')}`,\n );\n }\n\n // Type checks\n if (typeof parsed.version !== 'number') errors.push('\"version\" must be a number');\n if (typeof parsed.name !== 'string') errors.push('\"name\" must be a string');\n if (\n parsed.enforcement !== undefined &&\n parsed.enforcement !== 'warn' &&\n parsed.enforcement !== 'enforce'\n ) {\n errors.push('\"enforcement\" must be \"warn\" or \"enforce\"');\n }\n\n // Stack validation\n if (typeof parsed.stack !== 'object' || parsed.stack === null) {\n errors.push('\"stack\" must be an object');\n } else {\n const stack = parsed.stack as Record<string, unknown>;\n if (typeof stack.language !== 'string') errors.push('\"stack.language\" must be a string');\n if (typeof stack.packageManager !== 'string')\n errors.push('\"stack.packageManager\" must be a string');\n }\n\n // Rules validation\n if (typeof parsed.rules !== 'object' || parsed.rules === null) {\n errors.push('\"rules\" must be an object');\n } else {\n const rules = parsed.rules as Record<string, unknown>;\n if (typeof rules.maxFileLines !== 'number')\n errors.push('\"rules.maxFileLines\" must be a number');\n if (typeof rules.maxFunctionLines !== 'number')\n errors.push('\"rules.maxFunctionLines\" must be a number');\n if (typeof rules.requireTests !== 'boolean')\n errors.push('\"rules.requireTests\" must be a boolean');\n if (typeof rules.enforceNaming !== 'boolean')\n errors.push('\"rules.enforceNaming\" must be a boolean');\n if (typeof rules.enforceBoundaries !== 'boolean')\n errors.push('\"rules.enforceBoundaries\" must be a boolean');\n }\n\n // Ignore validation\n if (parsed.ignore !== undefined && !Array.isArray(parsed.ignore)) {\n errors.push('\"ignore\" must be an array');\n }\n\n if (errors.length > 0) {\n throw new Error(`Invalid viberails config at ${configPath}: ${errors.join('; ')}`);\n }\n}\n\n/**\n * Load and parse a viberails config file.\n *\n * Reads the JSON file at the given path, validates that it contains\n * the required fields (version, name, stack, rules), and returns\n * the parsed config.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig\n * @throws If the file doesn't exist, contains invalid JSON, or is missing required fields\n */\nexport async function loadConfig(configPath: string): Promise<ViberailsConfig> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new Error(`Config file not found: ${configPath}. Run \"npx viberails\" to generate one.`);\n }\n throw new Error(`Failed to read config file at ${configPath}: ${(err as Error).message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in config file at ${configPath}. Check for syntax errors.`);\n }\n\n validateConfig(parsed, configPath);\n\n // Safe to cast: validateConfig has verified all required fields and types\n return parsed as unknown as ViberailsConfig;\n}\n\n/**\n * Safely load a viberails config file, returning null on any error.\n *\n * Used by the CLI for \"does config already exist?\" checks where\n * failure is an expected case, not an error.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig, or null if loading fails\n */\nexport async function loadConfigSafe(configPath: string): Promise<ViberailsConfig | null> {\n try {\n return await loadConfig(configPath);\n } catch {\n return null;\n }\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n PackageConfigOverrides,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { CONVENTION_KEYS, generateConfig } from './generate-config.js';\n\n/**\n * Merge stack: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStack(existing: ConfigStack, fresh: ConfigStack): ConfigStack {\n return {\n language: existing.language,\n packageManager: existing.packageManager,\n framework: existing.framework ?? fresh.framework,\n styling: existing.styling ?? fresh.styling,\n backend: existing.backend ?? fresh.backend,\n linter: existing.linter ?? fresh.linter,\n formatter: existing.formatter ?? fresh.formatter,\n testRunner: existing.testRunner ?? fresh.testRunner,\n };\n}\n\n/**\n * Merge structure: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStructure(existing: ConfigStructure, fresh: ConfigStructure): ConfigStructure {\n return {\n srcDir: existing.srcDir ?? fresh.srcDir,\n pages: existing.pages ?? fresh.pages,\n components: existing.components ?? fresh.components,\n hooks: existing.hooks ?? fresh.hooks,\n utils: existing.utils ?? fresh.utils,\n types: existing.types ?? fresh.types,\n tests: existing.tests ?? fresh.tests,\n testPattern: existing.testPattern ?? fresh.testPattern,\n };\n}\n\n/**\n * Check if a convention key exists in the existing config\n * (either as a string or as an object with a value).\n */\nfunction hasConvention(conventions: ConfigConventions, key: keyof ConfigConventions): boolean {\n return conventions[key] !== undefined;\n}\n\n/**\n * Mark a ConventionValue as newly detected by adding `_detected: true`.\n * Only applies to object-form values (not plain strings).\n */\nfunction markAsDetected(value: ConventionValue): ConventionValue {\n if (typeof value === 'string') {\n return { value, _confidence: 'high', _consistency: 100, _detected: true };\n }\n return { ...value, _detected: true };\n}\n\n/**\n * Merge conventions: keep all existing values, add new detections with `_detected: true`.\n */\nfunction mergeConventions(\n existing: ConfigConventions,\n fresh: ConfigConventions,\n): ConfigConventions {\n const merged: ConfigConventions = { ...existing };\n\n for (const key of CONVENTION_KEYS) {\n if (!hasConvention(existing, key) && fresh[key] !== undefined) {\n merged[key] = markAsDetected(fresh[key]!);\n }\n }\n\n return merged;\n}\n\n/**\n * Merge a new scan result into an existing config for `viberails sync`.\n *\n * Preserves all developer-confirmed values from the existing config.\n * Adds newly detected conventions with a `_detected: true` annotation\n * so the developer can review them. Never removes rules or values\n * the developer has set.\n *\n * @param existing - The current ViberailsConfig (from viberails.config.json)\n * @param scanResult - Fresh scan results from re-scanning the project\n * @returns A merged config that preserves existing values and adds new detections\n */\nexport function mergeConfig(existing: ViberailsConfig, scanResult: ScanResult): ViberailsConfig {\n const fresh = generateConfig(scanResult);\n\n const merged: ViberailsConfig = {\n $schema: existing.$schema ?? fresh.$schema,\n version: existing.version,\n name: existing.name,\n enforcement: existing.enforcement,\n stack: mergeStack(existing.stack, fresh.stack),\n structure: mergeStructure(existing.structure, fresh.structure),\n conventions: mergeConventions(existing.conventions, fresh.conventions),\n rules: { ...existing.rules },\n ignore: [...existing.ignore],\n };\n\n // Workspace: always take fresh scan (structure can change)\n if (fresh.workspace) {\n merged.workspace = fresh.workspace;\n }\n\n // Boundaries: preserve existing rules (user may have adjusted)\n if (existing.boundaries) {\n merged.boundaries = [...existing.boundaries];\n } else if (fresh.boundaries) {\n merged.boundaries = [...fresh.boundaries];\n }\n\n // Packages: preserve existing overrides, add new ones\n if (existing.packages || fresh.packages) {\n merged.packages = mergePackageOverrides(existing.packages, fresh.packages);\n }\n\n return merged;\n}\n\n/**\n * Merge per-package overrides: keep existing user-edited overrides,\n * add new packages from fresh scan.\n */\nfunction mergePackageOverrides(\n existing?: PackageConfigOverrides[],\n fresh?: PackageConfigOverrides[],\n): PackageConfigOverrides[] | undefined {\n if (!fresh || fresh.length === 0) return existing;\n if (!existing || existing.length === 0) return fresh;\n\n const existingByPath = new Map(existing.map((p) => [p.path, p]));\n const merged: PackageConfigOverrides[] = [...existing];\n\n for (const freshPkg of fresh) {\n if (!existingByPath.has(freshPkg.path)) {\n merged.push(freshPkg);\n }\n }\n\n return merged.length > 0 ? merged : undefined;\n}\n","/**\n * JSON Schema (draft-07) definition for viberails.config.json.\n *\n * This schema will eventually be hosted at https://viberails.sh/schema/v1.json.\n * For now it is exported as a TypeScript object that can be serialized to JSON.\n */\nexport const configSchema = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://viberails.sh/schema/v1.json',\n title: 'viberails configuration',\n description: 'Configuration file for viberails — guardrails for vibe coding.',\n type: 'object',\n required: ['version', 'name', 'stack', 'rules'],\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema URL for editor validation.',\n },\n version: {\n type: 'number',\n const: 1,\n description: 'Config format version. Always 1 for V1.0.',\n },\n name: {\n type: 'string',\n description: 'Project name, typically from package.json.',\n },\n enforcement: {\n type: 'string',\n enum: ['warn', 'enforce'],\n default: 'warn',\n description: 'Whether conventions are warned about or enforced as errors.',\n },\n stack: {\n type: 'object',\n required: ['language', 'packageManager'],\n properties: {\n framework: {\n type: 'string',\n description: 'Primary framework identifier (e.g. \"nextjs@15\", \"remix@2\").',\n },\n language: {\n type: 'string',\n description: 'Primary language (e.g. \"typescript\", \"javascript\").',\n },\n styling: {\n type: 'string',\n description: 'Styling solution (e.g. \"tailwindcss@4\", \"css-modules\").',\n },\n backend: {\n type: 'string',\n description: 'Backend framework (e.g. \"express@5\", \"fastify\").',\n },\n packageManager: {\n type: 'string',\n description: 'Package manager (e.g. \"pnpm\", \"npm\", \"yarn\").',\n },\n linter: {\n type: 'string',\n description: 'Linter (e.g. \"eslint@9\", \"biome\").',\n },\n formatter: {\n type: 'string',\n description: 'Formatter (e.g. \"prettier\", \"biome\").',\n },\n testRunner: {\n type: 'string',\n description: 'Test runner (e.g. \"vitest\", \"jest\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured technology stack.',\n },\n structure: {\n type: 'object',\n properties: {\n srcDir: {\n type: 'string',\n description: 'Source directory (e.g. \"src\"), or omit for flat structure.',\n },\n pages: {\n type: 'string',\n description: 'Pages or routes directory (e.g. \"src/app\").',\n },\n components: {\n type: 'string',\n description: 'Components directory (e.g. \"src/components\").',\n },\n hooks: {\n type: 'string',\n description: 'Hooks directory (e.g. \"src/hooks\").',\n },\n utils: {\n type: 'string',\n description: 'Utilities directory (e.g. \"src/utils\", \"src/lib\").',\n },\n types: {\n type: 'string',\n description: 'Type definitions directory (e.g. \"src/types\").',\n },\n tests: {\n type: 'string',\n description: 'Tests directory (e.g. \"tests\", \"__tests__\").',\n },\n testPattern: {\n type: 'string',\n description: 'Test file naming pattern (e.g. \"*.test.ts\", \"*.spec.ts\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured directory structure.',\n },\n conventions: {\n type: 'object',\n properties: {\n fileNaming: { $ref: '#/definitions/conventionValue' },\n componentNaming: { $ref: '#/definitions/conventionValue' },\n hookNaming: { $ref: '#/definitions/conventionValue' },\n importAlias: { $ref: '#/definitions/conventionValue' },\n },\n additionalProperties: false,\n description: 'Detected or configured coding conventions.',\n },\n rules: {\n type: 'object',\n required: [\n 'maxFileLines',\n 'maxFunctionLines',\n 'requireTests',\n 'enforceNaming',\n 'enforceBoundaries',\n ],\n properties: {\n maxFileLines: {\n type: 'number',\n default: 300,\n description: 'Maximum number of lines allowed per file.',\n },\n maxFunctionLines: {\n type: 'number',\n default: 50,\n description: 'Maximum number of lines allowed per function.',\n },\n requireTests: {\n type: 'boolean',\n default: true,\n description: 'Whether to require test files for source modules.',\n },\n enforceNaming: {\n type: 'boolean',\n default: true,\n description: 'Whether to enforce detected file naming conventions.',\n },\n enforceBoundaries: {\n type: 'boolean',\n default: false,\n description: 'Whether to enforce module boundary rules.',\n },\n },\n additionalProperties: false,\n description: 'Rule thresholds and toggles for enforcement.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Glob patterns for files and directories to ignore.',\n },\n boundaries: {\n type: 'array',\n items: {\n type: 'object',\n required: ['from', 'to', 'allow'],\n properties: {\n from: {\n type: 'string',\n description: 'Source package or directory pattern.',\n },\n to: {\n type: 'string',\n description: 'Target package or directory pattern.',\n },\n allow: {\n type: 'boolean',\n description: 'Whether this import direction is allowed or disallowed.',\n },\n reason: {\n type: 'string',\n description: 'Human-readable explanation of why this boundary exists.',\n },\n },\n additionalProperties: false,\n },\n description: 'Module boundary rules for import enforcement.',\n },\n workspace: {\n type: 'object',\n required: ['packages', 'isMonorepo'],\n properties: {\n packages: {\n type: 'array',\n items: { type: 'string' },\n description: 'Relative paths to workspace packages.',\n },\n isMonorepo: {\n type: 'boolean',\n description: 'Whether this project is a monorepo with multiple packages.',\n },\n },\n additionalProperties: false,\n description: 'Workspace configuration for monorepo projects.',\n },\n packages: {\n type: 'array',\n items: {\n type: 'object',\n required: ['name', 'path'],\n properties: {\n name: { type: 'string', description: 'Package name from package.json.' },\n path: { type: 'string', description: 'Relative path to the package.' },\n stack: {\n type: 'object',\n properties: {\n framework: { type: 'string' },\n language: { type: 'string' },\n styling: { type: 'string' },\n backend: { type: 'string' },\n packageManager: { type: 'string' },\n linter: { type: 'string' },\n formatter: { type: 'string' },\n testRunner: { type: 'string' },\n },\n additionalProperties: false,\n },\n conventions: { $ref: '#/properties/conventions' },\n rules: {\n type: 'object',\n properties: {\n maxFileLines: { type: 'number' },\n maxFunctionLines: { type: 'number' },\n requireTests: { type: 'boolean' },\n enforceNaming: { type: 'boolean' },\n enforceBoundaries: { type: 'boolean' },\n },\n additionalProperties: false,\n },\n ignore: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n description: 'Per-package overrides for monorepo projects.',\n },\n },\n additionalProperties: false,\n definitions: {\n conventionValue: {\n description:\n 'A convention value — either a plain string (user-confirmed) or an object with scanner metadata.',\n oneOf: [\n { type: 'string' },\n {\n type: 'object',\n required: ['value', '_confidence', '_consistency'],\n properties: {\n value: {\n type: 'string',\n description: 'The convention value.',\n },\n _confidence: {\n type: 'string',\n enum: ['high', 'medium', 'low'],\n description: 'Scanner confidence level.',\n },\n _consistency: {\n type: 'number',\n minimum: 0,\n maximum: 100,\n description: 'Scanner consistency percentage.',\n },\n _detected: {\n type: 'boolean',\n description: 'Set by mergeConfig when a convention is newly detected during sync.',\n },\n },\n additionalProperties: false,\n },\n ],\n },\n },\n} as const;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -120,6 +120,10 @@ declare const configSchema: {
|
|
|
120
120
|
readonly type: "string";
|
|
121
121
|
readonly description: "Linter (e.g. \"eslint@9\", \"biome\").";
|
|
122
122
|
};
|
|
123
|
+
readonly formatter: {
|
|
124
|
+
readonly type: "string";
|
|
125
|
+
readonly description: "Formatter (e.g. \"prettier\", \"biome\").";
|
|
126
|
+
};
|
|
123
127
|
readonly testRunner: {
|
|
124
128
|
readonly type: "string";
|
|
125
129
|
readonly description: "Test runner (e.g. \"vitest\", \"jest\").";
|
|
@@ -272,6 +276,85 @@ declare const configSchema: {
|
|
|
272
276
|
readonly additionalProperties: false;
|
|
273
277
|
readonly description: "Workspace configuration for monorepo projects.";
|
|
274
278
|
};
|
|
279
|
+
readonly packages: {
|
|
280
|
+
readonly type: "array";
|
|
281
|
+
readonly items: {
|
|
282
|
+
readonly type: "object";
|
|
283
|
+
readonly required: readonly ["name", "path"];
|
|
284
|
+
readonly properties: {
|
|
285
|
+
readonly name: {
|
|
286
|
+
readonly type: "string";
|
|
287
|
+
readonly description: "Package name from package.json.";
|
|
288
|
+
};
|
|
289
|
+
readonly path: {
|
|
290
|
+
readonly type: "string";
|
|
291
|
+
readonly description: "Relative path to the package.";
|
|
292
|
+
};
|
|
293
|
+
readonly stack: {
|
|
294
|
+
readonly type: "object";
|
|
295
|
+
readonly properties: {
|
|
296
|
+
readonly framework: {
|
|
297
|
+
readonly type: "string";
|
|
298
|
+
};
|
|
299
|
+
readonly language: {
|
|
300
|
+
readonly type: "string";
|
|
301
|
+
};
|
|
302
|
+
readonly styling: {
|
|
303
|
+
readonly type: "string";
|
|
304
|
+
};
|
|
305
|
+
readonly backend: {
|
|
306
|
+
readonly type: "string";
|
|
307
|
+
};
|
|
308
|
+
readonly packageManager: {
|
|
309
|
+
readonly type: "string";
|
|
310
|
+
};
|
|
311
|
+
readonly linter: {
|
|
312
|
+
readonly type: "string";
|
|
313
|
+
};
|
|
314
|
+
readonly formatter: {
|
|
315
|
+
readonly type: "string";
|
|
316
|
+
};
|
|
317
|
+
readonly testRunner: {
|
|
318
|
+
readonly type: "string";
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
readonly additionalProperties: false;
|
|
322
|
+
};
|
|
323
|
+
readonly conventions: {
|
|
324
|
+
readonly $ref: "#/properties/conventions";
|
|
325
|
+
};
|
|
326
|
+
readonly rules: {
|
|
327
|
+
readonly type: "object";
|
|
328
|
+
readonly properties: {
|
|
329
|
+
readonly maxFileLines: {
|
|
330
|
+
readonly type: "number";
|
|
331
|
+
};
|
|
332
|
+
readonly maxFunctionLines: {
|
|
333
|
+
readonly type: "number";
|
|
334
|
+
};
|
|
335
|
+
readonly requireTests: {
|
|
336
|
+
readonly type: "boolean";
|
|
337
|
+
};
|
|
338
|
+
readonly enforceNaming: {
|
|
339
|
+
readonly type: "boolean";
|
|
340
|
+
};
|
|
341
|
+
readonly enforceBoundaries: {
|
|
342
|
+
readonly type: "boolean";
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
readonly additionalProperties: false;
|
|
346
|
+
};
|
|
347
|
+
readonly ignore: {
|
|
348
|
+
readonly type: "array";
|
|
349
|
+
readonly items: {
|
|
350
|
+
readonly type: "string";
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
readonly additionalProperties: false;
|
|
355
|
+
};
|
|
356
|
+
readonly description: "Per-package overrides for monorepo projects.";
|
|
357
|
+
};
|
|
275
358
|
};
|
|
276
359
|
readonly additionalProperties: false;
|
|
277
360
|
readonly definitions: {
|
package/dist/index.d.ts
CHANGED
|
@@ -120,6 +120,10 @@ declare const configSchema: {
|
|
|
120
120
|
readonly type: "string";
|
|
121
121
|
readonly description: "Linter (e.g. \"eslint@9\", \"biome\").";
|
|
122
122
|
};
|
|
123
|
+
readonly formatter: {
|
|
124
|
+
readonly type: "string";
|
|
125
|
+
readonly description: "Formatter (e.g. \"prettier\", \"biome\").";
|
|
126
|
+
};
|
|
123
127
|
readonly testRunner: {
|
|
124
128
|
readonly type: "string";
|
|
125
129
|
readonly description: "Test runner (e.g. \"vitest\", \"jest\").";
|
|
@@ -272,6 +276,85 @@ declare const configSchema: {
|
|
|
272
276
|
readonly additionalProperties: false;
|
|
273
277
|
readonly description: "Workspace configuration for monorepo projects.";
|
|
274
278
|
};
|
|
279
|
+
readonly packages: {
|
|
280
|
+
readonly type: "array";
|
|
281
|
+
readonly items: {
|
|
282
|
+
readonly type: "object";
|
|
283
|
+
readonly required: readonly ["name", "path"];
|
|
284
|
+
readonly properties: {
|
|
285
|
+
readonly name: {
|
|
286
|
+
readonly type: "string";
|
|
287
|
+
readonly description: "Package name from package.json.";
|
|
288
|
+
};
|
|
289
|
+
readonly path: {
|
|
290
|
+
readonly type: "string";
|
|
291
|
+
readonly description: "Relative path to the package.";
|
|
292
|
+
};
|
|
293
|
+
readonly stack: {
|
|
294
|
+
readonly type: "object";
|
|
295
|
+
readonly properties: {
|
|
296
|
+
readonly framework: {
|
|
297
|
+
readonly type: "string";
|
|
298
|
+
};
|
|
299
|
+
readonly language: {
|
|
300
|
+
readonly type: "string";
|
|
301
|
+
};
|
|
302
|
+
readonly styling: {
|
|
303
|
+
readonly type: "string";
|
|
304
|
+
};
|
|
305
|
+
readonly backend: {
|
|
306
|
+
readonly type: "string";
|
|
307
|
+
};
|
|
308
|
+
readonly packageManager: {
|
|
309
|
+
readonly type: "string";
|
|
310
|
+
};
|
|
311
|
+
readonly linter: {
|
|
312
|
+
readonly type: "string";
|
|
313
|
+
};
|
|
314
|
+
readonly formatter: {
|
|
315
|
+
readonly type: "string";
|
|
316
|
+
};
|
|
317
|
+
readonly testRunner: {
|
|
318
|
+
readonly type: "string";
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
readonly additionalProperties: false;
|
|
322
|
+
};
|
|
323
|
+
readonly conventions: {
|
|
324
|
+
readonly $ref: "#/properties/conventions";
|
|
325
|
+
};
|
|
326
|
+
readonly rules: {
|
|
327
|
+
readonly type: "object";
|
|
328
|
+
readonly properties: {
|
|
329
|
+
readonly maxFileLines: {
|
|
330
|
+
readonly type: "number";
|
|
331
|
+
};
|
|
332
|
+
readonly maxFunctionLines: {
|
|
333
|
+
readonly type: "number";
|
|
334
|
+
};
|
|
335
|
+
readonly requireTests: {
|
|
336
|
+
readonly type: "boolean";
|
|
337
|
+
};
|
|
338
|
+
readonly enforceNaming: {
|
|
339
|
+
readonly type: "boolean";
|
|
340
|
+
};
|
|
341
|
+
readonly enforceBoundaries: {
|
|
342
|
+
readonly type: "boolean";
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
readonly additionalProperties: false;
|
|
346
|
+
};
|
|
347
|
+
readonly ignore: {
|
|
348
|
+
readonly type: "array";
|
|
349
|
+
readonly items: {
|
|
350
|
+
readonly type: "string";
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
readonly additionalProperties: false;
|
|
355
|
+
};
|
|
356
|
+
readonly description: "Per-package overrides for monorepo projects.";
|
|
357
|
+
};
|
|
275
358
|
};
|
|
276
359
|
readonly additionalProperties: false;
|
|
277
360
|
readonly definitions: {
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,70 @@ var DEFAULT_IGNORE = ["**/*.d.ts", "dist/**", "node_modules/**"];
|
|
|
10
10
|
|
|
11
11
|
// src/generate-config.ts
|
|
12
12
|
import * as path from "path";
|
|
13
|
+
|
|
14
|
+
// src/generate-overrides.ts
|
|
15
|
+
function conventionsDiffer(pkgConventions, globalConventions) {
|
|
16
|
+
const overrides = {};
|
|
17
|
+
let hasDiff = false;
|
|
18
|
+
for (const key of CONVENTION_KEYS) {
|
|
19
|
+
const detected = pkgConventions[key];
|
|
20
|
+
if (!detected) continue;
|
|
21
|
+
const mapped = mapConvention(detected);
|
|
22
|
+
if (mapped === void 0) continue;
|
|
23
|
+
const globalValue = globalConventions[key];
|
|
24
|
+
const globalStr = typeof globalValue === "string" ? globalValue : globalValue?.value;
|
|
25
|
+
if (detected.value !== globalStr) {
|
|
26
|
+
overrides[key] = mapped;
|
|
27
|
+
hasDiff = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return hasDiff ? overrides : void 0;
|
|
31
|
+
}
|
|
32
|
+
function generatePackageOverrides(scanResult, globalConfig) {
|
|
33
|
+
if (!scanResult.packages || scanResult.packages.length <= 1) return void 0;
|
|
34
|
+
const overrides = [];
|
|
35
|
+
for (const pkg of scanResult.packages) {
|
|
36
|
+
const override = {
|
|
37
|
+
name: pkg.name,
|
|
38
|
+
path: pkg.relativePath
|
|
39
|
+
};
|
|
40
|
+
let hasDiff = false;
|
|
41
|
+
const stackOverride = {};
|
|
42
|
+
let hasStackDiff = false;
|
|
43
|
+
const optionalStackFields = [
|
|
44
|
+
"framework",
|
|
45
|
+
"styling",
|
|
46
|
+
"backend",
|
|
47
|
+
"linter",
|
|
48
|
+
"formatter",
|
|
49
|
+
"testRunner"
|
|
50
|
+
];
|
|
51
|
+
for (const field of optionalStackFields) {
|
|
52
|
+
const pkgItem = pkg.stack[field];
|
|
53
|
+
if (!pkgItem) continue;
|
|
54
|
+
const pkgValue = formatStackItem(pkgItem);
|
|
55
|
+
if (pkgValue !== globalConfig.stack[field]) {
|
|
56
|
+
stackOverride[field] = pkgValue;
|
|
57
|
+
hasStackDiff = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasStackDiff) {
|
|
61
|
+
override.stack = stackOverride;
|
|
62
|
+
hasDiff = true;
|
|
63
|
+
}
|
|
64
|
+
const conventionOverrides = conventionsDiffer(pkg.conventions, globalConfig.conventions);
|
|
65
|
+
if (conventionOverrides) {
|
|
66
|
+
override.conventions = conventionOverrides;
|
|
67
|
+
hasDiff = true;
|
|
68
|
+
}
|
|
69
|
+
if (hasDiff) {
|
|
70
|
+
overrides.push(override);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return overrides.length > 0 ? overrides : void 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/generate-config.ts
|
|
13
77
|
function formatStackItem(item) {
|
|
14
78
|
return item.version ? `${item.name}@${item.version}` : item.name;
|
|
15
79
|
}
|
|
@@ -23,6 +87,7 @@ function mapStack(scanResult) {
|
|
|
23
87
|
if (stack.styling) config.styling = formatStackItem(stack.styling);
|
|
24
88
|
if (stack.backend) config.backend = formatStackItem(stack.backend);
|
|
25
89
|
if (stack.linter) config.linter = formatStackItem(stack.linter);
|
|
90
|
+
if (stack.formatter) config.formatter = formatStackItem(stack.formatter);
|
|
26
91
|
if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);
|
|
27
92
|
return config;
|
|
28
93
|
}
|
|
@@ -99,12 +164,17 @@ function generateConfig(scanResult) {
|
|
|
99
164
|
};
|
|
100
165
|
config.boundaries = [];
|
|
101
166
|
}
|
|
167
|
+
const packageOverrides = generatePackageOverrides(scanResult, config);
|
|
168
|
+
if (packageOverrides) {
|
|
169
|
+
config.packages = packageOverrides;
|
|
170
|
+
}
|
|
102
171
|
return config;
|
|
103
172
|
}
|
|
104
173
|
|
|
105
174
|
// src/load-config.ts
|
|
106
175
|
import * as fs from "fs/promises";
|
|
107
176
|
function validateConfig(parsed, configPath) {
|
|
177
|
+
const errors = [];
|
|
108
178
|
const required = ["version", "name", "stack", "rules"];
|
|
109
179
|
const missing = required.filter((field) => parsed[field] === void 0);
|
|
110
180
|
if (missing.length > 0) {
|
|
@@ -112,6 +182,40 @@ function validateConfig(parsed, configPath) {
|
|
|
112
182
|
`Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(", ")}`
|
|
113
183
|
);
|
|
114
184
|
}
|
|
185
|
+
if (typeof parsed.version !== "number") errors.push('"version" must be a number');
|
|
186
|
+
if (typeof parsed.name !== "string") errors.push('"name" must be a string');
|
|
187
|
+
if (parsed.enforcement !== void 0 && parsed.enforcement !== "warn" && parsed.enforcement !== "enforce") {
|
|
188
|
+
errors.push('"enforcement" must be "warn" or "enforce"');
|
|
189
|
+
}
|
|
190
|
+
if (typeof parsed.stack !== "object" || parsed.stack === null) {
|
|
191
|
+
errors.push('"stack" must be an object');
|
|
192
|
+
} else {
|
|
193
|
+
const stack = parsed.stack;
|
|
194
|
+
if (typeof stack.language !== "string") errors.push('"stack.language" must be a string');
|
|
195
|
+
if (typeof stack.packageManager !== "string")
|
|
196
|
+
errors.push('"stack.packageManager" must be a string');
|
|
197
|
+
}
|
|
198
|
+
if (typeof parsed.rules !== "object" || parsed.rules === null) {
|
|
199
|
+
errors.push('"rules" must be an object');
|
|
200
|
+
} else {
|
|
201
|
+
const rules = parsed.rules;
|
|
202
|
+
if (typeof rules.maxFileLines !== "number")
|
|
203
|
+
errors.push('"rules.maxFileLines" must be a number');
|
|
204
|
+
if (typeof rules.maxFunctionLines !== "number")
|
|
205
|
+
errors.push('"rules.maxFunctionLines" must be a number');
|
|
206
|
+
if (typeof rules.requireTests !== "boolean")
|
|
207
|
+
errors.push('"rules.requireTests" must be a boolean');
|
|
208
|
+
if (typeof rules.enforceNaming !== "boolean")
|
|
209
|
+
errors.push('"rules.enforceNaming" must be a boolean');
|
|
210
|
+
if (typeof rules.enforceBoundaries !== "boolean")
|
|
211
|
+
errors.push('"rules.enforceBoundaries" must be a boolean');
|
|
212
|
+
}
|
|
213
|
+
if (parsed.ignore !== void 0 && !Array.isArray(parsed.ignore)) {
|
|
214
|
+
errors.push('"ignore" must be an array');
|
|
215
|
+
}
|
|
216
|
+
if (errors.length > 0) {
|
|
217
|
+
throw new Error(`Invalid viberails config at ${configPath}: ${errors.join("; ")}`);
|
|
218
|
+
}
|
|
115
219
|
}
|
|
116
220
|
async function loadConfig(configPath) {
|
|
117
221
|
let raw;
|
|
@@ -150,6 +254,7 @@ function mergeStack(existing, fresh) {
|
|
|
150
254
|
styling: existing.styling ?? fresh.styling,
|
|
151
255
|
backend: existing.backend ?? fresh.backend,
|
|
152
256
|
linter: existing.linter ?? fresh.linter,
|
|
257
|
+
formatter: existing.formatter ?? fresh.formatter,
|
|
153
258
|
testRunner: existing.testRunner ?? fresh.testRunner
|
|
154
259
|
};
|
|
155
260
|
}
|
|
@@ -174,15 +279,9 @@ function markAsDetected(value) {
|
|
|
174
279
|
}
|
|
175
280
|
return { ...value, _detected: true };
|
|
176
281
|
}
|
|
177
|
-
var CONVENTION_KEYS2 = [
|
|
178
|
-
"fileNaming",
|
|
179
|
-
"componentNaming",
|
|
180
|
-
"hookNaming",
|
|
181
|
-
"importAlias"
|
|
182
|
-
];
|
|
183
282
|
function mergeConventions(existing, fresh) {
|
|
184
283
|
const merged = { ...existing };
|
|
185
|
-
for (const key of
|
|
284
|
+
for (const key of CONVENTION_KEYS) {
|
|
186
285
|
if (!hasConvention(existing, key) && fresh[key] !== void 0) {
|
|
187
286
|
merged[key] = markAsDetected(fresh[key]);
|
|
188
287
|
}
|
|
@@ -210,8 +309,23 @@ function mergeConfig(existing, scanResult) {
|
|
|
210
309
|
} else if (fresh.boundaries) {
|
|
211
310
|
merged.boundaries = [...fresh.boundaries];
|
|
212
311
|
}
|
|
312
|
+
if (existing.packages || fresh.packages) {
|
|
313
|
+
merged.packages = mergePackageOverrides(existing.packages, fresh.packages);
|
|
314
|
+
}
|
|
213
315
|
return merged;
|
|
214
316
|
}
|
|
317
|
+
function mergePackageOverrides(existing, fresh) {
|
|
318
|
+
if (!fresh || fresh.length === 0) return existing;
|
|
319
|
+
if (!existing || existing.length === 0) return fresh;
|
|
320
|
+
const existingByPath = new Map(existing.map((p) => [p.path, p]));
|
|
321
|
+
const merged = [...existing];
|
|
322
|
+
for (const freshPkg of fresh) {
|
|
323
|
+
if (!existingByPath.has(freshPkg.path)) {
|
|
324
|
+
merged.push(freshPkg);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return merged.length > 0 ? merged : void 0;
|
|
328
|
+
}
|
|
215
329
|
|
|
216
330
|
// src/schema.ts
|
|
217
331
|
var configSchema = {
|
|
@@ -269,6 +383,10 @@ var configSchema = {
|
|
|
269
383
|
type: "string",
|
|
270
384
|
description: 'Linter (e.g. "eslint@9", "biome").'
|
|
271
385
|
},
|
|
386
|
+
formatter: {
|
|
387
|
+
type: "string",
|
|
388
|
+
description: 'Formatter (e.g. "prettier", "biome").'
|
|
389
|
+
},
|
|
272
390
|
testRunner: {
|
|
273
391
|
type: "string",
|
|
274
392
|
description: 'Test runner (e.g. "vitest", "jest").'
|
|
@@ -414,6 +532,46 @@ var configSchema = {
|
|
|
414
532
|
},
|
|
415
533
|
additionalProperties: false,
|
|
416
534
|
description: "Workspace configuration for monorepo projects."
|
|
535
|
+
},
|
|
536
|
+
packages: {
|
|
537
|
+
type: "array",
|
|
538
|
+
items: {
|
|
539
|
+
type: "object",
|
|
540
|
+
required: ["name", "path"],
|
|
541
|
+
properties: {
|
|
542
|
+
name: { type: "string", description: "Package name from package.json." },
|
|
543
|
+
path: { type: "string", description: "Relative path to the package." },
|
|
544
|
+
stack: {
|
|
545
|
+
type: "object",
|
|
546
|
+
properties: {
|
|
547
|
+
framework: { type: "string" },
|
|
548
|
+
language: { type: "string" },
|
|
549
|
+
styling: { type: "string" },
|
|
550
|
+
backend: { type: "string" },
|
|
551
|
+
packageManager: { type: "string" },
|
|
552
|
+
linter: { type: "string" },
|
|
553
|
+
formatter: { type: "string" },
|
|
554
|
+
testRunner: { type: "string" }
|
|
555
|
+
},
|
|
556
|
+
additionalProperties: false
|
|
557
|
+
},
|
|
558
|
+
conventions: { $ref: "#/properties/conventions" },
|
|
559
|
+
rules: {
|
|
560
|
+
type: "object",
|
|
561
|
+
properties: {
|
|
562
|
+
maxFileLines: { type: "number" },
|
|
563
|
+
maxFunctionLines: { type: "number" },
|
|
564
|
+
requireTests: { type: "boolean" },
|
|
565
|
+
enforceNaming: { type: "boolean" },
|
|
566
|
+
enforceBoundaries: { type: "boolean" }
|
|
567
|
+
},
|
|
568
|
+
additionalProperties: false
|
|
569
|
+
},
|
|
570
|
+
ignore: { type: "array", items: { type: "string" } }
|
|
571
|
+
},
|
|
572
|
+
additionalProperties: false
|
|
573
|
+
},
|
|
574
|
+
description: "Per-package overrides for monorepo projects."
|
|
417
575
|
}
|
|
418
576
|
},
|
|
419
577
|
additionalProperties: false,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/defaults.ts","../src/generate-config.ts","../src/load-config.ts","../src/merge-config.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import type { ConfigRules } from '@viberails/types';\n\n/**\n * Default rule thresholds and toggles for a new viberails config.\n * These values are intentionally conservative to build trust on first run.\n */\nexport const DEFAULT_RULES: ConfigRules = {\n maxFileLines: 300,\n maxFunctionLines: 50,\n requireTests: true,\n enforceNaming: true,\n enforceBoundaries: false,\n};\n\n/**\n * Default glob patterns for files and directories to ignore.\n */\nexport const DEFAULT_IGNORE: string[] = ['**/*.d.ts', 'dist/**', 'node_modules/**'];\n","import * as path from 'node:path';\nimport type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n DetectedConvention,\n DirectoryRole,\n ScanResult,\n StackItem,\n ViberailsConfig,\n} from '@viberails/types';\nimport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\n\n/**\n * Format a StackItem as a config string: `\"name@version\"` or `\"name\"`.\n */\nfunction formatStackItem(item: StackItem): string {\n return item.version ? `${item.name}@${item.version}` : item.name;\n}\n\n/**\n * Map DetectedStack → ConfigStack by formatting each StackItem.\n */\nfunction mapStack(scanResult: ScanResult): ConfigStack {\n const { stack } = scanResult;\n const config: ConfigStack = {\n language: formatStackItem(stack.language),\n packageManager: formatStackItem(stack.packageManager),\n };\n\n if (stack.framework) config.framework = formatStackItem(stack.framework);\n if (stack.styling) config.styling = formatStackItem(stack.styling);\n if (stack.backend) config.backend = formatStackItem(stack.backend);\n if (stack.linter) config.linter = formatStackItem(stack.linter);\n if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);\n\n return config;\n}\n\n/** Directory roles that map to ConfigStructure fields. */\nconst ROLE_TO_FIELD: Partial<Record<DirectoryRole, keyof ConfigStructure>> = {\n pages: 'pages',\n components: 'components',\n hooks: 'hooks',\n utils: 'utils',\n types: 'types',\n tests: 'tests',\n};\n\n/**\n * Map DetectedStructure → ConfigStructure by finding the first directory\n * for each known role.\n */\nfunction mapStructure(scanResult: ScanResult): ConfigStructure {\n const { structure } = scanResult;\n const config: ConfigStructure = {};\n\n if (structure.srcDir) {\n config.srcDir = structure.srcDir;\n }\n\n for (const dir of structure.directories) {\n const field = ROLE_TO_FIELD[dir.role];\n if (field && config[field] === undefined) {\n (config as Record<string, string>)[field] = dir.path;\n }\n }\n\n if (structure.testPattern) {\n config.testPattern = structure.testPattern.value;\n }\n\n return config;\n}\n\n/**\n * Convert a DetectedConvention to a ConventionValue with metadata.\n * Returns undefined for low-confidence conventions (they are omitted).\n */\nfunction mapConvention(convention: DetectedConvention): ConventionValue | undefined {\n if (convention.confidence === 'low') {\n return undefined;\n }\n\n return {\n value: convention.value,\n _confidence: convention.confidence,\n _consistency: convention.consistency,\n };\n}\n\n/** Convention keys from ScanResult that map to ConfigConventions fields. */\nconst CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Map scanner conventions → ConfigConventions, omitting low-confidence entries.\n */\nfunction mapConventions(scanResult: ScanResult): ConfigConventions {\n const config: ConfigConventions = {};\n\n for (const key of CONVENTION_KEYS) {\n const detected = scanResult.conventions[key];\n if (detected) {\n const value = mapConvention(detected);\n if (value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n return config;\n}\n\n/**\n * Generate a ViberailsConfig from scan results.\n *\n * Maps the scanner's DetectedStack, DetectedStructure, and conventions\n * into the config format with smart defaults. Low-confidence conventions\n * are omitted. The project name is derived from the root directory basename.\n *\n * @param scanResult - The output of scanning a project\n * @returns A complete ViberailsConfig ready to be written as JSON\n */\nexport function generateConfig(scanResult: ScanResult): ViberailsConfig {\n const config: ViberailsConfig = {\n $schema: 'https://viberails.sh/schema/v1.json',\n version: 1,\n name: path.basename(scanResult.root),\n enforcement: 'warn',\n stack: mapStack(scanResult),\n structure: mapStructure(scanResult),\n conventions: mapConventions(scanResult),\n rules: { ...DEFAULT_RULES },\n ignore: [...DEFAULT_IGNORE],\n };\n\n if (scanResult.workspace) {\n config.workspace = {\n packages: scanResult.workspace.packages.map((p) => p.relativePath),\n isMonorepo: true,\n };\n config.boundaries = [];\n }\n\n return config;\n}\n","import * as fs from 'node:fs/promises';\nimport type { ViberailsConfig } from '@viberails/types';\n\n/**\n * Validate that a parsed object has the required ViberailsConfig fields.\n * Throws a descriptive error if any required field is missing.\n */\nfunction validateConfig(parsed: Record<string, unknown>, configPath: string): void {\n const required = ['version', 'name', 'stack', 'rules'] as const;\n const missing = required.filter((field) => parsed[field] === undefined);\n\n if (missing.length > 0) {\n throw new Error(\n `Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(', ')}`,\n );\n }\n}\n\n/**\n * Load and parse a viberails config file.\n *\n * Reads the JSON file at the given path, validates that it contains\n * the required fields (version, name, stack, rules), and returns\n * the parsed config.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig\n * @throws If the file doesn't exist, contains invalid JSON, or is missing required fields\n */\nexport async function loadConfig(configPath: string): Promise<ViberailsConfig> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new Error(`Config file not found: ${configPath}. Run \"npx viberails\" to generate one.`);\n }\n throw new Error(`Failed to read config file at ${configPath}: ${(err as Error).message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in config file at ${configPath}. Check for syntax errors.`);\n }\n\n validateConfig(parsed, configPath);\n\n return parsed as unknown as ViberailsConfig;\n}\n\n/**\n * Safely load a viberails config file, returning null on any error.\n *\n * Used by the CLI for \"does config already exist?\" checks where\n * failure is an expected case, not an error.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig, or null if loading fails\n */\nexport async function loadConfigSafe(configPath: string): Promise<ViberailsConfig | null> {\n try {\n return await loadConfig(configPath);\n } catch {\n return null;\n }\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { generateConfig } from './generate-config.js';\n\n/**\n * Merge stack: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStack(existing: ConfigStack, fresh: ConfigStack): ConfigStack {\n return {\n language: existing.language,\n packageManager: existing.packageManager,\n framework: existing.framework ?? fresh.framework,\n styling: existing.styling ?? fresh.styling,\n backend: existing.backend ?? fresh.backend,\n linter: existing.linter ?? fresh.linter,\n testRunner: existing.testRunner ?? fresh.testRunner,\n };\n}\n\n/**\n * Merge structure: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStructure(existing: ConfigStructure, fresh: ConfigStructure): ConfigStructure {\n return {\n srcDir: existing.srcDir ?? fresh.srcDir,\n pages: existing.pages ?? fresh.pages,\n components: existing.components ?? fresh.components,\n hooks: existing.hooks ?? fresh.hooks,\n utils: existing.utils ?? fresh.utils,\n types: existing.types ?? fresh.types,\n tests: existing.tests ?? fresh.tests,\n testPattern: existing.testPattern ?? fresh.testPattern,\n };\n}\n\n/**\n * Check if a convention key exists in the existing config\n * (either as a string or as an object with a value).\n */\nfunction hasConvention(conventions: ConfigConventions, key: keyof ConfigConventions): boolean {\n return conventions[key] !== undefined;\n}\n\n/**\n * Mark a ConventionValue as newly detected by adding `_detected: true`.\n * Only applies to object-form values (not plain strings).\n */\nfunction markAsDetected(value: ConventionValue): ConventionValue {\n if (typeof value === 'string') {\n return { value, _confidence: 'high', _consistency: 100, _detected: true };\n }\n return { ...value, _detected: true };\n}\n\n/** Convention keys to iterate during merge. */\nconst CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Merge conventions: keep all existing values, add new detections with `_detected: true`.\n */\nfunction mergeConventions(\n existing: ConfigConventions,\n fresh: ConfigConventions,\n): ConfigConventions {\n const merged: ConfigConventions = { ...existing };\n\n for (const key of CONVENTION_KEYS) {\n if (!hasConvention(existing, key) && fresh[key] !== undefined) {\n merged[key] = markAsDetected(fresh[key]!);\n }\n }\n\n return merged;\n}\n\n/**\n * Merge a new scan result into an existing config for `viberails sync`.\n *\n * Preserves all developer-confirmed values from the existing config.\n * Adds newly detected conventions with a `_detected: true` annotation\n * so the developer can review them. Never removes rules or values\n * the developer has set.\n *\n * @param existing - The current ViberailsConfig (from viberails.config.json)\n * @param scanResult - Fresh scan results from re-scanning the project\n * @returns A merged config that preserves existing values and adds new detections\n */\nexport function mergeConfig(existing: ViberailsConfig, scanResult: ScanResult): ViberailsConfig {\n const fresh = generateConfig(scanResult);\n\n const merged: ViberailsConfig = {\n $schema: existing.$schema ?? fresh.$schema,\n version: existing.version,\n name: existing.name,\n enforcement: existing.enforcement,\n stack: mergeStack(existing.stack, fresh.stack),\n structure: mergeStructure(existing.structure, fresh.structure),\n conventions: mergeConventions(existing.conventions, fresh.conventions),\n rules: { ...existing.rules },\n ignore: [...existing.ignore],\n };\n\n // Workspace: always take fresh scan (structure can change)\n if (fresh.workspace) {\n merged.workspace = fresh.workspace;\n }\n\n // Boundaries: preserve existing rules (user may have adjusted)\n if (existing.boundaries) {\n merged.boundaries = [...existing.boundaries];\n } else if (fresh.boundaries) {\n merged.boundaries = [...fresh.boundaries];\n }\n\n return merged;\n}\n","/**\n * JSON Schema (draft-07) definition for viberails.config.json.\n *\n * This schema will eventually be hosted at https://viberails.sh/schema/v1.json.\n * For now it is exported as a TypeScript object that can be serialized to JSON.\n */\nexport const configSchema = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://viberails.sh/schema/v1.json',\n title: 'viberails configuration',\n description: 'Configuration file for viberails — guardrails for vibe coding.',\n type: 'object',\n required: ['version', 'name', 'stack', 'rules'],\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema URL for editor validation.',\n },\n version: {\n type: 'number',\n const: 1,\n description: 'Config format version. Always 1 for V1.0.',\n },\n name: {\n type: 'string',\n description: 'Project name, typically from package.json.',\n },\n enforcement: {\n type: 'string',\n enum: ['warn', 'enforce'],\n default: 'warn',\n description: 'Whether conventions are warned about or enforced as errors.',\n },\n stack: {\n type: 'object',\n required: ['language', 'packageManager'],\n properties: {\n framework: {\n type: 'string',\n description: 'Primary framework identifier (e.g. \"nextjs@15\", \"remix@2\").',\n },\n language: {\n type: 'string',\n description: 'Primary language (e.g. \"typescript\", \"javascript\").',\n },\n styling: {\n type: 'string',\n description: 'Styling solution (e.g. \"tailwindcss@4\", \"css-modules\").',\n },\n backend: {\n type: 'string',\n description: 'Backend framework (e.g. \"express@5\", \"fastify\").',\n },\n packageManager: {\n type: 'string',\n description: 'Package manager (e.g. \"pnpm\", \"npm\", \"yarn\").',\n },\n linter: {\n type: 'string',\n description: 'Linter (e.g. \"eslint@9\", \"biome\").',\n },\n testRunner: {\n type: 'string',\n description: 'Test runner (e.g. \"vitest\", \"jest\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured technology stack.',\n },\n structure: {\n type: 'object',\n properties: {\n srcDir: {\n type: 'string',\n description: 'Source directory (e.g. \"src\"), or omit for flat structure.',\n },\n pages: {\n type: 'string',\n description: 'Pages or routes directory (e.g. \"src/app\").',\n },\n components: {\n type: 'string',\n description: 'Components directory (e.g. \"src/components\").',\n },\n hooks: {\n type: 'string',\n description: 'Hooks directory (e.g. \"src/hooks\").',\n },\n utils: {\n type: 'string',\n description: 'Utilities directory (e.g. \"src/utils\", \"src/lib\").',\n },\n types: {\n type: 'string',\n description: 'Type definitions directory (e.g. \"src/types\").',\n },\n tests: {\n type: 'string',\n description: 'Tests directory (e.g. \"tests\", \"__tests__\").',\n },\n testPattern: {\n type: 'string',\n description: 'Test file naming pattern (e.g. \"*.test.ts\", \"*.spec.ts\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured directory structure.',\n },\n conventions: {\n type: 'object',\n properties: {\n fileNaming: { $ref: '#/definitions/conventionValue' },\n componentNaming: { $ref: '#/definitions/conventionValue' },\n hookNaming: { $ref: '#/definitions/conventionValue' },\n importAlias: { $ref: '#/definitions/conventionValue' },\n },\n additionalProperties: false,\n description: 'Detected or configured coding conventions.',\n },\n rules: {\n type: 'object',\n required: [\n 'maxFileLines',\n 'maxFunctionLines',\n 'requireTests',\n 'enforceNaming',\n 'enforceBoundaries',\n ],\n properties: {\n maxFileLines: {\n type: 'number',\n default: 300,\n description: 'Maximum number of lines allowed per file.',\n },\n maxFunctionLines: {\n type: 'number',\n default: 50,\n description: 'Maximum number of lines allowed per function.',\n },\n requireTests: {\n type: 'boolean',\n default: true,\n description: 'Whether to require test files for source modules.',\n },\n enforceNaming: {\n type: 'boolean',\n default: true,\n description: 'Whether to enforce detected file naming conventions.',\n },\n enforceBoundaries: {\n type: 'boolean',\n default: false,\n description: 'Whether to enforce module boundary rules.',\n },\n },\n additionalProperties: false,\n description: 'Rule thresholds and toggles for enforcement.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Glob patterns for files and directories to ignore.',\n },\n boundaries: {\n type: 'array',\n items: {\n type: 'object',\n required: ['from', 'to', 'allow'],\n properties: {\n from: {\n type: 'string',\n description: 'Source package or directory pattern.',\n },\n to: {\n type: 'string',\n description: 'Target package or directory pattern.',\n },\n allow: {\n type: 'boolean',\n description: 'Whether this import direction is allowed or disallowed.',\n },\n reason: {\n type: 'string',\n description: 'Human-readable explanation of why this boundary exists.',\n },\n },\n additionalProperties: false,\n },\n description: 'Module boundary rules for import enforcement.',\n },\n workspace: {\n type: 'object',\n required: ['packages', 'isMonorepo'],\n properties: {\n packages: {\n type: 'array',\n items: { type: 'string' },\n description: 'Relative paths to workspace packages.',\n },\n isMonorepo: {\n type: 'boolean',\n description: 'Whether this project is a monorepo with multiple packages.',\n },\n },\n additionalProperties: false,\n description: 'Workspace configuration for monorepo projects.',\n },\n },\n additionalProperties: false,\n definitions: {\n conventionValue: {\n description:\n 'A convention value — either a plain string (user-confirmed) or an object with scanner metadata.',\n oneOf: [\n { type: 'string' },\n {\n type: 'object',\n required: ['value', '_confidence', '_consistency'],\n properties: {\n value: {\n type: 'string',\n description: 'The convention value.',\n },\n _confidence: {\n type: 'string',\n enum: ['high', 'medium', 'low'],\n description: 'Scanner confidence level.',\n },\n _consistency: {\n type: 'number',\n minimum: 0,\n maximum: 100,\n description: 'Scanner consistency percentage.',\n },\n _detected: {\n type: 'boolean',\n description: 'Set by mergeConfig when a convention is newly detected during sync.',\n },\n },\n additionalProperties: false,\n },\n ],\n },\n },\n} as const;\n","export const VERSION = '0.1.0';\n\nexport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nexport { generateConfig } from './generate-config.js';\nexport { loadConfig, loadConfigSafe } from './load-config.js';\nexport { mergeConfig } from './merge-config.js';\nexport { configSchema } from './schema.js';\n"],"mappings":"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","names":["CONVENTION_KEYS"]}
|
|
1
|
+
{"version":3,"sources":["../src/defaults.ts","../src/generate-config.ts","../src/generate-overrides.ts","../src/load-config.ts","../src/merge-config.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import type { ConfigRules } from '@viberails/types';\n\n/**\n * Default rule thresholds and toggles for a new viberails config.\n * These values are intentionally conservative to build trust on first run.\n */\nexport const DEFAULT_RULES: ConfigRules = {\n maxFileLines: 300,\n maxFunctionLines: 50,\n requireTests: true,\n enforceNaming: true,\n enforceBoundaries: false,\n};\n\n/**\n * Default glob patterns for files and directories to ignore.\n */\nexport const DEFAULT_IGNORE: string[] = ['**/*.d.ts', 'dist/**', 'node_modules/**'];\n","import * as path from 'node:path';\nimport type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n DetectedConvention,\n DirectoryRole,\n ScanResult,\n StackItem,\n ViberailsConfig,\n} from '@viberails/types';\nimport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nimport { generatePackageOverrides } from './generate-overrides.js';\n\n/**\n * Format a StackItem as a config string: `\"name@version\"` or `\"name\"`.\n */\nexport function formatStackItem(item: StackItem): string {\n return item.version ? `${item.name}@${item.version}` : item.name;\n}\n\n/**\n * Map DetectedStack → ConfigStack by formatting each StackItem.\n */\nfunction mapStack(scanResult: ScanResult): ConfigStack {\n const { stack } = scanResult;\n const config: ConfigStack = {\n language: formatStackItem(stack.language),\n packageManager: formatStackItem(stack.packageManager),\n };\n\n if (stack.framework) config.framework = formatStackItem(stack.framework);\n if (stack.styling) config.styling = formatStackItem(stack.styling);\n if (stack.backend) config.backend = formatStackItem(stack.backend);\n if (stack.linter) config.linter = formatStackItem(stack.linter);\n if (stack.formatter) config.formatter = formatStackItem(stack.formatter);\n if (stack.testRunner) config.testRunner = formatStackItem(stack.testRunner);\n\n return config;\n}\n\n/** Directory roles that map to ConfigStructure fields. */\nconst ROLE_TO_FIELD: Partial<Record<DirectoryRole, keyof ConfigStructure>> = {\n pages: 'pages',\n components: 'components',\n hooks: 'hooks',\n utils: 'utils',\n types: 'types',\n tests: 'tests',\n};\n\n/**\n * Map DetectedStructure → ConfigStructure by finding the first directory\n * for each known role.\n */\nfunction mapStructure(scanResult: ScanResult): ConfigStructure {\n const { structure } = scanResult;\n const config: ConfigStructure = {};\n\n if (structure.srcDir) {\n config.srcDir = structure.srcDir;\n }\n\n for (const dir of structure.directories) {\n const field = ROLE_TO_FIELD[dir.role];\n if (field && config[field] === undefined) {\n (config as Record<string, string>)[field] = dir.path;\n }\n }\n\n if (structure.testPattern) {\n config.testPattern = structure.testPattern.value;\n }\n\n return config;\n}\n\n/**\n * Convert a DetectedConvention to a ConventionValue with metadata.\n * Returns undefined for low-confidence conventions (they are omitted).\n */\nexport function mapConvention(convention: DetectedConvention): ConventionValue | undefined {\n if (convention.confidence === 'low') {\n return undefined;\n }\n\n return {\n value: convention.value,\n _confidence: convention.confidence,\n _consistency: convention.consistency,\n };\n}\n\n/** Convention keys from ScanResult that map to ConfigConventions fields. */\nexport const CONVENTION_KEYS: (keyof ConfigConventions)[] = [\n 'fileNaming',\n 'componentNaming',\n 'hookNaming',\n 'importAlias',\n];\n\n/**\n * Map scanner conventions → ConfigConventions, omitting low-confidence entries.\n */\nfunction mapConventions(scanResult: ScanResult): ConfigConventions {\n const config: ConfigConventions = {};\n\n for (const key of CONVENTION_KEYS) {\n const detected = scanResult.conventions[key];\n if (detected) {\n const value = mapConvention(detected);\n if (value !== undefined) {\n config[key] = value;\n }\n }\n }\n\n return config;\n}\n\n/**\n * Generate a ViberailsConfig from scan results.\n *\n * Maps the scanner's DetectedStack, DetectedStructure, and conventions\n * into the config format with smart defaults. Low-confidence conventions\n * are omitted. The project name is derived from the root directory basename.\n *\n * @param scanResult - The output of scanning a project\n * @returns A complete ViberailsConfig ready to be written as JSON\n */\nexport function generateConfig(scanResult: ScanResult): ViberailsConfig {\n const config: ViberailsConfig = {\n $schema: 'https://viberails.sh/schema/v1.json',\n version: 1,\n name: path.basename(scanResult.root),\n enforcement: 'warn',\n stack: mapStack(scanResult),\n structure: mapStructure(scanResult),\n conventions: mapConventions(scanResult),\n rules: { ...DEFAULT_RULES },\n ignore: [...DEFAULT_IGNORE],\n };\n\n if (scanResult.workspace) {\n config.workspace = {\n packages: scanResult.workspace.packages.map((p) => p.relativePath),\n isMonorepo: true,\n };\n config.boundaries = [];\n }\n\n const packageOverrides = generatePackageOverrides(scanResult, config);\n if (packageOverrides) {\n config.packages = packageOverrides;\n }\n\n return config;\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n DetectedConvention,\n PackageConfigOverrides,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { CONVENTION_KEYS, formatStackItem, mapConvention } from './generate-config.js';\n\n/**\n * Compare a package's conventions against the global config conventions.\n * Returns only the differing conventions, or undefined if all match.\n */\nexport function conventionsDiffer(\n pkgConventions: Record<string, DetectedConvention>,\n globalConventions: ConfigConventions,\n): Partial<ConfigConventions> | undefined {\n const overrides: Partial<ConfigConventions> = {};\n let hasDiff = false;\n\n for (const key of CONVENTION_KEYS) {\n const detected = pkgConventions[key];\n if (!detected) continue;\n\n const mapped = mapConvention(detected);\n if (mapped === undefined) continue;\n\n const globalValue = globalConventions[key];\n const globalStr = typeof globalValue === 'string' ? globalValue : globalValue?.value;\n if (detected.value !== globalStr) {\n overrides[key] = mapped;\n hasDiff = true;\n }\n }\n\n return hasDiff ? overrides : undefined;\n}\n\n/**\n * Generate per-package config overrides for packages whose stack or\n * conventions differ from the aggregate global config.\n */\nexport function generatePackageOverrides(\n scanResult: ScanResult,\n globalConfig: ViberailsConfig,\n): PackageConfigOverrides[] | undefined {\n if (!scanResult.packages || scanResult.packages.length <= 1) return undefined;\n\n const overrides: PackageConfigOverrides[] = [];\n\n for (const pkg of scanResult.packages) {\n const override: PackageConfigOverrides = {\n name: pkg.name,\n path: pkg.relativePath,\n };\n let hasDiff = false;\n\n // Compare stack fields — only include overrides when the package has a\n // value that differs from the global, not when the package simply lacks the field\n const stackOverride: Partial<ConfigStack> = {};\n let hasStackDiff = false;\n\n const optionalStackFields = [\n 'framework',\n 'styling',\n 'backend',\n 'linter',\n 'formatter',\n 'testRunner',\n ] as const;\n for (const field of optionalStackFields) {\n const pkgItem = pkg.stack[field];\n if (!pkgItem) continue;\n const pkgValue = formatStackItem(pkgItem);\n if (pkgValue !== globalConfig.stack[field]) {\n stackOverride[field] = pkgValue;\n hasStackDiff = true;\n }\n }\n\n if (hasStackDiff) {\n override.stack = stackOverride;\n hasDiff = true;\n }\n\n // Compare conventions\n const conventionOverrides = conventionsDiffer(pkg.conventions, globalConfig.conventions);\n if (conventionOverrides) {\n override.conventions = conventionOverrides;\n hasDiff = true;\n }\n\n if (hasDiff) {\n overrides.push(override);\n }\n }\n\n return overrides.length > 0 ? overrides : undefined;\n}\n","import * as fs from 'node:fs/promises';\nimport type { ViberailsConfig } from '@viberails/types';\n\n/**\n * Validate that a parsed object has the required ViberailsConfig fields\n * and that their types are correct.\n * Throws a descriptive error if validation fails.\n */\nfunction validateConfig(parsed: Record<string, unknown>, configPath: string): void {\n const errors: string[] = [];\n\n // Required top-level fields\n const required = ['version', 'name', 'stack', 'rules'] as const;\n const missing = required.filter((field) => parsed[field] === undefined);\n if (missing.length > 0) {\n throw new Error(\n `Invalid viberails config at ${configPath}: missing required field(s): ${missing.join(', ')}`,\n );\n }\n\n // Type checks\n if (typeof parsed.version !== 'number') errors.push('\"version\" must be a number');\n if (typeof parsed.name !== 'string') errors.push('\"name\" must be a string');\n if (\n parsed.enforcement !== undefined &&\n parsed.enforcement !== 'warn' &&\n parsed.enforcement !== 'enforce'\n ) {\n errors.push('\"enforcement\" must be \"warn\" or \"enforce\"');\n }\n\n // Stack validation\n if (typeof parsed.stack !== 'object' || parsed.stack === null) {\n errors.push('\"stack\" must be an object');\n } else {\n const stack = parsed.stack as Record<string, unknown>;\n if (typeof stack.language !== 'string') errors.push('\"stack.language\" must be a string');\n if (typeof stack.packageManager !== 'string')\n errors.push('\"stack.packageManager\" must be a string');\n }\n\n // Rules validation\n if (typeof parsed.rules !== 'object' || parsed.rules === null) {\n errors.push('\"rules\" must be an object');\n } else {\n const rules = parsed.rules as Record<string, unknown>;\n if (typeof rules.maxFileLines !== 'number')\n errors.push('\"rules.maxFileLines\" must be a number');\n if (typeof rules.maxFunctionLines !== 'number')\n errors.push('\"rules.maxFunctionLines\" must be a number');\n if (typeof rules.requireTests !== 'boolean')\n errors.push('\"rules.requireTests\" must be a boolean');\n if (typeof rules.enforceNaming !== 'boolean')\n errors.push('\"rules.enforceNaming\" must be a boolean');\n if (typeof rules.enforceBoundaries !== 'boolean')\n errors.push('\"rules.enforceBoundaries\" must be a boolean');\n }\n\n // Ignore validation\n if (parsed.ignore !== undefined && !Array.isArray(parsed.ignore)) {\n errors.push('\"ignore\" must be an array');\n }\n\n if (errors.length > 0) {\n throw new Error(`Invalid viberails config at ${configPath}: ${errors.join('; ')}`);\n }\n}\n\n/**\n * Load and parse a viberails config file.\n *\n * Reads the JSON file at the given path, validates that it contains\n * the required fields (version, name, stack, rules), and returns\n * the parsed config.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig\n * @throws If the file doesn't exist, contains invalid JSON, or is missing required fields\n */\nexport async function loadConfig(configPath: string): Promise<ViberailsConfig> {\n let raw: string;\n try {\n raw = await fs.readFile(configPath, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') {\n throw new Error(`Config file not found: ${configPath}. Run \"npx viberails\" to generate one.`);\n }\n throw new Error(`Failed to read config file at ${configPath}: ${(err as Error).message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n throw new Error(`Invalid JSON in config file at ${configPath}. Check for syntax errors.`);\n }\n\n validateConfig(parsed, configPath);\n\n // Safe to cast: validateConfig has verified all required fields and types\n return parsed as unknown as ViberailsConfig;\n}\n\n/**\n * Safely load a viberails config file, returning null on any error.\n *\n * Used by the CLI for \"does config already exist?\" checks where\n * failure is an expected case, not an error.\n *\n * @param configPath - Absolute or relative path to viberails.config.json\n * @returns The parsed ViberailsConfig, or null if loading fails\n */\nexport async function loadConfigSafe(configPath: string): Promise<ViberailsConfig | null> {\n try {\n return await loadConfig(configPath);\n } catch {\n return null;\n }\n}\n","import type {\n ConfigConventions,\n ConfigStack,\n ConfigStructure,\n ConventionValue,\n PackageConfigOverrides,\n ScanResult,\n ViberailsConfig,\n} from '@viberails/types';\nimport { CONVENTION_KEYS, generateConfig } from './generate-config.js';\n\n/**\n * Merge stack: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStack(existing: ConfigStack, fresh: ConfigStack): ConfigStack {\n return {\n language: existing.language,\n packageManager: existing.packageManager,\n framework: existing.framework ?? fresh.framework,\n styling: existing.styling ?? fresh.styling,\n backend: existing.backend ?? fresh.backend,\n linter: existing.linter ?? fresh.linter,\n formatter: existing.formatter ?? fresh.formatter,\n testRunner: existing.testRunner ?? fresh.testRunner,\n };\n}\n\n/**\n * Merge structure: keep existing values, fill in undefined fields from fresh scan.\n */\nfunction mergeStructure(existing: ConfigStructure, fresh: ConfigStructure): ConfigStructure {\n return {\n srcDir: existing.srcDir ?? fresh.srcDir,\n pages: existing.pages ?? fresh.pages,\n components: existing.components ?? fresh.components,\n hooks: existing.hooks ?? fresh.hooks,\n utils: existing.utils ?? fresh.utils,\n types: existing.types ?? fresh.types,\n tests: existing.tests ?? fresh.tests,\n testPattern: existing.testPattern ?? fresh.testPattern,\n };\n}\n\n/**\n * Check if a convention key exists in the existing config\n * (either as a string or as an object with a value).\n */\nfunction hasConvention(conventions: ConfigConventions, key: keyof ConfigConventions): boolean {\n return conventions[key] !== undefined;\n}\n\n/**\n * Mark a ConventionValue as newly detected by adding `_detected: true`.\n * Only applies to object-form values (not plain strings).\n */\nfunction markAsDetected(value: ConventionValue): ConventionValue {\n if (typeof value === 'string') {\n return { value, _confidence: 'high', _consistency: 100, _detected: true };\n }\n return { ...value, _detected: true };\n}\n\n/**\n * Merge conventions: keep all existing values, add new detections with `_detected: true`.\n */\nfunction mergeConventions(\n existing: ConfigConventions,\n fresh: ConfigConventions,\n): ConfigConventions {\n const merged: ConfigConventions = { ...existing };\n\n for (const key of CONVENTION_KEYS) {\n if (!hasConvention(existing, key) && fresh[key] !== undefined) {\n merged[key] = markAsDetected(fresh[key]!);\n }\n }\n\n return merged;\n}\n\n/**\n * Merge a new scan result into an existing config for `viberails sync`.\n *\n * Preserves all developer-confirmed values from the existing config.\n * Adds newly detected conventions with a `_detected: true` annotation\n * so the developer can review them. Never removes rules or values\n * the developer has set.\n *\n * @param existing - The current ViberailsConfig (from viberails.config.json)\n * @param scanResult - Fresh scan results from re-scanning the project\n * @returns A merged config that preserves existing values and adds new detections\n */\nexport function mergeConfig(existing: ViberailsConfig, scanResult: ScanResult): ViberailsConfig {\n const fresh = generateConfig(scanResult);\n\n const merged: ViberailsConfig = {\n $schema: existing.$schema ?? fresh.$schema,\n version: existing.version,\n name: existing.name,\n enforcement: existing.enforcement,\n stack: mergeStack(existing.stack, fresh.stack),\n structure: mergeStructure(existing.structure, fresh.structure),\n conventions: mergeConventions(existing.conventions, fresh.conventions),\n rules: { ...existing.rules },\n ignore: [...existing.ignore],\n };\n\n // Workspace: always take fresh scan (structure can change)\n if (fresh.workspace) {\n merged.workspace = fresh.workspace;\n }\n\n // Boundaries: preserve existing rules (user may have adjusted)\n if (existing.boundaries) {\n merged.boundaries = [...existing.boundaries];\n } else if (fresh.boundaries) {\n merged.boundaries = [...fresh.boundaries];\n }\n\n // Packages: preserve existing overrides, add new ones\n if (existing.packages || fresh.packages) {\n merged.packages = mergePackageOverrides(existing.packages, fresh.packages);\n }\n\n return merged;\n}\n\n/**\n * Merge per-package overrides: keep existing user-edited overrides,\n * add new packages from fresh scan.\n */\nfunction mergePackageOverrides(\n existing?: PackageConfigOverrides[],\n fresh?: PackageConfigOverrides[],\n): PackageConfigOverrides[] | undefined {\n if (!fresh || fresh.length === 0) return existing;\n if (!existing || existing.length === 0) return fresh;\n\n const existingByPath = new Map(existing.map((p) => [p.path, p]));\n const merged: PackageConfigOverrides[] = [...existing];\n\n for (const freshPkg of fresh) {\n if (!existingByPath.has(freshPkg.path)) {\n merged.push(freshPkg);\n }\n }\n\n return merged.length > 0 ? merged : undefined;\n}\n","/**\n * JSON Schema (draft-07) definition for viberails.config.json.\n *\n * This schema will eventually be hosted at https://viberails.sh/schema/v1.json.\n * For now it is exported as a TypeScript object that can be serialized to JSON.\n */\nexport const configSchema = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n $id: 'https://viberails.sh/schema/v1.json',\n title: 'viberails configuration',\n description: 'Configuration file for viberails — guardrails for vibe coding.',\n type: 'object',\n required: ['version', 'name', 'stack', 'rules'],\n properties: {\n $schema: {\n type: 'string',\n description: 'JSON Schema URL for editor validation.',\n },\n version: {\n type: 'number',\n const: 1,\n description: 'Config format version. Always 1 for V1.0.',\n },\n name: {\n type: 'string',\n description: 'Project name, typically from package.json.',\n },\n enforcement: {\n type: 'string',\n enum: ['warn', 'enforce'],\n default: 'warn',\n description: 'Whether conventions are warned about or enforced as errors.',\n },\n stack: {\n type: 'object',\n required: ['language', 'packageManager'],\n properties: {\n framework: {\n type: 'string',\n description: 'Primary framework identifier (e.g. \"nextjs@15\", \"remix@2\").',\n },\n language: {\n type: 'string',\n description: 'Primary language (e.g. \"typescript\", \"javascript\").',\n },\n styling: {\n type: 'string',\n description: 'Styling solution (e.g. \"tailwindcss@4\", \"css-modules\").',\n },\n backend: {\n type: 'string',\n description: 'Backend framework (e.g. \"express@5\", \"fastify\").',\n },\n packageManager: {\n type: 'string',\n description: 'Package manager (e.g. \"pnpm\", \"npm\", \"yarn\").',\n },\n linter: {\n type: 'string',\n description: 'Linter (e.g. \"eslint@9\", \"biome\").',\n },\n formatter: {\n type: 'string',\n description: 'Formatter (e.g. \"prettier\", \"biome\").',\n },\n testRunner: {\n type: 'string',\n description: 'Test runner (e.g. \"vitest\", \"jest\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured technology stack.',\n },\n structure: {\n type: 'object',\n properties: {\n srcDir: {\n type: 'string',\n description: 'Source directory (e.g. \"src\"), or omit for flat structure.',\n },\n pages: {\n type: 'string',\n description: 'Pages or routes directory (e.g. \"src/app\").',\n },\n components: {\n type: 'string',\n description: 'Components directory (e.g. \"src/components\").',\n },\n hooks: {\n type: 'string',\n description: 'Hooks directory (e.g. \"src/hooks\").',\n },\n utils: {\n type: 'string',\n description: 'Utilities directory (e.g. \"src/utils\", \"src/lib\").',\n },\n types: {\n type: 'string',\n description: 'Type definitions directory (e.g. \"src/types\").',\n },\n tests: {\n type: 'string',\n description: 'Tests directory (e.g. \"tests\", \"__tests__\").',\n },\n testPattern: {\n type: 'string',\n description: 'Test file naming pattern (e.g. \"*.test.ts\", \"*.spec.ts\").',\n },\n },\n additionalProperties: false,\n description: 'Detected or configured directory structure.',\n },\n conventions: {\n type: 'object',\n properties: {\n fileNaming: { $ref: '#/definitions/conventionValue' },\n componentNaming: { $ref: '#/definitions/conventionValue' },\n hookNaming: { $ref: '#/definitions/conventionValue' },\n importAlias: { $ref: '#/definitions/conventionValue' },\n },\n additionalProperties: false,\n description: 'Detected or configured coding conventions.',\n },\n rules: {\n type: 'object',\n required: [\n 'maxFileLines',\n 'maxFunctionLines',\n 'requireTests',\n 'enforceNaming',\n 'enforceBoundaries',\n ],\n properties: {\n maxFileLines: {\n type: 'number',\n default: 300,\n description: 'Maximum number of lines allowed per file.',\n },\n maxFunctionLines: {\n type: 'number',\n default: 50,\n description: 'Maximum number of lines allowed per function.',\n },\n requireTests: {\n type: 'boolean',\n default: true,\n description: 'Whether to require test files for source modules.',\n },\n enforceNaming: {\n type: 'boolean',\n default: true,\n description: 'Whether to enforce detected file naming conventions.',\n },\n enforceBoundaries: {\n type: 'boolean',\n default: false,\n description: 'Whether to enforce module boundary rules.',\n },\n },\n additionalProperties: false,\n description: 'Rule thresholds and toggles for enforcement.',\n },\n ignore: {\n type: 'array',\n items: { type: 'string' },\n description: 'Glob patterns for files and directories to ignore.',\n },\n boundaries: {\n type: 'array',\n items: {\n type: 'object',\n required: ['from', 'to', 'allow'],\n properties: {\n from: {\n type: 'string',\n description: 'Source package or directory pattern.',\n },\n to: {\n type: 'string',\n description: 'Target package or directory pattern.',\n },\n allow: {\n type: 'boolean',\n description: 'Whether this import direction is allowed or disallowed.',\n },\n reason: {\n type: 'string',\n description: 'Human-readable explanation of why this boundary exists.',\n },\n },\n additionalProperties: false,\n },\n description: 'Module boundary rules for import enforcement.',\n },\n workspace: {\n type: 'object',\n required: ['packages', 'isMonorepo'],\n properties: {\n packages: {\n type: 'array',\n items: { type: 'string' },\n description: 'Relative paths to workspace packages.',\n },\n isMonorepo: {\n type: 'boolean',\n description: 'Whether this project is a monorepo with multiple packages.',\n },\n },\n additionalProperties: false,\n description: 'Workspace configuration for monorepo projects.',\n },\n packages: {\n type: 'array',\n items: {\n type: 'object',\n required: ['name', 'path'],\n properties: {\n name: { type: 'string', description: 'Package name from package.json.' },\n path: { type: 'string', description: 'Relative path to the package.' },\n stack: {\n type: 'object',\n properties: {\n framework: { type: 'string' },\n language: { type: 'string' },\n styling: { type: 'string' },\n backend: { type: 'string' },\n packageManager: { type: 'string' },\n linter: { type: 'string' },\n formatter: { type: 'string' },\n testRunner: { type: 'string' },\n },\n additionalProperties: false,\n },\n conventions: { $ref: '#/properties/conventions' },\n rules: {\n type: 'object',\n properties: {\n maxFileLines: { type: 'number' },\n maxFunctionLines: { type: 'number' },\n requireTests: { type: 'boolean' },\n enforceNaming: { type: 'boolean' },\n enforceBoundaries: { type: 'boolean' },\n },\n additionalProperties: false,\n },\n ignore: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n description: 'Per-package overrides for monorepo projects.',\n },\n },\n additionalProperties: false,\n definitions: {\n conventionValue: {\n description:\n 'A convention value — either a plain string (user-confirmed) or an object with scanner metadata.',\n oneOf: [\n { type: 'string' },\n {\n type: 'object',\n required: ['value', '_confidence', '_consistency'],\n properties: {\n value: {\n type: 'string',\n description: 'The convention value.',\n },\n _confidence: {\n type: 'string',\n enum: ['high', 'medium', 'low'],\n description: 'Scanner confidence level.',\n },\n _consistency: {\n type: 'number',\n minimum: 0,\n maximum: 100,\n description: 'Scanner consistency percentage.',\n },\n _detected: {\n type: 'boolean',\n description: 'Set by mergeConfig when a convention is newly detected during sync.',\n },\n },\n additionalProperties: false,\n },\n ],\n },\n },\n} as const;\n","export const VERSION = '0.1.0';\n\nexport { DEFAULT_IGNORE, DEFAULT_RULES } from './defaults.js';\nexport { generateConfig } from './generate-config.js';\nexport { loadConfig, loadConfigSafe } from './load-config.js';\nexport { mergeConfig } from './merge-config.js';\nexport { configSchema } from './schema.js';\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viberails/config",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Config generation and loading for viberails",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
28
|
+
"ajv": "^8.18.0",
|
|
29
|
+
"@viberails/types": "0.2.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@types/node": "^25.3.5"
|
|
32
|
-
"ajv": "^8.18.0"
|
|
32
|
+
"@types/node": "^25.3.5"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsup",
|