@hyphaene/hexa-ts-kit 1.11.0 → 1.12.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.
@@ -753,6 +753,114 @@ var rulesRegistry = {
753
753
  }
754
754
  },
755
755
  // ==========================================================================
756
+ // Contracts Nomenclature Rules (CTR-ENUM-*, CTR-RESPONSE-*, etc.)
757
+ // ==========================================================================
758
+ "CTR-ENUM-001": {
759
+ title: "Enum bundle missing required exports",
760
+ severity: "error",
761
+ category: "contracts",
762
+ why: "Enum bundles must export {name}Schema, {NAME}, and {NAME}_VALUES for consistent API.",
763
+ autoFixable: false
764
+ },
765
+ "CTR-ENUM-002": {
766
+ title: "Enum bundle missing createEnumBundle()",
767
+ severity: "warning",
768
+ category: "contracts",
769
+ why: "Enum bundles should use createEnumBundle() for consistent schema/enum/values creation.",
770
+ autoFixable: false
771
+ },
772
+ "CTR-ENUM-003": {
773
+ title: "Enum bundle missing inferred type export",
774
+ severity: "error",
775
+ category: "contracts",
776
+ why: "Enum bundles must export a type via z.infer for type safety.",
777
+ autoFixable: false
778
+ },
779
+ "CTR-RESPONSE-001": {
780
+ title: "Response file missing export ending with {HTTP}Schema",
781
+ severity: "error",
782
+ category: "contracts",
783
+ why: "Response files must export a schema whose name ends with {HTTP}Schema (e.g. MyFeature200Schema or MyFeatureResponse200Schema).",
784
+ autoFixable: false
785
+ },
786
+ "CTR-RESPONSE-002": {
787
+ title: "Response file missing default export",
788
+ severity: "error",
789
+ category: "contracts",
790
+ why: "Response files must have a default export for fixture validation scripts.",
791
+ autoFixable: false
792
+ },
793
+ "CTR-RESPONSE-003": {
794
+ title: "Response file missing OpenAPI documentation",
795
+ severity: "error",
796
+ category: "contracts",
797
+ why: "Response schema fields must have .describe() or .openapi() for OpenAPI documentation generation.",
798
+ autoFixable: false
799
+ },
800
+ "CTR-REQUEST-001": {
801
+ title: "Request file missing typed schema export",
802
+ severity: "error",
803
+ category: "contracts",
804
+ why: "Request files must export a schema whose name ends with QuerySchema, BodySchema, HeadersSchema, or ParamsSchema.",
805
+ autoFixable: false
806
+ },
807
+ "CTR-REQUEST-002": {
808
+ title: "Request file missing OpenAPI documentation",
809
+ severity: "error",
810
+ category: "contracts",
811
+ why: "Request schema fields should use .describe() for OpenAPI documentation.",
812
+ autoFixable: false
813
+ },
814
+ "CTR-CONTRACT-001": {
815
+ title: "Contract file missing {Name}Contract export",
816
+ severity: "error",
817
+ category: "contracts",
818
+ why: "Contract files must export a named {Name}Contract for discoverability.",
819
+ autoFixable: false
820
+ },
821
+ "CTR-CONTRACT-002": {
822
+ title: "Contract file missing strictStatusCodes: true",
823
+ severity: "error",
824
+ category: "contracts",
825
+ why: "Contract files must use strictStatusCodes: true for type safety.",
826
+ autoFixable: false
827
+ },
828
+ "CTR-CONTRACT-003": {
829
+ title: "Contract file missing contractMetadata()",
830
+ severity: "error",
831
+ category: "contracts",
832
+ why: "Contract endpoints must use contractMetadata() for permission and tag declaration.",
833
+ autoFixable: false
834
+ },
835
+ "CTR-INDEX-001": {
836
+ title: "Index file contains local declarations",
837
+ severity: "error",
838
+ category: "contracts",
839
+ why: "Index files must only contain re-exports (export * from / export { } from).",
840
+ autoFixable: false
841
+ },
842
+ "CTR-HELPER-001": {
843
+ title: "Helper file contains Zod data schemas",
844
+ severity: "warning",
845
+ category: "contracts",
846
+ why: "Helper files should contain pure functions, not Zod data schemas.",
847
+ autoFixable: false
848
+ },
849
+ "CTR-NAMING-001": {
850
+ title: "File has non-standard suffix",
851
+ severity: "error",
852
+ category: "contracts",
853
+ why: "All source files must use an allowed suffix from the nomenclature.",
854
+ autoFixable: false
855
+ },
856
+ "CTR-DEPRECATED-001": {
857
+ title: "Deprecated file export missing @deprecated JSDoc",
858
+ severity: "error",
859
+ category: "contracts",
860
+ why: "All exports in .deprecated.ts files must have @deprecated JSDoc annotation.",
861
+ autoFixable: false
862
+ },
863
+ // ==========================================================================
756
864
  // Contracts Colocation Rules (COL-CTR-*)
757
865
  // ==========================================================================
758
866
  "COL-CTR-001": {
@@ -837,7 +945,7 @@ var ContractsLintConfigSchema = z.object({
837
945
  metadata: z.object({
838
946
  permissions: SourceExtractConfigSchema,
839
947
  apiServices: SourceExtractConfigSchema
840
- }),
948
+ }).optional(),
841
949
  disabledRules: z.array(z.string()).optional()
842
950
  });
843
951
  var VaultLintConfigSchema = z.object({
@@ -2280,6 +2388,476 @@ function checkSchemaImport(contractFile, _contractDir, imp) {
2280
2388
  return results;
2281
2389
  }
2282
2390
 
2391
+ // src/lint/checkers/contracts/nomenclature.ts
2392
+ import fg9 from "fast-glob";
2393
+ import { readFileSync as readFileSync4 } from "fs";
2394
+ import { basename as basename5 } from "path";
2395
+ import { parse as parse3 } from "@typescript-eslint/typescript-estree";
2396
+ var ALLOWED_SUFFIXES = [
2397
+ ".enum-bundle.ts",
2398
+ ".schema.ts",
2399
+ ".response.ts",
2400
+ ".request.ts",
2401
+ ".presenter.ts",
2402
+ ".contract.ts",
2403
+ ".helpers.ts",
2404
+ ".constants.ts",
2405
+ ".deprecated.ts",
2406
+ ".test.ts",
2407
+ ".contract.test.ts",
2408
+ ".migration.test.ts",
2409
+ ".rules.ts"
2410
+ ];
2411
+ var ALLOWED_SPECIAL_FILES = ["index.ts", "openapi.ts"];
2412
+ function parseFile(filePath) {
2413
+ try {
2414
+ const content = readFileSync4(filePath, "utf-8");
2415
+ return parse3(content, {
2416
+ loc: true,
2417
+ range: true,
2418
+ comment: true,
2419
+ jsx: false
2420
+ });
2421
+ } catch {
2422
+ return null;
2423
+ }
2424
+ }
2425
+ function readFileContent(filePath) {
2426
+ return readFileSync4(filePath, "utf-8");
2427
+ }
2428
+ function getExportedNames(ast) {
2429
+ const names = [];
2430
+ for (const node of ast.body) {
2431
+ if (node.type === "ExportNamedDeclaration") {
2432
+ if (node.declaration) {
2433
+ if (node.declaration.type === "VariableDeclaration") {
2434
+ for (const decl of node.declaration.declarations) {
2435
+ if (decl.id.type === "Identifier") {
2436
+ names.push(decl.id.name);
2437
+ } else if (decl.id.type === "ObjectPattern") {
2438
+ for (const prop of decl.id.properties) {
2439
+ if (prop.type === "Property" && prop.value.type === "Identifier") {
2440
+ names.push(prop.value.name);
2441
+ }
2442
+ }
2443
+ }
2444
+ }
2445
+ }
2446
+ if (node.declaration.type === "TSTypeAliasDeclaration") {
2447
+ names.push(node.declaration.id.name);
2448
+ }
2449
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
2450
+ names.push(node.declaration.id.name);
2451
+ }
2452
+ }
2453
+ if (node.specifiers) {
2454
+ for (const spec of node.specifiers) {
2455
+ if (spec.exported.type === "Identifier") {
2456
+ names.push(spec.exported.name);
2457
+ }
2458
+ }
2459
+ }
2460
+ }
2461
+ }
2462
+ return names;
2463
+ }
2464
+ function hasDefaultExport(ast) {
2465
+ return ast.body.some(
2466
+ (node) => node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration" && node.specifiers?.some(
2467
+ (s) => s.exported.type === "Identifier" && s.exported.name === "default"
2468
+ )
2469
+ );
2470
+ }
2471
+ function hasOpenApiDocumentation(content) {
2472
+ return /\.describe\s*\(/.test(content) || /\.openapi\s*\(/.test(content);
2473
+ }
2474
+ function hasCreateEnumBundleCall(content) {
2475
+ return /createEnumBundle\s*\(/.test(content);
2476
+ }
2477
+ function hasZodInferTypeExport(content) {
2478
+ return /export\s+type\s+\w+\s*=\s*z\.infer/.test(content);
2479
+ }
2480
+ function hasZodDataSchemas(content) {
2481
+ return /(?:const|let|var)\s+\w+\s*=\s*z\.(?:object|enum|array)\s*\(/.test(
2482
+ content
2483
+ );
2484
+ }
2485
+ function getExportsWithoutDeprecatedJsdoc(ast, content) {
2486
+ const missingDeprecated = [];
2487
+ for (const node of ast.body) {
2488
+ if (node.type !== "ExportNamedDeclaration" && node.type !== "ExportDefaultDeclaration") {
2489
+ continue;
2490
+ }
2491
+ let exportName = "default";
2492
+ if (node.type === "ExportNamedDeclaration") {
2493
+ if (node.declaration) {
2494
+ if (node.declaration.type === "VariableDeclaration") {
2495
+ const decl = node.declaration.declarations[0];
2496
+ if (decl?.id.type === "Identifier") {
2497
+ exportName = decl.id.name;
2498
+ }
2499
+ } else if (node.declaration.type === "TSTypeAliasDeclaration" || node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
2500
+ exportName = node.declaration.type === "TSTypeAliasDeclaration" ? node.declaration.id.name : node.declaration.id.name;
2501
+ }
2502
+ }
2503
+ if (node.source) continue;
2504
+ }
2505
+ const linesBefore = content.substring(0, node.range?.[0] ?? 0);
2506
+ const lastCommentBlock = linesBefore.match(/\/\*\*[\s\S]*?\*\/\s*$/);
2507
+ if (!lastCommentBlock || !lastCommentBlock[0].includes("@deprecated")) {
2508
+ missingDeprecated.push(exportName);
2509
+ }
2510
+ }
2511
+ return missingDeprecated;
2512
+ }
2513
+ function getNonReexportStatements(ast) {
2514
+ const violations = [];
2515
+ for (const node of ast.body) {
2516
+ if (node.type === "ImportDeclaration") continue;
2517
+ if (node.type === "ExportAllDeclaration") continue;
2518
+ if (node.type === "ExportNamedDeclaration" && node.source) continue;
2519
+ if (node.type === "ExportNamedDeclaration" && node.exportKind === "type" && node.source)
2520
+ continue;
2521
+ violations.push(node);
2522
+ }
2523
+ return violations;
2524
+ }
2525
+ async function checkEnumBundleRules(cwd) {
2526
+ const results = [];
2527
+ const files = await fg9("src/**/*.enum-bundle.ts", {
2528
+ cwd,
2529
+ ignore: ["**/node_modules/**", "**/dist/**"]
2530
+ });
2531
+ for (const file of files) {
2532
+ const fullPath = `${cwd}/${file}`;
2533
+ const content = readFileContent(fullPath);
2534
+ const ast = parseFile(fullPath);
2535
+ if (!ast) continue;
2536
+ const exportedNames = getExportedNames(ast);
2537
+ const fileName = basename5(file, ".enum-bundle.ts");
2538
+ const upperName = fileName.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
2539
+ const hasSchema = exportedNames.some((n) => n.endsWith("Schema"));
2540
+ const hasEnumMapper = exportedNames.some(
2541
+ (n) => n === n.toUpperCase() && n.length > 1
2542
+ );
2543
+ const hasValues = exportedNames.some((n) => n.endsWith("_VALUES"));
2544
+ if (!hasSchema || !hasEnumMapper || !hasValues) {
2545
+ const missing = [];
2546
+ if (!hasSchema) missing.push("{name}Schema");
2547
+ if (!hasEnumMapper) missing.push("{NAME}");
2548
+ if (!hasValues) missing.push("{NAME}_VALUES");
2549
+ results.push({
2550
+ ruleId: "CTR-ENUM-001",
2551
+ severity: "error",
2552
+ message: `Enum bundle ${file} missing exports: ${missing.join(", ")}`,
2553
+ file,
2554
+ suggestion: `Export ${upperName}_VALUES, ${upperName}, and a Schema const`
2555
+ });
2556
+ }
2557
+ if (!hasCreateEnumBundleCall(content)) {
2558
+ results.push({
2559
+ ruleId: "CTR-ENUM-002",
2560
+ severity: "warning",
2561
+ message: `Enum bundle ${file} does not use createEnumBundle()`,
2562
+ file,
2563
+ suggestion: "Use createEnumBundle() for consistent schema/enum/values creation"
2564
+ });
2565
+ }
2566
+ if (!hasZodInferTypeExport(content)) {
2567
+ results.push({
2568
+ ruleId: "CTR-ENUM-003",
2569
+ severity: "error",
2570
+ message: `Enum bundle ${file} missing type export via z.infer`,
2571
+ file,
2572
+ suggestion: "Add: export type {Name} = z.infer<typeof {name}Schema>"
2573
+ });
2574
+ }
2575
+ }
2576
+ return results;
2577
+ }
2578
+ async function checkResponseRules(cwd) {
2579
+ const results = [];
2580
+ const files = await fg9("src/**/*.response.ts", {
2581
+ cwd,
2582
+ ignore: ["**/node_modules/**", "**/dist/**"]
2583
+ });
2584
+ for (const file of files) {
2585
+ const fullPath = `${cwd}/${file}`;
2586
+ const content = readFileContent(fullPath);
2587
+ const ast = parseFile(fullPath);
2588
+ if (!ast) continue;
2589
+ const exportedNames = getExportedNames(ast);
2590
+ const fileName = basename5(file, ".response.ts");
2591
+ const httpCode = fileName.match(/^(\d{3})$/)?.[1];
2592
+ if (httpCode) {
2593
+ if (!exportedNames.some((n) => n.includes(httpCode) && n.endsWith("Schema"))) {
2594
+ results.push({
2595
+ ruleId: "CTR-RESPONSE-001",
2596
+ severity: "error",
2597
+ message: `Response file ${file} missing export containing "${httpCode}" and ending with "Schema"`,
2598
+ file,
2599
+ suggestion: `Export a schema whose name contains ${httpCode} and ends with Schema (e.g. MyFeature${httpCode}Schema)`
2600
+ });
2601
+ }
2602
+ }
2603
+ if (!hasDefaultExport(ast)) {
2604
+ results.push({
2605
+ ruleId: "CTR-RESPONSE-002",
2606
+ severity: "error",
2607
+ message: `Response file ${file} missing default export`,
2608
+ file,
2609
+ suggestion: "Add: export default Response{HTTP}Schema"
2610
+ });
2611
+ }
2612
+ if (!hasOpenApiDocumentation(content)) {
2613
+ results.push({
2614
+ ruleId: "CTR-RESPONSE-003",
2615
+ severity: "error",
2616
+ message: `Response file ${file} has no OpenAPI documentation (.describe() or .openapi())`,
2617
+ file,
2618
+ suggestion: "Add .describe('...') or .openapi({ description: '...' }) to schema fields"
2619
+ });
2620
+ }
2621
+ }
2622
+ return results;
2623
+ }
2624
+ async function checkRequestRules(cwd) {
2625
+ const results = [];
2626
+ const files = await fg9("src/**/*.request.ts", {
2627
+ cwd,
2628
+ ignore: ["**/node_modules/**", "**/dist/**"]
2629
+ });
2630
+ for (const file of files) {
2631
+ const fullPath = `${cwd}/${file}`;
2632
+ const content = readFileContent(fullPath);
2633
+ const ast = parseFile(fullPath);
2634
+ if (!ast) continue;
2635
+ const exportedNames = getExportedNames(ast);
2636
+ const hasTypedSchema = exportedNames.some(
2637
+ (n) => /(Query|Body|Headers|Params)Schema$/.test(n)
2638
+ );
2639
+ if (!hasTypedSchema) {
2640
+ results.push({
2641
+ ruleId: "CTR-REQUEST-001",
2642
+ severity: "error",
2643
+ message: `Request file ${file} missing typed schema export (ending with QuerySchema, BodySchema, HeadersSchema, or ParamsSchema)`,
2644
+ file,
2645
+ suggestion: "Export a schema whose name ends with: QuerySchema, BodySchema, HeadersSchema, or ParamsSchema"
2646
+ });
2647
+ }
2648
+ if (!hasOpenApiDocumentation(content)) {
2649
+ results.push({
2650
+ ruleId: "CTR-REQUEST-002",
2651
+ severity: "error",
2652
+ message: `Request file ${file} has no OpenAPI documentation (.describe() or .openapi())`,
2653
+ file,
2654
+ suggestion: "Add .describe('...') or .openapi({ description: '...' }) to schema fields"
2655
+ });
2656
+ }
2657
+ }
2658
+ return results;
2659
+ }
2660
+ async function checkContractFileRules(cwd) {
2661
+ const results = [];
2662
+ const files = await fg9("src/**/*.contract.ts", {
2663
+ cwd,
2664
+ ignore: ["**/node_modules/**", "**/dist/**", "**/*.contract.test.ts"]
2665
+ });
2666
+ for (const file of files) {
2667
+ const fullPath = `${cwd}/${file}`;
2668
+ const content = readFileContent(fullPath);
2669
+ const ast = parseFile(fullPath);
2670
+ if (!ast) continue;
2671
+ const exportedNames = getExportedNames(ast);
2672
+ const hasContractExport = exportedNames.some((n) => n.endsWith("Contract"));
2673
+ if (!hasContractExport) {
2674
+ results.push({
2675
+ ruleId: "CTR-CONTRACT-001",
2676
+ severity: "error",
2677
+ message: `Contract file ${file} missing {Name}Contract export`,
2678
+ file,
2679
+ suggestion: "Export const {Name}Contract = c.router({...})"
2680
+ });
2681
+ }
2682
+ if (!content.includes("strictStatusCodes")) {
2683
+ results.push({
2684
+ ruleId: "CTR-CONTRACT-002",
2685
+ severity: "error",
2686
+ message: `Contract file ${file} missing strictStatusCodes: true`,
2687
+ file,
2688
+ suggestion: "Add strictStatusCodes: true to all endpoints"
2689
+ });
2690
+ }
2691
+ if (!content.includes("contractMetadata")) {
2692
+ results.push({
2693
+ ruleId: "CTR-CONTRACT-003",
2694
+ severity: "error",
2695
+ message: `Contract file ${file} missing contractMetadata() usage`,
2696
+ file,
2697
+ suggestion: "Use metadata: contractMetadata({...}) for each endpoint"
2698
+ });
2699
+ }
2700
+ }
2701
+ return results;
2702
+ }
2703
+ async function checkIndexRules(cwd) {
2704
+ const results = [];
2705
+ const files = await fg9("src/**/index.ts", {
2706
+ cwd,
2707
+ ignore: ["**/node_modules/**", "**/dist/**", "**/lib/**"]
2708
+ });
2709
+ for (const file of files) {
2710
+ const fullPath = `${cwd}/${file}`;
2711
+ const ast = parseFile(fullPath);
2712
+ if (!ast) continue;
2713
+ const violations = getNonReexportStatements(ast);
2714
+ if (violations.length > 0) {
2715
+ results.push({
2716
+ ruleId: "CTR-INDEX-001",
2717
+ severity: "error",
2718
+ message: `Index file ${file} contains ${violations.length} non-reexport statement(s)`,
2719
+ file,
2720
+ line: violations[0]?.loc?.start.line,
2721
+ suggestion: "Index files should only contain: export * from '...' or export { X } from '...'"
2722
+ });
2723
+ }
2724
+ }
2725
+ return results;
2726
+ }
2727
+ async function checkHelperRules(cwd) {
2728
+ const results = [];
2729
+ const files = await fg9("src/**/*.helpers.ts", {
2730
+ cwd,
2731
+ ignore: ["**/node_modules/**", "**/dist/**"]
2732
+ });
2733
+ for (const file of files) {
2734
+ const fullPath = `${cwd}/${file}`;
2735
+ const content = readFileContent(fullPath);
2736
+ if (hasZodDataSchemas(content)) {
2737
+ results.push({
2738
+ ruleId: "CTR-HELPER-001",
2739
+ severity: "warning",
2740
+ message: `Helper file ${file} contains Zod data schemas`,
2741
+ file,
2742
+ suggestion: "Move Zod data schemas to .schema.ts or .enum-bundle.ts files"
2743
+ });
2744
+ }
2745
+ }
2746
+ return results;
2747
+ }
2748
+ async function checkNamingRules(cwd) {
2749
+ const results = [];
2750
+ const files = await fg9("src/**/*.ts", {
2751
+ cwd,
2752
+ ignore: [
2753
+ "**/node_modules/**",
2754
+ "**/dist/**",
2755
+ "**/lib/**",
2756
+ "**/__tests__/**"
2757
+ ]
2758
+ });
2759
+ for (const file of files) {
2760
+ const fileName = basename5(file);
2761
+ if (ALLOWED_SPECIAL_FILES.includes(fileName)) continue;
2762
+ const matchesSuffix = ALLOWED_SUFFIXES.some(
2763
+ (suffix) => fileName.endsWith(suffix)
2764
+ );
2765
+ if (!matchesSuffix) {
2766
+ results.push({
2767
+ ruleId: "CTR-NAMING-001",
2768
+ severity: "error",
2769
+ message: `File ${file} has non-standard suffix. Allowed: ${ALLOWED_SUFFIXES.join(", ")}`,
2770
+ file,
2771
+ suggestion: `Rename to use one of: ${ALLOWED_SUFFIXES.map((s) => `*${s}`).join(", ")}`
2772
+ });
2773
+ }
2774
+ }
2775
+ return results;
2776
+ }
2777
+ async function checkDeprecatedRules(cwd) {
2778
+ const results = [];
2779
+ const files = await fg9("src/**/*.deprecated.ts", {
2780
+ cwd,
2781
+ ignore: ["**/node_modules/**", "**/dist/**"]
2782
+ });
2783
+ for (const file of files) {
2784
+ const fullPath = `${cwd}/${file}`;
2785
+ const content = readFileContent(fullPath);
2786
+ const ast = parseFile(fullPath);
2787
+ if (!ast) continue;
2788
+ const missing = getExportsWithoutDeprecatedJsdoc(ast, content);
2789
+ if (missing.length > 0) {
2790
+ results.push({
2791
+ ruleId: "CTR-DEPRECATED-001",
2792
+ severity: "error",
2793
+ message: `Deprecated file ${file} has exports without @deprecated JSDoc: ${missing.join(", ")}`,
2794
+ file,
2795
+ suggestion: "Add /** @deprecated Use X instead */ before each export"
2796
+ });
2797
+ }
2798
+ }
2799
+ return results;
2800
+ }
2801
+ var contractsNomenclatureChecker = {
2802
+ name: "contracts-nomenclature",
2803
+ rules: [
2804
+ "CTR-ENUM-001",
2805
+ "CTR-ENUM-002",
2806
+ "CTR-ENUM-003",
2807
+ "CTR-RESPONSE-001",
2808
+ "CTR-RESPONSE-002",
2809
+ "CTR-RESPONSE-003",
2810
+ "CTR-REQUEST-001",
2811
+ "CTR-REQUEST-002",
2812
+ "CTR-CONTRACT-001",
2813
+ "CTR-CONTRACT-002",
2814
+ "CTR-CONTRACT-003",
2815
+ "CTR-INDEX-001",
2816
+ "CTR-HELPER-001",
2817
+ "CTR-NAMING-001",
2818
+ "CTR-DEPRECATED-001"
2819
+ ],
2820
+ async check(ctx) {
2821
+ const configResult = loadConfig(ctx.cwd);
2822
+ if (!configResult.success || configResult.config.project.type !== "contracts-lib") {
2823
+ return [];
2824
+ }
2825
+ const disabledRules = getDisabledRules(configResult.config);
2826
+ const results = [];
2827
+ const [
2828
+ enumResults,
2829
+ responseResults,
2830
+ requestResults,
2831
+ contractResults,
2832
+ indexResults,
2833
+ helperResults,
2834
+ namingResults,
2835
+ deprecatedResults
2836
+ ] = await Promise.all([
2837
+ checkEnumBundleRules(ctx.cwd),
2838
+ checkResponseRules(ctx.cwd),
2839
+ checkRequestRules(ctx.cwd),
2840
+ checkContractFileRules(ctx.cwd),
2841
+ checkIndexRules(ctx.cwd),
2842
+ checkHelperRules(ctx.cwd),
2843
+ checkNamingRules(ctx.cwd),
2844
+ checkDeprecatedRules(ctx.cwd)
2845
+ ]);
2846
+ results.push(
2847
+ ...enumResults,
2848
+ ...responseResults,
2849
+ ...requestResults,
2850
+ ...contractResults,
2851
+ ...indexResults,
2852
+ ...helperResults,
2853
+ ...namingResults,
2854
+ ...deprecatedResults
2855
+ );
2856
+ if (disabledRules.length === 0) return results;
2857
+ return results.filter((r) => !disabledRules.includes(r.ruleId));
2858
+ }
2859
+ };
2860
+
2283
2861
  // src/lint/checkers/contracts/index.ts
2284
2862
  var contractsChecker = {
2285
2863
  name: "contracts",
@@ -2361,6 +2939,8 @@ var contractsChecker = {
2361
2939
  }
2362
2940
  const colocationResults = await contractsColocationChecker.check(ctx);
2363
2941
  results.push(...colocationResults);
2942
+ const nomenclatureResults = await contractsNomenclatureChecker.check(ctx);
2943
+ results.push(...nomenclatureResults);
2364
2944
  return results;
2365
2945
  }
2366
2946
  };
@@ -2759,10 +3339,10 @@ Completed in ${elapsed}ms`);
2759
3339
  }
2760
3340
 
2761
3341
  // src/commands/analyze.ts
2762
- import { basename as basename5 } from "path";
3342
+ import { basename as basename6 } from "path";
2763
3343
  import { execSync as execSync2 } from "child_process";
2764
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
2765
- import fg9 from "fast-glob";
3344
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3345
+ import fg10 from "fast-glob";
2766
3346
  import matter from "gray-matter";
2767
3347
  import { minimatch } from "minimatch";
2768
3348
  function expandPath(p) {
@@ -2791,18 +3371,18 @@ function loadKnowledgeMappings(knowledgePath) {
2791
3371
  if (!existsSync6(expandedPath)) {
2792
3372
  return [];
2793
3373
  }
2794
- const knowledgeFiles = fg9.sync("**/*.knowledge.md", {
3374
+ const knowledgeFiles = fg10.sync("**/*.knowledge.md", {
2795
3375
  cwd: expandedPath,
2796
3376
  absolute: true
2797
3377
  });
2798
3378
  const mappings = [];
2799
3379
  for (const file of knowledgeFiles) {
2800
3380
  try {
2801
- const content = readFileSync4(file, "utf-8");
3381
+ const content = readFileSync5(file, "utf-8");
2802
3382
  const { data } = matter(content);
2803
3383
  if (data.match) {
2804
3384
  mappings.push({
2805
- name: data.name || basename5(file, ".knowledge.md"),
3385
+ name: data.name || basename6(file, ".knowledge.md"),
2806
3386
  path: file,
2807
3387
  match: data.match,
2808
3388
  description: data.description
@@ -2815,7 +3395,7 @@ function loadKnowledgeMappings(knowledgePath) {
2815
3395
  }
2816
3396
  function matchFileToKnowledges(file, mappings) {
2817
3397
  const results = [];
2818
- const fileName = basename5(file);
3398
+ const fileName = basename6(file);
2819
3399
  for (const mapping of mappings) {
2820
3400
  if (minimatch(fileName, mapping.match) || minimatch(file, mapping.match)) {
2821
3401
  results.push({
@@ -2877,7 +3457,7 @@ async function analyzeCommand(files = [], options) {
2877
3457
  }
2878
3458
 
2879
3459
  // src/commands/scaffold.ts
2880
- import { resolve as resolve3, dirname as dirname5, basename as basename6, join as join4 } from "path";
3460
+ import { resolve as resolve3, dirname as dirname5, basename as basename7, join as join4 } from "path";
2881
3461
  import { mkdirSync, writeFileSync, existsSync as existsSync7 } from "fs";
2882
3462
  function toPascalCase(str) {
2883
3463
  return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
@@ -2887,7 +3467,7 @@ function toCamelCase(str) {
2887
3467
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
2888
3468
  }
2889
3469
  function generateVueFeature(featurePath) {
2890
- const featureName = basename6(featurePath);
3470
+ const featureName = basename7(featurePath);
2891
3471
  const pascalName = toPascalCase(featureName);
2892
3472
  const camelName = toCamelCase(featureName);
2893
3473
  return [
@@ -3004,7 +3584,7 @@ describe('${pascalName}Rules', () => {
3004
3584
  ];
3005
3585
  }
3006
3586
  function generateNestJSFeature(featurePath) {
3007
- const featureName = basename6(featurePath);
3587
+ const featureName = basename7(featurePath);
3008
3588
  const pascalName = toPascalCase(featureName);
3009
3589
  const camelName = toCamelCase(featureName);
3010
3590
  return [
@@ -3095,7 +3675,7 @@ describe('${pascalName}Controller', () => {
3095
3675
  ];
3096
3676
  }
3097
3677
  function generatePlaywrightFeature(featurePath) {
3098
- const featureName = basename6(featurePath);
3678
+ const featureName = basename7(featurePath);
3099
3679
  const pascalName = toPascalCase(featureName);
3100
3680
  const camelName = toCamelCase(featureName);
3101
3681
  return [
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  lintCommand,
6
6
  loadConfig,
7
7
  scaffoldCommand
8
- } from "./chunk-IBVI6IS2.js";
8
+ } from "./chunk-EB6S5X3P.js";
9
9
  import {
10
10
  scanForVaultRepos
11
11
  } from "./chunk-WXFSGE4N.js";
@@ -3,7 +3,7 @@ import {
3
3
  analyzeCore,
4
4
  lintCore,
5
5
  scaffoldCore
6
- } from "./chunk-IBVI6IS2.js";
6
+ } from "./chunk-EB6S5X3P.js";
7
7
 
8
8
  // src/mcp-server.ts
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -18,7 +18,7 @@ var server = new McpServer({
18
18
  });
19
19
  server.tool(
20
20
  "lint",
21
- "Lint files for colocation architecture rules. Checks 71 rules across structure (COL, NAM, DOM) and AST (VUE, RUL, TSP) categories. Use --changed for incremental mode on git-modified files only.",
21
+ "Lint files for colocation architecture rules. Checks 96 rules across structure (COL, NAM, DOM, CTR) and AST (VUE, RUL, TSP) categories. Use --changed for incremental mode on git-modified files only.",
22
22
  {
23
23
  cwd: z.string().optional().describe("Working directory to lint (default: current directory)"),
24
24
  path: z.string().optional().describe("Alias for cwd - path to lint"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyphaene/hexa-ts-kit",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "TypeScript dev kit for Claude Code agents: architecture linting, scaffolding, knowledge analysis",
5
5
  "type": "module",
6
6
  "bin": {