@hyphaene/hexa-ts-kit 1.11.0 → 1.12.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.
@@ -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,453 @@ 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, { loc: true, range: true, comment: true, jsx: false });
2416
+ } catch {
2417
+ return null;
2418
+ }
2419
+ }
2420
+ function readFileContent(filePath) {
2421
+ return readFileSync4(filePath, "utf-8");
2422
+ }
2423
+ function getExportedNames(ast) {
2424
+ const names = [];
2425
+ for (const node of ast.body) {
2426
+ if (node.type === "ExportNamedDeclaration") {
2427
+ if (node.declaration) {
2428
+ if (node.declaration.type === "VariableDeclaration") {
2429
+ for (const decl of node.declaration.declarations) {
2430
+ if (decl.id.type === "Identifier") {
2431
+ names.push(decl.id.name);
2432
+ }
2433
+ }
2434
+ }
2435
+ if (node.declaration.type === "TSTypeAliasDeclaration") {
2436
+ names.push(node.declaration.id.name);
2437
+ }
2438
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
2439
+ names.push(node.declaration.id.name);
2440
+ }
2441
+ }
2442
+ if (node.specifiers) {
2443
+ for (const spec of node.specifiers) {
2444
+ if (spec.exported.type === "Identifier") {
2445
+ names.push(spec.exported.name);
2446
+ }
2447
+ }
2448
+ }
2449
+ }
2450
+ }
2451
+ return names;
2452
+ }
2453
+ function hasDefaultExport(ast) {
2454
+ return ast.body.some(
2455
+ (node) => node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration" && node.specifiers?.some(
2456
+ (s) => s.exported.type === "Identifier" && s.exported.name === "default"
2457
+ )
2458
+ );
2459
+ }
2460
+ function hasOpenApiDocumentation(content) {
2461
+ return /\.describe\s*\(/.test(content) || /\.openapi\s*\(/.test(content);
2462
+ }
2463
+ function hasCreateEnumBundleCall(content) {
2464
+ return /createEnumBundle\s*\(/.test(content);
2465
+ }
2466
+ function hasZodInferTypeExport(content) {
2467
+ return /export\s+type\s+\w+\s*=\s*z\.infer/.test(content);
2468
+ }
2469
+ function hasZodDataSchemas(content) {
2470
+ return /(?:const|let|var)\s+\w+\s*=\s*z\.(?:object|enum|array)\s*\(/.test(content);
2471
+ }
2472
+ function getExportsWithoutDeprecatedJsdoc(ast, content) {
2473
+ const missingDeprecated = [];
2474
+ for (const node of ast.body) {
2475
+ if (node.type !== "ExportNamedDeclaration" && node.type !== "ExportDefaultDeclaration") {
2476
+ continue;
2477
+ }
2478
+ let exportName = "default";
2479
+ if (node.type === "ExportNamedDeclaration") {
2480
+ if (node.declaration) {
2481
+ if (node.declaration.type === "VariableDeclaration") {
2482
+ const decl = node.declaration.declarations[0];
2483
+ if (decl?.id.type === "Identifier") {
2484
+ exportName = decl.id.name;
2485
+ }
2486
+ } else if (node.declaration.type === "TSTypeAliasDeclaration" || node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
2487
+ exportName = node.declaration.type === "TSTypeAliasDeclaration" ? node.declaration.id.name : node.declaration.id.name;
2488
+ }
2489
+ }
2490
+ if (node.source) continue;
2491
+ }
2492
+ const linesBefore = content.substring(0, node.range?.[0] ?? 0);
2493
+ const lastCommentBlock = linesBefore.match(/\/\*\*[\s\S]*?\*\/\s*$/);
2494
+ if (!lastCommentBlock || !lastCommentBlock[0].includes("@deprecated")) {
2495
+ missingDeprecated.push(exportName);
2496
+ }
2497
+ }
2498
+ return missingDeprecated;
2499
+ }
2500
+ function getNonReexportStatements(ast) {
2501
+ const violations = [];
2502
+ for (const node of ast.body) {
2503
+ if (node.type === "ImportDeclaration") continue;
2504
+ if (node.type === "ExportAllDeclaration") continue;
2505
+ if (node.type === "ExportNamedDeclaration" && node.source) continue;
2506
+ if (node.type === "ExportNamedDeclaration" && node.exportKind === "type" && node.source) continue;
2507
+ violations.push(node);
2508
+ }
2509
+ return violations;
2510
+ }
2511
+ async function checkEnumBundleRules(cwd) {
2512
+ const results = [];
2513
+ const files = await fg9("src/**/*.enum-bundle.ts", {
2514
+ cwd,
2515
+ ignore: ["**/node_modules/**", "**/dist/**"]
2516
+ });
2517
+ for (const file of files) {
2518
+ const fullPath = `${cwd}/${file}`;
2519
+ const content = readFileContent(fullPath);
2520
+ const ast = parseFile(fullPath);
2521
+ if (!ast) continue;
2522
+ const exportedNames = getExportedNames(ast);
2523
+ const fileName = basename5(file, ".enum-bundle.ts");
2524
+ const upperName = fileName.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
2525
+ const hasSchema = exportedNames.some((n) => n.endsWith("Schema"));
2526
+ const hasEnumMapper = exportedNames.some((n) => n === n.toUpperCase() && n.length > 1);
2527
+ const hasValues = exportedNames.some((n) => n.endsWith("_VALUES"));
2528
+ if (!hasSchema || !hasEnumMapper || !hasValues) {
2529
+ const missing = [];
2530
+ if (!hasSchema) missing.push("{name}Schema");
2531
+ if (!hasEnumMapper) missing.push("{NAME}");
2532
+ if (!hasValues) missing.push("{NAME}_VALUES");
2533
+ results.push({
2534
+ ruleId: "CTR-ENUM-001",
2535
+ severity: "error",
2536
+ message: `Enum bundle ${file} missing exports: ${missing.join(", ")}`,
2537
+ file,
2538
+ suggestion: `Export ${upperName}_VALUES, ${upperName}, and a Schema const`
2539
+ });
2540
+ }
2541
+ if (!hasCreateEnumBundleCall(content)) {
2542
+ results.push({
2543
+ ruleId: "CTR-ENUM-002",
2544
+ severity: "warning",
2545
+ message: `Enum bundle ${file} does not use createEnumBundle()`,
2546
+ file,
2547
+ suggestion: "Use createEnumBundle() for consistent schema/enum/values creation"
2548
+ });
2549
+ }
2550
+ if (!hasZodInferTypeExport(content)) {
2551
+ results.push({
2552
+ ruleId: "CTR-ENUM-003",
2553
+ severity: "error",
2554
+ message: `Enum bundle ${file} missing type export via z.infer`,
2555
+ file,
2556
+ suggestion: "Add: export type {Name} = z.infer<typeof {name}Schema>"
2557
+ });
2558
+ }
2559
+ }
2560
+ return results;
2561
+ }
2562
+ async function checkResponseRules(cwd) {
2563
+ const results = [];
2564
+ const files = await fg9("src/**/*.response.ts", {
2565
+ cwd,
2566
+ ignore: ["**/node_modules/**", "**/dist/**"]
2567
+ });
2568
+ for (const file of files) {
2569
+ const fullPath = `${cwd}/${file}`;
2570
+ const content = readFileContent(fullPath);
2571
+ const ast = parseFile(fullPath);
2572
+ if (!ast) continue;
2573
+ const exportedNames = getExportedNames(ast);
2574
+ const fileName = basename5(file, ".response.ts");
2575
+ const httpCode = fileName.match(/^(\d{3})$/)?.[1];
2576
+ if (httpCode) {
2577
+ if (!exportedNames.some((n) => n.includes(httpCode) && n.endsWith("Schema"))) {
2578
+ results.push({
2579
+ ruleId: "CTR-RESPONSE-001",
2580
+ severity: "error",
2581
+ message: `Response file ${file} missing export containing "${httpCode}" and ending with "Schema"`,
2582
+ file,
2583
+ suggestion: `Export a schema whose name contains ${httpCode} and ends with Schema (e.g. MyFeature${httpCode}Schema)`
2584
+ });
2585
+ }
2586
+ }
2587
+ if (!hasDefaultExport(ast)) {
2588
+ results.push({
2589
+ ruleId: "CTR-RESPONSE-002",
2590
+ severity: "error",
2591
+ message: `Response file ${file} missing default export`,
2592
+ file,
2593
+ suggestion: "Add: export default Response{HTTP}Schema"
2594
+ });
2595
+ }
2596
+ if (!hasOpenApiDocumentation(content)) {
2597
+ results.push({
2598
+ ruleId: "CTR-RESPONSE-003",
2599
+ severity: "error",
2600
+ message: `Response file ${file} has no OpenAPI documentation (.describe() or .openapi())`,
2601
+ file,
2602
+ suggestion: "Add .describe('...') or .openapi({ description: '...' }) to schema fields"
2603
+ });
2604
+ }
2605
+ }
2606
+ return results;
2607
+ }
2608
+ async function checkRequestRules(cwd) {
2609
+ const results = [];
2610
+ const files = await fg9("src/**/*.request.ts", {
2611
+ cwd,
2612
+ ignore: ["**/node_modules/**", "**/dist/**"]
2613
+ });
2614
+ for (const file of files) {
2615
+ const fullPath = `${cwd}/${file}`;
2616
+ const content = readFileContent(fullPath);
2617
+ const ast = parseFile(fullPath);
2618
+ if (!ast) continue;
2619
+ const exportedNames = getExportedNames(ast);
2620
+ const hasTypedSchema = exportedNames.some(
2621
+ (n) => /(Query|Body|Headers|Params)Schema$/.test(n)
2622
+ );
2623
+ if (!hasTypedSchema) {
2624
+ results.push({
2625
+ ruleId: "CTR-REQUEST-001",
2626
+ severity: "error",
2627
+ message: `Request file ${file} missing typed schema export (ending with QuerySchema, BodySchema, HeadersSchema, or ParamsSchema)`,
2628
+ file,
2629
+ suggestion: "Export a schema whose name ends with: QuerySchema, BodySchema, HeadersSchema, or ParamsSchema"
2630
+ });
2631
+ }
2632
+ if (!hasOpenApiDocumentation(content)) {
2633
+ results.push({
2634
+ ruleId: "CTR-REQUEST-002",
2635
+ severity: "error",
2636
+ message: `Request file ${file} has no OpenAPI documentation (.describe() or .openapi())`,
2637
+ file,
2638
+ suggestion: "Add .describe('...') or .openapi({ description: '...' }) to schema fields"
2639
+ });
2640
+ }
2641
+ }
2642
+ return results;
2643
+ }
2644
+ async function checkContractFileRules(cwd) {
2645
+ const results = [];
2646
+ const files = await fg9("src/**/*.contract.ts", {
2647
+ cwd,
2648
+ ignore: ["**/node_modules/**", "**/dist/**", "**/*.contract.test.ts"]
2649
+ });
2650
+ for (const file of files) {
2651
+ const fullPath = `${cwd}/${file}`;
2652
+ const content = readFileContent(fullPath);
2653
+ const ast = parseFile(fullPath);
2654
+ if (!ast) continue;
2655
+ const exportedNames = getExportedNames(ast);
2656
+ const hasContractExport = exportedNames.some((n) => n.endsWith("Contract"));
2657
+ if (!hasContractExport) {
2658
+ results.push({
2659
+ ruleId: "CTR-CONTRACT-001",
2660
+ severity: "error",
2661
+ message: `Contract file ${file} missing {Name}Contract export`,
2662
+ file,
2663
+ suggestion: "Export const {Name}Contract = c.router({...})"
2664
+ });
2665
+ }
2666
+ if (!content.includes("strictStatusCodes")) {
2667
+ results.push({
2668
+ ruleId: "CTR-CONTRACT-002",
2669
+ severity: "error",
2670
+ message: `Contract file ${file} missing strictStatusCodes: true`,
2671
+ file,
2672
+ suggestion: "Add strictStatusCodes: true to all endpoints"
2673
+ });
2674
+ }
2675
+ if (!content.includes("contractMetadata")) {
2676
+ results.push({
2677
+ ruleId: "CTR-CONTRACT-003",
2678
+ severity: "error",
2679
+ message: `Contract file ${file} missing contractMetadata() usage`,
2680
+ file,
2681
+ suggestion: "Use metadata: contractMetadata({...}) for each endpoint"
2682
+ });
2683
+ }
2684
+ }
2685
+ return results;
2686
+ }
2687
+ async function checkIndexRules(cwd) {
2688
+ const results = [];
2689
+ const files = await fg9("src/**/index.ts", {
2690
+ cwd,
2691
+ ignore: ["**/node_modules/**", "**/dist/**", "**/lib/**"]
2692
+ });
2693
+ for (const file of files) {
2694
+ const fullPath = `${cwd}/${file}`;
2695
+ const ast = parseFile(fullPath);
2696
+ if (!ast) continue;
2697
+ const violations = getNonReexportStatements(ast);
2698
+ if (violations.length > 0) {
2699
+ results.push({
2700
+ ruleId: "CTR-INDEX-001",
2701
+ severity: "error",
2702
+ message: `Index file ${file} contains ${violations.length} non-reexport statement(s)`,
2703
+ file,
2704
+ line: violations[0]?.loc?.start.line,
2705
+ suggestion: "Index files should only contain: export * from '...' or export { X } from '...'"
2706
+ });
2707
+ }
2708
+ }
2709
+ return results;
2710
+ }
2711
+ async function checkHelperRules(cwd) {
2712
+ const results = [];
2713
+ const files = await fg9("src/**/*.helpers.ts", {
2714
+ cwd,
2715
+ ignore: ["**/node_modules/**", "**/dist/**"]
2716
+ });
2717
+ for (const file of files) {
2718
+ const fullPath = `${cwd}/${file}`;
2719
+ const content = readFileContent(fullPath);
2720
+ if (hasZodDataSchemas(content)) {
2721
+ results.push({
2722
+ ruleId: "CTR-HELPER-001",
2723
+ severity: "warning",
2724
+ message: `Helper file ${file} contains Zod data schemas`,
2725
+ file,
2726
+ suggestion: "Move Zod data schemas to .schema.ts or .enum-bundle.ts files"
2727
+ });
2728
+ }
2729
+ }
2730
+ return results;
2731
+ }
2732
+ async function checkNamingRules(cwd) {
2733
+ const results = [];
2734
+ const files = await fg9("src/**/*.ts", {
2735
+ cwd,
2736
+ ignore: ["**/node_modules/**", "**/dist/**", "**/lib/**", "**/__tests__/**"]
2737
+ });
2738
+ for (const file of files) {
2739
+ const fileName = basename5(file);
2740
+ if (ALLOWED_SPECIAL_FILES.includes(fileName)) continue;
2741
+ const matchesSuffix = ALLOWED_SUFFIXES.some((suffix) => fileName.endsWith(suffix));
2742
+ if (!matchesSuffix) {
2743
+ results.push({
2744
+ ruleId: "CTR-NAMING-001",
2745
+ severity: "error",
2746
+ message: `File ${file} has non-standard suffix. Allowed: ${ALLOWED_SUFFIXES.join(", ")}`,
2747
+ file,
2748
+ suggestion: `Rename to use one of: ${ALLOWED_SUFFIXES.map((s) => `*${s}`).join(", ")}`
2749
+ });
2750
+ }
2751
+ }
2752
+ return results;
2753
+ }
2754
+ async function checkDeprecatedRules(cwd) {
2755
+ const results = [];
2756
+ const files = await fg9("src/**/*.deprecated.ts", {
2757
+ cwd,
2758
+ ignore: ["**/node_modules/**", "**/dist/**"]
2759
+ });
2760
+ for (const file of files) {
2761
+ const fullPath = `${cwd}/${file}`;
2762
+ const content = readFileContent(fullPath);
2763
+ const ast = parseFile(fullPath);
2764
+ if (!ast) continue;
2765
+ const missing = getExportsWithoutDeprecatedJsdoc(ast, content);
2766
+ if (missing.length > 0) {
2767
+ results.push({
2768
+ ruleId: "CTR-DEPRECATED-001",
2769
+ severity: "error",
2770
+ message: `Deprecated file ${file} has exports without @deprecated JSDoc: ${missing.join(", ")}`,
2771
+ file,
2772
+ suggestion: "Add /** @deprecated Use X instead */ before each export"
2773
+ });
2774
+ }
2775
+ }
2776
+ return results;
2777
+ }
2778
+ var contractsNomenclatureChecker = {
2779
+ name: "contracts-nomenclature",
2780
+ rules: [
2781
+ "CTR-ENUM-001",
2782
+ "CTR-ENUM-002",
2783
+ "CTR-ENUM-003",
2784
+ "CTR-RESPONSE-001",
2785
+ "CTR-RESPONSE-002",
2786
+ "CTR-RESPONSE-003",
2787
+ "CTR-REQUEST-001",
2788
+ "CTR-REQUEST-002",
2789
+ "CTR-CONTRACT-001",
2790
+ "CTR-CONTRACT-002",
2791
+ "CTR-CONTRACT-003",
2792
+ "CTR-INDEX-001",
2793
+ "CTR-HELPER-001",
2794
+ "CTR-NAMING-001",
2795
+ "CTR-DEPRECATED-001"
2796
+ ],
2797
+ async check(ctx) {
2798
+ const configResult = loadConfig(ctx.cwd);
2799
+ if (!configResult.success || configResult.config.project.type !== "contracts-lib") {
2800
+ return [];
2801
+ }
2802
+ const disabledRules = getDisabledRules(configResult.config);
2803
+ const results = [];
2804
+ const [
2805
+ enumResults,
2806
+ responseResults,
2807
+ requestResults,
2808
+ contractResults,
2809
+ indexResults,
2810
+ helperResults,
2811
+ namingResults,
2812
+ deprecatedResults
2813
+ ] = await Promise.all([
2814
+ checkEnumBundleRules(ctx.cwd),
2815
+ checkResponseRules(ctx.cwd),
2816
+ checkRequestRules(ctx.cwd),
2817
+ checkContractFileRules(ctx.cwd),
2818
+ checkIndexRules(ctx.cwd),
2819
+ checkHelperRules(ctx.cwd),
2820
+ checkNamingRules(ctx.cwd),
2821
+ checkDeprecatedRules(ctx.cwd)
2822
+ ]);
2823
+ results.push(
2824
+ ...enumResults,
2825
+ ...responseResults,
2826
+ ...requestResults,
2827
+ ...contractResults,
2828
+ ...indexResults,
2829
+ ...helperResults,
2830
+ ...namingResults,
2831
+ ...deprecatedResults
2832
+ );
2833
+ if (disabledRules.length === 0) return results;
2834
+ return results.filter((r) => !disabledRules.includes(r.ruleId));
2835
+ }
2836
+ };
2837
+
2283
2838
  // src/lint/checkers/contracts/index.ts
2284
2839
  var contractsChecker = {
2285
2840
  name: "contracts",
@@ -2361,6 +2916,8 @@ var contractsChecker = {
2361
2916
  }
2362
2917
  const colocationResults = await contractsColocationChecker.check(ctx);
2363
2918
  results.push(...colocationResults);
2919
+ const nomenclatureResults = await contractsNomenclatureChecker.check(ctx);
2920
+ results.push(...nomenclatureResults);
2364
2921
  return results;
2365
2922
  }
2366
2923
  };
@@ -2759,10 +3316,10 @@ Completed in ${elapsed}ms`);
2759
3316
  }
2760
3317
 
2761
3318
  // src/commands/analyze.ts
2762
- import { basename as basename5 } from "path";
3319
+ import { basename as basename6 } from "path";
2763
3320
  import { execSync as execSync2 } from "child_process";
2764
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
2765
- import fg9 from "fast-glob";
3321
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3322
+ import fg10 from "fast-glob";
2766
3323
  import matter from "gray-matter";
2767
3324
  import { minimatch } from "minimatch";
2768
3325
  function expandPath(p) {
@@ -2791,18 +3348,18 @@ function loadKnowledgeMappings(knowledgePath) {
2791
3348
  if (!existsSync6(expandedPath)) {
2792
3349
  return [];
2793
3350
  }
2794
- const knowledgeFiles = fg9.sync("**/*.knowledge.md", {
3351
+ const knowledgeFiles = fg10.sync("**/*.knowledge.md", {
2795
3352
  cwd: expandedPath,
2796
3353
  absolute: true
2797
3354
  });
2798
3355
  const mappings = [];
2799
3356
  for (const file of knowledgeFiles) {
2800
3357
  try {
2801
- const content = readFileSync4(file, "utf-8");
3358
+ const content = readFileSync5(file, "utf-8");
2802
3359
  const { data } = matter(content);
2803
3360
  if (data.match) {
2804
3361
  mappings.push({
2805
- name: data.name || basename5(file, ".knowledge.md"),
3362
+ name: data.name || basename6(file, ".knowledge.md"),
2806
3363
  path: file,
2807
3364
  match: data.match,
2808
3365
  description: data.description
@@ -2815,7 +3372,7 @@ function loadKnowledgeMappings(knowledgePath) {
2815
3372
  }
2816
3373
  function matchFileToKnowledges(file, mappings) {
2817
3374
  const results = [];
2818
- const fileName = basename5(file);
3375
+ const fileName = basename6(file);
2819
3376
  for (const mapping of mappings) {
2820
3377
  if (minimatch(fileName, mapping.match) || minimatch(file, mapping.match)) {
2821
3378
  results.push({
@@ -2877,7 +3434,7 @@ async function analyzeCommand(files = [], options) {
2877
3434
  }
2878
3435
 
2879
3436
  // src/commands/scaffold.ts
2880
- import { resolve as resolve3, dirname as dirname5, basename as basename6, join as join4 } from "path";
3437
+ import { resolve as resolve3, dirname as dirname5, basename as basename7, join as join4 } from "path";
2881
3438
  import { mkdirSync, writeFileSync, existsSync as existsSync7 } from "fs";
2882
3439
  function toPascalCase(str) {
2883
3440
  return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
@@ -2887,7 +3444,7 @@ function toCamelCase(str) {
2887
3444
  return pascal.charAt(0).toLowerCase() + pascal.slice(1);
2888
3445
  }
2889
3446
  function generateVueFeature(featurePath) {
2890
- const featureName = basename6(featurePath);
3447
+ const featureName = basename7(featurePath);
2891
3448
  const pascalName = toPascalCase(featureName);
2892
3449
  const camelName = toCamelCase(featureName);
2893
3450
  return [
@@ -3004,7 +3561,7 @@ describe('${pascalName}Rules', () => {
3004
3561
  ];
3005
3562
  }
3006
3563
  function generateNestJSFeature(featurePath) {
3007
- const featureName = basename6(featurePath);
3564
+ const featureName = basename7(featurePath);
3008
3565
  const pascalName = toPascalCase(featureName);
3009
3566
  const camelName = toCamelCase(featureName);
3010
3567
  return [
@@ -3095,7 +3652,7 @@ describe('${pascalName}Controller', () => {
3095
3652
  ];
3096
3653
  }
3097
3654
  function generatePlaywrightFeature(featurePath) {
3098
- const featureName = basename6(featurePath);
3655
+ const featureName = basename7(featurePath);
3099
3656
  const pascalName = toPascalCase(featureName);
3100
3657
  const camelName = toCamelCase(featureName);
3101
3658
  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-TGZA3GHG.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-TGZA3GHG.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.0",
4
4
  "description": "TypeScript dev kit for Claude Code agents: architecture linting, scaffolding, knowledge analysis",
5
5
  "type": "module",
6
6
  "bin": {