@titan-design/style-profile 0.0.0

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.js ADDED
@@ -0,0 +1,974 @@
1
+ // src/schema/style-rule.ts
2
+ import { z } from "zod";
3
+ var ExampleSchema = z.object({
4
+ good: z.string().optional(),
5
+ bad: z.string().optional(),
6
+ source: z.string().optional()
7
+ });
8
+ var StabilitySchema = z.enum(["high", "medium", "low"]);
9
+ var FixabilitySchema = z.enum([
10
+ "safe",
11
+ "maybe-incorrect",
12
+ "requires-input",
13
+ "not-fixable"
14
+ ]);
15
+ var StyleRuleSchema = z.object({
16
+ convention: z.union([
17
+ z.string(),
18
+ z.number(),
19
+ z.boolean(),
20
+ z.array(z.string())
21
+ ]),
22
+ confidence: z.number().min(0).max(1),
23
+ stability: StabilitySchema.optional(),
24
+ fixability: FixabilitySchema.optional(),
25
+ description: z.string().optional(),
26
+ examples: z.array(ExampleSchema).optional(),
27
+ extensions: z.record(z.string(), z.unknown()).optional()
28
+ });
29
+
30
+ // src/schema/profile.ts
31
+ import { z as z2 } from "zod";
32
+ var SCHEMA_VERSION = "1.0.0";
33
+ var CategorySchema = z2.record(z2.string(), StyleRuleSchema);
34
+ var IdiomSchema = z2.object({
35
+ name: z2.string(),
36
+ description: z2.string(),
37
+ frequency: z2.number(),
38
+ confidence: z2.number().min(0).max(1),
39
+ example: z2.string().optional()
40
+ });
41
+ var AntiPatternSchema = z2.object({
42
+ pattern: z2.string(),
43
+ reason: z2.string(),
44
+ deprecated: z2.boolean().optional()
45
+ });
46
+ var OverrideSchema = z2.object({
47
+ files: z2.array(z2.string()),
48
+ naming: CategorySchema.optional(),
49
+ structure: CategorySchema.optional(),
50
+ documentation: CategorySchema.optional(),
51
+ errorHandling: CategorySchema.optional(),
52
+ formatting: CategorySchema.optional(),
53
+ patterns: CategorySchema.optional()
54
+ });
55
+ var SeverityThresholdsSchema = z2.object({
56
+ error: z2.number().min(0).max(1),
57
+ warn: z2.number().min(0).max(1),
58
+ info: z2.number().min(0).max(1)
59
+ });
60
+ var DEFAULT_SEVERITY_THRESHOLDS = {
61
+ error: 0.85,
62
+ warn: 0.6,
63
+ info: 0.4
64
+ };
65
+ var ProfileSchema = z2.object({
66
+ $schema: z2.string().optional(),
67
+ schemaVersion: z2.string(),
68
+ author: z2.string(),
69
+ generated: z2.string(),
70
+ sources: z2.array(z2.string()),
71
+ naming: CategorySchema,
72
+ structure: CategorySchema,
73
+ documentation: CategorySchema,
74
+ errorHandling: CategorySchema,
75
+ formatting: CategorySchema,
76
+ patterns: CategorySchema,
77
+ idioms: z2.object({
78
+ detected: z2.array(IdiomSchema)
79
+ }),
80
+ antiPatterns: z2.object({
81
+ acknowledged: z2.array(AntiPatternSchema)
82
+ }),
83
+ overrides: z2.array(OverrideSchema),
84
+ severityThresholds: SeverityThresholdsSchema.default(DEFAULT_SEVERITY_THRESHOLDS)
85
+ });
86
+ var PROFILE_CATEGORIES = [
87
+ "naming",
88
+ "structure",
89
+ "documentation",
90
+ "errorHandling",
91
+ "formatting",
92
+ "patterns"
93
+ ];
94
+
95
+ // src/io.ts
96
+ import * as fs from "fs/promises";
97
+ async function readProfile(filePath) {
98
+ const raw = await fs.readFile(filePath, "utf-8");
99
+ const json = JSON.parse(raw);
100
+ return ProfileSchema.parse(json);
101
+ }
102
+ async function writeProfile(filePath, profile) {
103
+ const validated = ProfileSchema.parse(profile);
104
+ await fs.writeFile(filePath, JSON.stringify(validated, null, 2) + "\n");
105
+ }
106
+ function validateProfile(data) {
107
+ return ProfileSchema.safeParse(data);
108
+ }
109
+
110
+ // src/migrations/registry.ts
111
+ var migrations = [];
112
+ function registerMigration(migration) {
113
+ migrations.push(migration);
114
+ migrations.sort((a, b) => a.from.localeCompare(b.from));
115
+ }
116
+ function getMigrations() {
117
+ return migrations;
118
+ }
119
+
120
+ // src/migrations/migrate-profile.ts
121
+ function migrateProfile(profile) {
122
+ let current = { ...profile };
123
+ const version = current.schemaVersion;
124
+ if (version === SCHEMA_VERSION) {
125
+ return current;
126
+ }
127
+ const migrations2 = getMigrations();
128
+ let currentVersion = version;
129
+ for (const migration of migrations2) {
130
+ if (migration.from === currentVersion) {
131
+ current = migration.migrate(current);
132
+ currentVersion = migration.to;
133
+ }
134
+ }
135
+ if (currentVersion !== SCHEMA_VERSION) {
136
+ throw new Error(
137
+ `No migration path from version ${version} to ${SCHEMA_VERSION}`
138
+ );
139
+ }
140
+ return current;
141
+ }
142
+
143
+ // src/exporters/template-helpers.ts
144
+ function extractAllRules(profile) {
145
+ const rules = [];
146
+ for (const category of PROFILE_CATEGORIES) {
147
+ const section = profile[category];
148
+ if (!section || typeof section !== "object") continue;
149
+ for (const [name, rule] of Object.entries(section)) {
150
+ if (!rule || typeof rule !== "object") continue;
151
+ rules.push({
152
+ category,
153
+ name,
154
+ convention: rule.convention,
155
+ confidence: rule.confidence,
156
+ stability: rule.stability,
157
+ description: rule.description,
158
+ examples: rule.examples,
159
+ extensions: rule.extensions
160
+ });
161
+ }
162
+ }
163
+ return rules;
164
+ }
165
+ function getTopRules(profile, count = 8) {
166
+ const errorThreshold = profile.severityThresholds?.error ?? 0.85;
167
+ return extractAllRules(profile).filter((r) => r.confidence >= errorThreshold).sort((a, b) => b.confidence - a.confidence).slice(0, count);
168
+ }
169
+ function getRulesForCategory(profile, category) {
170
+ return extractAllRules(profile).filter((r) => r.category === category);
171
+ }
172
+ function getRulesByCategory(profile) {
173
+ const grouped = /* @__PURE__ */ new Map();
174
+ for (const rule of extractAllRules(profile)) {
175
+ const existing = grouped.get(rule.category) ?? [];
176
+ existing.push(rule);
177
+ grouped.set(rule.category, existing);
178
+ }
179
+ return grouped;
180
+ }
181
+ var byConfidence = (a, b) => b.confidence - a.confidence;
182
+ function getRulesByTier(profile) {
183
+ const allRules = extractAllRules(profile);
184
+ const errorThreshold = profile.severityThresholds?.error ?? 0.85;
185
+ const warnThreshold = profile.severityThresholds?.warn ?? 0.6;
186
+ const infoThreshold = profile.severityThresholds?.info ?? 0.4;
187
+ const critical = [];
188
+ const strong = [];
189
+ const preferred = [];
190
+ for (const rule of allRules) {
191
+ if (rule.confidence >= errorThreshold) {
192
+ critical.push(rule);
193
+ } else if (rule.confidence >= warnThreshold) {
194
+ strong.push(rule);
195
+ } else if (rule.confidence >= infoThreshold) {
196
+ preferred.push(rule);
197
+ }
198
+ }
199
+ critical.sort(byConfidence);
200
+ strong.sort(byConfidence);
201
+ preferred.sort(byConfidence);
202
+ return { critical, strong, preferred };
203
+ }
204
+ function readableConvention(rule) {
205
+ if (typeof rule.convention === "boolean" || typeof rule.convention === "number") {
206
+ return rule.description ?? JSON.stringify(rule.convention);
207
+ }
208
+ if (Array.isArray(rule.convention)) {
209
+ return rule.convention.join(" \u2192 ");
210
+ }
211
+ return String(rule.convention);
212
+ }
213
+ function detectLanguages(profile) {
214
+ const langs = [];
215
+ const hasTypescriptSignals = Object.keys(profile.naming).length > 0 || Object.keys(profile.structure).length > 0;
216
+ if (hasTypescriptSignals) langs.push("typescript");
217
+ return langs.length > 0 ? langs : ["typescript"];
218
+ }
219
+
220
+ // src/exporters/templates.ts
221
+ import Handlebars from "handlebars";
222
+ import { existsSync, readFileSync } from "fs";
223
+ import { dirname, join } from "path";
224
+ import { fileURLToPath } from "url";
225
+ function findPackageRoot(start) {
226
+ let dir = start;
227
+ while (!existsSync(join(dir, "package.json"))) {
228
+ const parent = dirname(dir);
229
+ if (parent === dir) throw new Error(`style-profile package root not found above ${start}`);
230
+ dir = parent;
231
+ }
232
+ return dir;
233
+ }
234
+ function loadTemplate(name) {
235
+ const root = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
236
+ const source = readFileSync(join(root, "templates", name), "utf-8");
237
+ return Handlebars.compile(source);
238
+ }
239
+
240
+ // src/exporters/skill.ts
241
+ function buildTieredRulesContext(profile) {
242
+ const tiers = getRulesByTier(profile);
243
+ const mapRule = (r) => ({
244
+ name: `${r.category}.${r.name}`,
245
+ readableConvention: readableConvention(r),
246
+ confidencePercent: (r.confidence * 100).toFixed(0)
247
+ });
248
+ return {
249
+ criticalRules: tiers.critical.map(mapRule),
250
+ strongRules: tiers.strong.map(mapRule),
251
+ preferredRules: tiers.preferred.map(mapRule)
252
+ };
253
+ }
254
+ function buildNamingContext(profile) {
255
+ const section = profile.naming;
256
+ return Object.entries(section).map(([name, rule]) => ({
257
+ label: name.charAt(0).toUpperCase() + name.slice(1),
258
+ convention: typeof rule.convention === "string" ? rule.convention : JSON.stringify(rule.convention),
259
+ confidencePercent: (rule.confidence * 100).toFixed(0),
260
+ stability: rule.stability,
261
+ fixability: rule.fixability,
262
+ description: rule.description,
263
+ examples: rule.examples
264
+ }));
265
+ }
266
+ function buildPatternsContext(profile) {
267
+ const section = profile.patterns;
268
+ const patternRules = Object.entries(section).map(([name, rule]) => ({
269
+ name,
270
+ strength: typeof rule.convention === "string" ? rule.convention : "detected",
271
+ confidencePercent: (rule.confidence * 100).toFixed(0),
272
+ description: rule.description,
273
+ fixability: rule.fixability,
274
+ examples: rule.examples
275
+ }));
276
+ const preferredPatterns = profile.structure?.preferredPatterns;
277
+ let preferredList;
278
+ if (preferredPatterns && Array.isArray(preferredPatterns.convention)) {
279
+ preferredList = preferredPatterns.convention;
280
+ }
281
+ return { patterns: patternRules, preferredPatterns: preferredList };
282
+ }
283
+ function buildIdiomsContext(profile) {
284
+ return profile.idioms.detected.map((idiom) => ({
285
+ name: idiom.name,
286
+ description: idiom.description,
287
+ example: idiom.example
288
+ }));
289
+ }
290
+ function buildAntiPatternsContext(profile) {
291
+ return profile.antiPatterns.acknowledged.map((ap) => ({
292
+ pattern: ap.pattern,
293
+ reason: ap.reason
294
+ }));
295
+ }
296
+ function buildLanguageContext(profile, lang) {
297
+ const tiers = getRulesByTier(profile);
298
+ const mapRule = (r) => ({
299
+ category: r.category,
300
+ name: r.name,
301
+ convention: typeof r.convention === "string" ? r.convention : JSON.stringify(r.convention),
302
+ confidencePercent: (r.confidence * 100).toFixed(0),
303
+ readableConvention: readableConvention(r),
304
+ description: r.description
305
+ });
306
+ return {
307
+ language: lang.charAt(0).toUpperCase() + lang.slice(1),
308
+ criticalRules: tiers.critical.map(mapRule),
309
+ strongRules: tiers.strong.map(mapRule),
310
+ preferredRules: tiers.preferred.map(mapRule)
311
+ };
312
+ }
313
+ function renderSkillFile(profile, languages) {
314
+ const skillTemplate = loadTemplate("skill.md.hbs");
315
+ const idioms = buildIdiomsContext(profile);
316
+ const antiPatterns = buildAntiPatternsContext(profile);
317
+ return {
318
+ path: "skill.md",
319
+ content: skillTemplate({
320
+ author: profile.author,
321
+ ...buildTieredRulesContext(profile),
322
+ languages,
323
+ idioms: idioms.length > 0 ? idioms : void 0,
324
+ antiPatterns: antiPatterns.length > 0 ? antiPatterns : void 0
325
+ })
326
+ };
327
+ }
328
+ function generateSkillFiles(profile) {
329
+ const files = [];
330
+ const languages = detectLanguages(profile);
331
+ files.push(renderSkillFile(profile, languages));
332
+ const namingTemplate = loadTemplate("naming.md.hbs");
333
+ files.push({
334
+ path: "references/naming.md",
335
+ content: namingTemplate({ rules: buildNamingContext(profile) })
336
+ });
337
+ const patternsTemplate = loadTemplate("patterns.md.hbs");
338
+ files.push({
339
+ path: "references/patterns.md",
340
+ content: patternsTemplate(buildPatternsContext(profile))
341
+ });
342
+ const langTemplate = loadTemplate("per-language.md.hbs");
343
+ for (const lang of languages) {
344
+ files.push({
345
+ path: `references/per-language/${lang}.md`,
346
+ content: langTemplate(buildLanguageContext(profile, lang))
347
+ });
348
+ }
349
+ return files;
350
+ }
351
+
352
+ // src/exporters/claude-rules.ts
353
+ function formatFrontmatter(globs, description) {
354
+ return [
355
+ "---",
356
+ `description: "${description}"`,
357
+ `globs: "${globs.join(", ")}"`,
358
+ "alwaysApply: false",
359
+ "---",
360
+ ""
361
+ ].join("\n");
362
+ }
363
+ function formatRuleLine(category, name, convention, description) {
364
+ const value = typeof convention === "string" ? convention : JSON.stringify(convention);
365
+ const desc = description ? ` -- ${description}` : "";
366
+ return `- **${category}.${name}**: \`${value}\`${desc}`;
367
+ }
368
+ function formatBody(author, rules) {
369
+ return [
370
+ `# ${author}'s TypeScript Style`,
371
+ "",
372
+ ...rules.sort((a, b) => b.confidence - a.confidence).map(
373
+ (r) => formatRuleLine(r.category, r.name, r.convention, r.description)
374
+ )
375
+ ].join("\n");
376
+ }
377
+ function generateClaudeRules(profile) {
378
+ const files = [];
379
+ const allRules = extractAllRules(profile);
380
+ const infoThreshold = profile.severityThresholds?.info ?? 0.4;
381
+ const eligibleRules = allRules.filter(
382
+ (r) => r.confidence >= infoThreshold
383
+ );
384
+ if (eligibleRules.length > 0) {
385
+ const frontmatter = formatFrontmatter(
386
+ ["**/*.ts", "**/*.tsx"],
387
+ `${profile.author}'s TypeScript coding style preferences`
388
+ );
389
+ files.push({
390
+ path: ".claude/rules/typescript.md",
391
+ content: frontmatter + formatBody(profile.author, eligibleRules) + "\n"
392
+ });
393
+ }
394
+ return files;
395
+ }
396
+
397
+ // src/exporters/hooks.ts
398
+ function generateHooksConfig(_profile) {
399
+ return {
400
+ hooks: [
401
+ {
402
+ event: "PostToolUse",
403
+ matcher: "Write",
404
+ command: "codewatch diff --fix $TOOL_INPUT_FILE_PATH"
405
+ },
406
+ {
407
+ event: "PostToolUse",
408
+ matcher: "Edit",
409
+ command: "codewatch diff --fix $TOOL_INPUT_FILE_PATH"
410
+ }
411
+ ]
412
+ };
413
+ }
414
+
415
+ // src/exporters/eslint-rules.ts
416
+ function toEslintSeverity(confidence, thresholds) {
417
+ if (confidence >= thresholds.error) return "error";
418
+ if (confidence >= thresholds.warn) return "warn";
419
+ if (confidence >= thresholds.info) return "info";
420
+ return null;
421
+ }
422
+ function severityRank(s) {
423
+ return s === "error" ? 3 : s === "warn" ? 2 : 1;
424
+ }
425
+ var NAMING_SELECTORS = {
426
+ variables: "variable",
427
+ functions: "function",
428
+ types: "typeLike",
429
+ constants: "variable"
430
+ };
431
+ function eslintExtensionOptions(rule) {
432
+ const eslintExt = rule.extensions?.eslint;
433
+ return eslintExt && typeof eslintExt === "object" && "options" in eslintExt && Array.isArray(eslintExt.options) ? eslintExt.options : void 0;
434
+ }
435
+ function namingSelectors(rule, selectorName) {
436
+ const eslintOptions = eslintExtensionOptions(rule);
437
+ if (eslintOptions) return eslintOptions;
438
+ if (typeof rule.convention === "string") {
439
+ return [{ selector: selectorName, format: [rule.convention] }];
440
+ }
441
+ return [];
442
+ }
443
+ function buildNamingConventionRule(profile) {
444
+ const thresholds = profile.severityThresholds;
445
+ const naming = profile.naming;
446
+ if (!naming) return null;
447
+ const selectors = [];
448
+ let maxSeverity = null;
449
+ for (const [key, rule] of Object.entries(naming)) {
450
+ if (key === "files") continue;
451
+ const selectorName = NAMING_SELECTORS[key];
452
+ if (!selectorName) continue;
453
+ const severity = toEslintSeverity(rule.confidence, thresholds);
454
+ if (!severity) continue;
455
+ if (!maxSeverity || severityRank(severity) > severityRank(maxSeverity)) {
456
+ maxSeverity = severity;
457
+ }
458
+ selectors.push(...namingSelectors(rule, selectorName));
459
+ }
460
+ if (selectors.length === 0 || !maxSeverity) return null;
461
+ return ["@typescript-eslint/naming-convention", [maxSeverity, ...selectors]];
462
+ }
463
+ function buildImportOrderRule(profile) {
464
+ const importOrder = profile.structure?.importOrder;
465
+ if (!importOrder) return null;
466
+ const severity = toEslintSeverity(importOrder.confidence, profile.severityThresholds);
467
+ if (!severity) return null;
468
+ const convention = Array.isArray(importOrder.convention) ? importOrder.convention : ["builtin", "external", "internal", "relative"];
469
+ return [
470
+ "perfectionist/sort-imports",
471
+ [
472
+ severity,
473
+ {
474
+ type: "natural",
475
+ groups: convention
476
+ }
477
+ ]
478
+ ];
479
+ }
480
+ function buildFunctionLengthRule(profile) {
481
+ const maxLines = profile.structure?.functionMaxLines;
482
+ if (!maxLines) return null;
483
+ const severity = toEslintSeverity(maxLines.confidence, profile.severityThresholds);
484
+ if (!severity) return null;
485
+ if (typeof maxLines.convention !== "number") return null;
486
+ return ["max-lines-per-function", [severity, { max: maxLines.convention }]];
487
+ }
488
+ function buildFileNamingRule(profile) {
489
+ const files = profile.naming?.files;
490
+ if (!files) return null;
491
+ const severity = toEslintSeverity(files.confidence, profile.severityThresholds);
492
+ if (!severity) return null;
493
+ if (typeof files.convention !== "string") return null;
494
+ const caseMap = {
495
+ "kebab-case": "kebabCase",
496
+ camelCase: "camelCase",
497
+ PascalCase: "pascalCase",
498
+ snake_case: "snakeCase"
499
+ };
500
+ return [
501
+ "unicorn/filename-case",
502
+ [severity, { case: { [caseMap[files.convention] ?? "kebabCase"]: true } }]
503
+ ];
504
+ }
505
+ function buildJsdocRules(profile) {
506
+ const rules = [];
507
+ const docs = profile.documentation;
508
+ if (!docs) return rules;
509
+ const functionDocs = docs.functionDocs;
510
+ if (!functionDocs) return rules;
511
+ const severity = toEslintSeverity(functionDocs.confidence, profile.severityThresholds);
512
+ if (!severity) return rules;
513
+ if (functionDocs.convention === "jsdoc-selective") {
514
+ rules.push(["jsdoc/require-jsdoc", [severity, { publicOnly: true }]]);
515
+ } else if (functionDocs.convention === "jsdoc-all") {
516
+ rules.push(["jsdoc/require-jsdoc", [severity]]);
517
+ }
518
+ return rules;
519
+ }
520
+
521
+ // src/exporters/eslint.ts
522
+ function extensionEntry(rule, thresholds) {
523
+ const ext = rule.extensions?.eslint;
524
+ if (!ext) return null;
525
+ const sev = toEslintSeverity(rule.confidence, thresholds);
526
+ if (sev === null) return null;
527
+ const plugin = ext.rule.includes("/") ? ext.rule.split("/").slice(0, -1).join("/") : "";
528
+ return {
529
+ plugin,
530
+ rule: ext.rule,
531
+ severity: sev === "info" ? "warn" : sev,
532
+ options: ext.options
533
+ };
534
+ }
535
+ function collectEslintExtensions(profile) {
536
+ const entries = [];
537
+ for (const category of PROFILE_CATEGORIES) {
538
+ const section = profile[category];
539
+ if (!section) continue;
540
+ for (const [, rule] of Object.entries(section)) {
541
+ const entry = extensionEntry(rule, profile.severityThresholds);
542
+ if (entry) entries.push(entry);
543
+ }
544
+ }
545
+ return entries;
546
+ }
547
+ function addImportOrderRule(profile, entries) {
548
+ const result = buildImportOrderRule(profile);
549
+ if (!result) return;
550
+ const [ruleName, ruleConfig] = result;
551
+ const configArray = ruleConfig;
552
+ const severity = configArray[0];
553
+ if (severity === "info") return;
554
+ entries.push({
555
+ plugin: "perfectionist",
556
+ rule: ruleName,
557
+ severity,
558
+ options: [configArray[1]]
559
+ });
560
+ }
561
+ function detectRequiredPlugins(entries) {
562
+ const plugins = /* @__PURE__ */ new Set();
563
+ for (const entry of entries) {
564
+ if (entry.plugin.includes("@typescript-eslint")) {
565
+ plugins.add("@typescript-eslint");
566
+ }
567
+ if (entry.plugin === "perfectionist") {
568
+ plugins.add("perfectionist");
569
+ }
570
+ if (entry.plugin.includes("unicorn")) {
571
+ plugins.add("unicorn");
572
+ }
573
+ if (entry.plugin.includes("jsdoc")) {
574
+ plugins.add("jsdoc");
575
+ }
576
+ }
577
+ return plugins;
578
+ }
579
+ function generateImportStatements(plugins) {
580
+ const imports = [];
581
+ if (plugins.has("@typescript-eslint")) {
582
+ imports.push(`import tseslint from "@typescript-eslint/eslint-plugin";`);
583
+ imports.push(`import tsparser from "@typescript-eslint/parser";`);
584
+ }
585
+ if (plugins.has("perfectionist")) {
586
+ imports.push(`import perfectionist from "eslint-plugin-perfectionist";`);
587
+ }
588
+ if (plugins.has("unicorn")) {
589
+ imports.push(`import unicorn from "eslint-plugin-unicorn";`);
590
+ }
591
+ if (plugins.has("jsdoc")) {
592
+ imports.push(`import jsdoc from "eslint-plugin-jsdoc";`);
593
+ }
594
+ return imports;
595
+ }
596
+ function buildRulesObject(entries) {
597
+ const rules = {};
598
+ for (const entry of entries) {
599
+ if (entry.options && entry.options.length > 0) {
600
+ rules[entry.rule] = [entry.severity, ...entry.options];
601
+ } else {
602
+ rules[entry.rule] = entry.severity;
603
+ }
604
+ }
605
+ return rules;
606
+ }
607
+ function generateEslintExport(profile) {
608
+ const entries = collectEslintExtensions(profile);
609
+ addImportOrderRule(profile, entries);
610
+ const plugins = detectRequiredPlugins(entries);
611
+ const imports = generateImportStatements(plugins);
612
+ const rules = buildRulesObject(entries);
613
+ const header = [
614
+ `// Generated by codewatch for ${profile.author}`,
615
+ `// Date: ${profile.generated}`,
616
+ `// Do not edit manually - regenerate with: codewatch export --format eslint`,
617
+ ""
618
+ ].join("\n");
619
+ const content = [
620
+ header,
621
+ ...imports,
622
+ "",
623
+ `export default ${JSON.stringify({ rules }, null, 2)};`,
624
+ ""
625
+ ].join("\n");
626
+ return { path: "eslint.config.js", content };
627
+ }
628
+
629
+ // src/exporters/ruff.ts
630
+ var CATEGORY_KEYS = [
631
+ "naming",
632
+ "structure",
633
+ "documentation",
634
+ "errorHandling",
635
+ "formatting",
636
+ "patterns"
637
+ ];
638
+ function addExtensionCodes(rule, select) {
639
+ if (!rule.extensions?.ruff) return;
640
+ const ext = rule.extensions.ruff;
641
+ if (!ext.codes) return;
642
+ for (const code of ext.codes) {
643
+ select.add(code.replace(/\d+$/, ""));
644
+ }
645
+ }
646
+ function applyFunctionMaxLines(category, name, rule, acc) {
647
+ if (category !== "structure" || name !== "functionMaxLines") return;
648
+ acc.select.add("C90");
649
+ if (typeof rule.convention === "number") {
650
+ acc.mccabe = { maxComplexity: rule.convention };
651
+ }
652
+ }
653
+ function applyRule(category, name, rule, acc) {
654
+ addExtensionCodes(rule, acc.select);
655
+ if (category === "naming") {
656
+ acc.select.add("N");
657
+ }
658
+ if (category === "structure" && name === "importOrder") {
659
+ acc.select.add("I");
660
+ if (Array.isArray(rule.convention)) {
661
+ acc.isort = { sectionOrder: rule.convention };
662
+ }
663
+ }
664
+ if (category === "documentation" && name === "functionDocs") {
665
+ acc.select.add("D");
666
+ if (typeof rule.convention === "string") {
667
+ acc.pydocstyle = { convention: rule.convention };
668
+ }
669
+ }
670
+ applyFunctionMaxLines(category, name, rule, acc);
671
+ }
672
+ function lineLengthOf(profile) {
673
+ const lineLengthRule = profile.formatting?.lineLength;
674
+ if (lineLengthRule && typeof lineLengthRule.convention === "number") {
675
+ return lineLengthRule.convention;
676
+ }
677
+ return void 0;
678
+ }
679
+ function buildRuffConfig(profile) {
680
+ const thresholds = profile.severityThresholds;
681
+ const acc = { select: /* @__PURE__ */ new Set() };
682
+ const fixable = /* @__PURE__ */ new Set(["ALL"]);
683
+ for (const category of CATEGORY_KEYS) {
684
+ const section = profile[category];
685
+ if (!section) continue;
686
+ for (const [name, rule] of Object.entries(
687
+ section
688
+ )) {
689
+ if (rule.confidence < thresholds.info) continue;
690
+ applyRule(category, name, rule, acc);
691
+ }
692
+ }
693
+ return {
694
+ lineLength: lineLengthOf(profile),
695
+ lint: {
696
+ select: [...acc.select].sort(),
697
+ ignore: [],
698
+ fixable: [...fixable],
699
+ mccabe: acc.mccabe,
700
+ pydocstyle: acc.pydocstyle,
701
+ isort: acc.isort
702
+ }
703
+ };
704
+ }
705
+ function toTomlArray(items) {
706
+ return `[${items.map((s) => `"${s}"`).join(", ")}]`;
707
+ }
708
+ function serializeLintTable(lint) {
709
+ const lines = ["[lint]"];
710
+ if (lint.select.length > 0) {
711
+ lines.push(`select = ${toTomlArray(lint.select)}`);
712
+ }
713
+ if (lint.ignore.length > 0) {
714
+ lines.push(`ignore = ${toTomlArray(lint.ignore)}`);
715
+ }
716
+ if (lint.fixable.length > 0) {
717
+ lines.push(`fixable = ${toTomlArray(lint.fixable)}`);
718
+ }
719
+ lines.push("");
720
+ return lines;
721
+ }
722
+ function serializeLintSubtables(lint) {
723
+ const lines = [];
724
+ if (lint.mccabe) {
725
+ lines.push("[lint.mccabe]");
726
+ lines.push(`max-complexity = ${lint.mccabe.maxComplexity}`);
727
+ lines.push("");
728
+ }
729
+ if (lint.pydocstyle) {
730
+ lines.push("[lint.pydocstyle]");
731
+ lines.push(`convention = "${lint.pydocstyle.convention}"`);
732
+ lines.push("");
733
+ }
734
+ if (lint.isort) {
735
+ lines.push("[lint.isort]");
736
+ lines.push(`section-order = ${toTomlArray(lint.isort.sectionOrder)}`);
737
+ lines.push("");
738
+ }
739
+ return lines;
740
+ }
741
+ function serializeToToml(config) {
742
+ const lines = [];
743
+ if (config.lineLength !== void 0) {
744
+ lines.push(`line-length = ${config.lineLength}`);
745
+ lines.push("");
746
+ }
747
+ lines.push(...serializeLintTable(config.lint));
748
+ lines.push(...serializeLintSubtables(config.lint));
749
+ return lines.join("\n");
750
+ }
751
+ function generateRuffExport(profile) {
752
+ const config = buildRuffConfig(profile);
753
+ const header = [
754
+ `# Generated by codewatch for ${profile.author}`,
755
+ `# Date: ${profile.generated}`,
756
+ `# Do not edit manually - regenerate with: codewatch export --format ruff`,
757
+ ""
758
+ ].join("\n");
759
+ return {
760
+ path: "ruff.toml",
761
+ content: header + serializeToToml(config)
762
+ };
763
+ }
764
+
765
+ // src/exporters/markdown.ts
766
+ function capitalize(str) {
767
+ const spaced = str.replace(/([A-Z])/g, " $1");
768
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
769
+ }
770
+ function formatConvention(convention) {
771
+ if (typeof convention === "string") return convention;
772
+ if (typeof convention === "number") return String(convention);
773
+ if (typeof convention === "boolean") return String(convention);
774
+ return JSON.stringify(convention);
775
+ }
776
+ function groupByCategory(rules) {
777
+ const byCategory = /* @__PURE__ */ new Map();
778
+ for (const rule of rules) {
779
+ const existing = byCategory.get(rule.category) ?? [];
780
+ existing.push(rule);
781
+ byCategory.set(rule.category, existing);
782
+ }
783
+ return byCategory;
784
+ }
785
+ function exampleLines(examples) {
786
+ const lines = [];
787
+ for (const example of examples) {
788
+ if (example.good) {
789
+ lines.push("```typescript");
790
+ lines.push("// Preferred");
791
+ lines.push(example.good);
792
+ lines.push("```");
793
+ lines.push("");
794
+ }
795
+ if (example.bad) {
796
+ lines.push("```typescript");
797
+ lines.push("// Avoid");
798
+ lines.push(example.bad);
799
+ lines.push("```");
800
+ lines.push("");
801
+ }
802
+ }
803
+ return lines;
804
+ }
805
+ function ruleLines(rule) {
806
+ const lines = [];
807
+ const confPct = (rule.confidence * 100).toFixed(0);
808
+ const value = formatConvention(rule.convention);
809
+ lines.push(`### ${capitalize(rule.name)}`);
810
+ lines.push("");
811
+ lines.push(
812
+ `**Convention**: \`${value}\` | **Confidence**: ${confPct}%${rule.stability ? ` | **Stability**: ${rule.stability}` : ""}`
813
+ );
814
+ lines.push("");
815
+ if (rule.description) {
816
+ lines.push(rule.description);
817
+ lines.push("");
818
+ }
819
+ if (rule.examples && rule.examples.length > 0) {
820
+ lines.push(...exampleLines(rule.examples));
821
+ }
822
+ return lines;
823
+ }
824
+ function generateMarkdownExport(profile) {
825
+ const allRules = extractAllRules(profile);
826
+ const byCategory = groupByCategory(allRules);
827
+ const lines = [];
828
+ lines.push(`# ${profile.author}'s Code Style Guide`);
829
+ lines.push("");
830
+ lines.push(`> Generated by codewatch on ${profile.generated}`);
831
+ lines.push(
832
+ `> Sources: ${profile.sources.length > 0 ? profile.sources.join(", ") : "N/A"}`
833
+ );
834
+ lines.push("");
835
+ for (const [category, rules] of byCategory) {
836
+ lines.push(`## ${capitalize(category)}`);
837
+ lines.push("");
838
+ const sorted = [...rules].sort((a, b) => b.confidence - a.confidence);
839
+ for (const rule of sorted) {
840
+ lines.push(...ruleLines(rule));
841
+ }
842
+ }
843
+ return {
844
+ path: "style-guide.md",
845
+ content: lines.join("\n")
846
+ };
847
+ }
848
+
849
+ // src/exporters/editorconfig.ts
850
+ function getConvention(section, key) {
851
+ const rule = section[key];
852
+ if (!rule) return void 0;
853
+ return rule.convention;
854
+ }
855
+ function indentAndNewlineLines(formatting) {
856
+ const lines = [];
857
+ const indentStyle = getConvention(formatting, "indentStyle");
858
+ if (indentStyle) {
859
+ const value = indentStyle === "spaces" ? "space" : indentStyle === "tabs" ? "tab" : indentStyle;
860
+ lines.push(`indent_style = ${value}`);
861
+ }
862
+ const indentSize = getConvention(formatting, "indentSize");
863
+ if (indentSize !== void 0) {
864
+ lines.push(`indent_size = ${indentSize}`);
865
+ }
866
+ const trailingNewline = getConvention(
867
+ formatting,
868
+ "trailingNewline"
869
+ );
870
+ if (trailingNewline !== void 0) {
871
+ lines.push(`insert_final_newline = ${trailingNewline}`);
872
+ }
873
+ return lines;
874
+ }
875
+ function generateEditorConfigExport(profile) {
876
+ const formatting = profile.formatting;
877
+ const lines = [];
878
+ lines.push("# Generated by codewatch");
879
+ lines.push("root = true");
880
+ lines.push("");
881
+ lines.push("[*]");
882
+ lines.push(...indentAndNewlineLines(formatting));
883
+ lines.push("end_of_line = lf");
884
+ lines.push("charset = utf-8");
885
+ lines.push("trim_trailing_whitespace = true");
886
+ const lineLength = getConvention(formatting, "lineLength");
887
+ if (lineLength !== void 0) {
888
+ lines.push(`max_line_length = ${lineLength}`);
889
+ }
890
+ lines.push("");
891
+ return {
892
+ path: ".editorconfig",
893
+ content: lines.join("\n")
894
+ };
895
+ }
896
+
897
+ // src/exporters/export-index.ts
898
+ var SUPPORTED_FORMATS = [
899
+ "skill",
900
+ "claude-rules",
901
+ "hooks",
902
+ "eslint",
903
+ "ruff",
904
+ "markdown",
905
+ "editorconfig"
906
+ ];
907
+ function hooksFiles(profile) {
908
+ const config = generateHooksConfig(profile);
909
+ return [
910
+ {
911
+ path: ".claude/settings.json",
912
+ content: JSON.stringify(config, null, 2)
913
+ }
914
+ ];
915
+ }
916
+ function exportProfile(profile, format) {
917
+ switch (format) {
918
+ case "skill":
919
+ return generateSkillFiles(profile);
920
+ case "claude-rules":
921
+ return generateClaudeRules(profile);
922
+ case "hooks":
923
+ return hooksFiles(profile);
924
+ case "eslint":
925
+ return [generateEslintExport(profile)];
926
+ case "ruff":
927
+ return [generateRuffExport(profile)];
928
+ case "markdown":
929
+ return [generateMarkdownExport(profile)];
930
+ case "editorconfig":
931
+ return [generateEditorConfigExport(profile)];
932
+ default:
933
+ throw new Error(
934
+ `Unsupported export format: "${format}". Supported formats: ${SUPPORTED_FORMATS.join(", ")}`
935
+ );
936
+ }
937
+ }
938
+ export {
939
+ DEFAULT_SEVERITY_THRESHOLDS,
940
+ ExampleSchema,
941
+ FixabilitySchema,
942
+ PROFILE_CATEGORIES,
943
+ ProfileSchema,
944
+ SCHEMA_VERSION,
945
+ SUPPORTED_FORMATS,
946
+ StabilitySchema,
947
+ StyleRuleSchema,
948
+ buildFileNamingRule,
949
+ buildFunctionLengthRule,
950
+ buildImportOrderRule,
951
+ buildJsdocRules,
952
+ buildNamingConventionRule,
953
+ detectLanguages,
954
+ exportProfile,
955
+ extractAllRules,
956
+ generateClaudeRules,
957
+ generateEditorConfigExport,
958
+ generateEslintExport,
959
+ generateHooksConfig,
960
+ generateMarkdownExport,
961
+ generateRuffExport,
962
+ generateSkillFiles,
963
+ getRulesByCategory,
964
+ getRulesForCategory,
965
+ getTopRules,
966
+ migrateProfile,
967
+ readProfile,
968
+ registerMigration,
969
+ severityRank,
970
+ toEslintSeverity,
971
+ validateProfile,
972
+ writeProfile
973
+ };
974
+ //# sourceMappingURL=index.js.map