@solid-stack/mason 1.0.31 → 1.0.32

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.
@@ -1,4 +1,4 @@
1
- import type { CircularDependencyInfo, FeatureDependencyEvidence, LayerType, LintOptions, LintSummary, Violation } from "../types/lint.js";
1
+ import type { CircularDependencyInfo, FeatureDependencyEvidence, LayerType, LintOptions, LintSummary, ModuleType, Violation } from "../types/lint.js";
2
2
  export declare class DependencyRuleError extends Error {
3
3
  readonly violations: Violation[];
4
4
  readonly isFailFast: boolean;
@@ -14,6 +14,7 @@ export interface TargetInfo {
14
14
  targetFileName?: string | undefined;
15
15
  isTargetPort?: boolean | undefined;
16
16
  isTargetRepo?: boolean | undefined;
17
+ isTargetError?: boolean | undefined;
17
18
  targetSharedModule?: string | undefined;
18
19
  isSharedRoot?: boolean | undefined;
19
20
  isSharedPorts?: boolean | undefined;
@@ -23,6 +24,8 @@ export interface TargetInfo {
23
24
  export declare function isTestFile(filePath: string, projectRoot?: string): boolean;
24
25
  export declare function scanDirFiles(dir: string, projectRoot?: string): string[];
25
26
  export declare function classifyFile(fullPath: string, projectRoot: string): {
27
+ moduleType?: ModuleType | undefined;
28
+ moduleName?: string | undefined;
26
29
  feature?: string | undefined;
27
30
  layer?: LayerType | undefined;
28
31
  };
@@ -31,6 +34,8 @@ export interface ParsedStatement {
31
34
  specifier: string;
32
35
  line: number;
33
36
  rawCode: string;
37
+ isTypeOnly: boolean;
38
+ importedNames: string[];
34
39
  }
35
40
  export declare function extractImportStatements(content: string): ParsedStatement[];
36
41
  export declare class FeatureDependencyGraph {
@@ -52,6 +57,10 @@ export declare function discoverAllFeatures(projectRoot: string): Array<{
52
57
  name: string;
53
58
  dir: string;
54
59
  }>;
60
+ export declare function discoverAllShared(projectRoot: string): Array<{
61
+ name: string;
62
+ dir: string;
63
+ }>;
55
64
  export interface ClassMethodInfo {
56
65
  name: string;
57
66
  isAsync: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"lintEngine.d.ts","sourceRoot":"","sources":["../../src/core/lintEngine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lintEngine.d.ts","sourceRoot":"","sources":["../../src/core/lintEngine.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -4915,22 +4915,38 @@ function scanDirFiles(dir, projectRoot) {
4915
4915
  function classifyFile(fullPath, projectRoot) {
4916
4916
  const rel = path2__default.default.relative(projectRoot, fullPath).replace(/\\/g, "/");
4917
4917
  const featureMatch = rel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/.*)?$/);
4918
- if (!featureMatch || !featureMatch[1] || !featureMatch[2]) return {};
4919
- const feature = featureMatch[1];
4920
- const subfolder = featureMatch[2];
4921
- let layer;
4922
- if (subfolder === "domain" || subfolder === "errors") {
4923
- layer = "domain";
4924
- } else if (subfolder === "useCases" || subfolder === "usecases") {
4925
- layer = "useCases";
4926
- } else if (subfolder === "services") {
4927
- layer = "services";
4928
- } else if (subfolder === "infrastructure") {
4929
- layer = "infrastructure";
4930
- } else if (subfolder === "pxExpress" || subfolder === "presentation" || subfolder === "handlers" || subfolder.startsWith("px")) {
4931
- layer = "presentation";
4932
- }
4933
- return { feature, layer };
4918
+ if (featureMatch && featureMatch[1] && featureMatch[2]) {
4919
+ const feature = featureMatch[1];
4920
+ const subfolder = featureMatch[2];
4921
+ let layer;
4922
+ if (subfolder === "domain" || subfolder === "errors") {
4923
+ layer = "domain";
4924
+ } else if (subfolder === "useCases" || subfolder === "usecases") {
4925
+ layer = "useCases";
4926
+ } else if (subfolder === "services") {
4927
+ layer = "services";
4928
+ } else if (subfolder === "infrastructure") {
4929
+ layer = "infrastructure";
4930
+ } else if (subfolder === "pxExpress" || subfolder === "presentation" || subfolder === "handlers" || subfolder.startsWith("px")) {
4931
+ layer = "presentation";
4932
+ }
4933
+ return { moduleType: "feature", moduleName: feature, feature, layer };
4934
+ }
4935
+ const sharedMatch = rel.match(/(?:^|\/)(?:src\/)?shared\/([^/]+)(?:\/(.*))?$/);
4936
+ if (sharedMatch && sharedMatch[1]) {
4937
+ const moduleName = sharedMatch[1];
4938
+ const rest = sharedMatch[2] || "";
4939
+ let layer = "root";
4940
+ if (rest.startsWith("domain/") || rest === "domain" || rest.startsWith("errors/") || rest === "errors") {
4941
+ layer = "domain";
4942
+ } else if (rest.startsWith("ports/") || rest === "ports") {
4943
+ layer = "ports";
4944
+ } else if (rest.startsWith("infrastructure/") || rest === "infrastructure") {
4945
+ layer = "infrastructure";
4946
+ }
4947
+ return { moduleType: "shared", moduleName, feature: moduleName, layer };
4948
+ }
4949
+ return {};
4934
4950
  }
4935
4951
  function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4936
4952
  if (specifier === "@solid-stack/di" || specifier.startsWith("@solid-stack/di/")) {
@@ -4985,6 +5001,7 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4985
5001
  const targetFileName = basePart.replace(/\.(js|ts|mjs|cjs)$/, "");
4986
5002
  const isTargetPort = /^I[A-Z]/.test(targetFileName);
4987
5003
  const isTargetRepo = isTargetPort && targetFileName.includes("Repo");
5004
+ const isTargetError = targetSubfolder === "errors" || rest.startsWith("errors/") || rest.includes("/errors/") || targetFileName.endsWith("Error") || targetFileName.endsWith("Errors");
4988
5005
  return {
4989
5006
  isExternal: false,
4990
5007
  isDiFramework: false,
@@ -4993,7 +5010,8 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4993
5010
  targetSubfolder,
4994
5011
  targetFileName,
4995
5012
  isTargetPort,
4996
- isTargetRepo
5013
+ isTargetRepo,
5014
+ isTargetError
4997
5015
  };
4998
5016
  }
4999
5017
  const sharedMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?shared\/([^/]+)(?:\/(.*))?$/);
@@ -5019,17 +5037,34 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
5019
5037
  function extractImportStatements(content) {
5020
5038
  const statements = [];
5021
5039
  const lines = content.split(/\r?\n/);
5022
- const regex = /(?:import\s+(?:type\s+)?(?:[\s\w*$,{}]+\s+from\s+)?|export\s+(?:type\s+)?(?:[\s\w*$,{}]+\s+from\s+)?|import\s*\(|require\s*\()\s*["']([^"']+)["']/g;
5040
+ const regex = /(?:import\s+(type\s+)?(?:([\s\w*$,{}]+)\s+from\s+)?|export\s+(type\s+)?(?:([\s\w*$,{}]+)\s+from\s+)?|import\s*\(|require\s*\()\s*["']([^"']+)["']/g;
5023
5041
  let match;
5024
5042
  while ((match = regex.exec(content)) !== null) {
5025
- const specifier = match[1];
5043
+ const isTypeOnly = Boolean(match[1] || match[3]);
5044
+ const importClause = (match[2] || match[4] || "").trim();
5045
+ const specifier = match[5];
5026
5046
  const charIndex = match.index;
5027
5047
  const lineNum = content.substring(0, charIndex).split("\n").length;
5028
5048
  const rawCode = lines[lineNum - 1]?.trim() || match[0].trim();
5049
+ const importedNames = [];
5050
+ if (importClause) {
5051
+ const cleaned = importClause.replace(/[{}]/g, "");
5052
+ const words = cleaned.split(",").map((s) => s.trim()).filter(Boolean);
5053
+ for (const w of words) {
5054
+ const parts = w.split(/\s+as\s+/);
5055
+ const name = parts[0].replace(/^type\s+/, "").trim();
5056
+ const alias = (parts[1] || name).trim();
5057
+ if (alias && alias !== "*") {
5058
+ importedNames.push(alias);
5059
+ }
5060
+ }
5061
+ }
5029
5062
  statements.push({
5030
5063
  specifier,
5031
5064
  line: lineNum,
5032
- rawCode
5065
+ rawCode,
5066
+ isTypeOnly,
5067
+ importedNames
5033
5068
  });
5034
5069
  }
5035
5070
  return statements;
@@ -5128,6 +5163,15 @@ function discoverAllFeatures(projectRoot) {
5128
5163
  dir: path2__default.default.join(featuresBase, e.name)
5129
5164
  })).sort((a, b) => a.name.localeCompare(b.name));
5130
5165
  }
5166
+ function discoverAllShared(projectRoot) {
5167
+ const sharedBase = resolveSharedDir(projectRoot);
5168
+ if (!fs4__default.default.existsSync(sharedBase)) return [];
5169
+ const entries = fs4__default.default.readdirSync(sharedBase, { withFileTypes: true });
5170
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "__tests__" && e.name !== "tests").map((e) => ({
5171
+ name: e.name,
5172
+ dir: path2__default.default.join(sharedBase, e.name)
5173
+ })).sort((a, b) => a.name.localeCompare(b.name));
5174
+ }
5131
5175
  function extractClassMethods(content, className) {
5132
5176
  let clean = "";
5133
5177
  let inLineComment = false;
@@ -5477,16 +5521,21 @@ function lintCodebase(options = {}) {
5477
5521
  const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
5478
5522
  const failFast = !options.complete;
5479
5523
  const features = discoverAllFeatures(projectRoot);
5524
+ const sharedModules = discoverAllShared(projectRoot);
5525
+ const allModules = [
5526
+ ...features.map((f) => ({ ...f, type: "feature" })),
5527
+ ...sharedModules.map((s) => ({ ...s, type: "shared" }))
5528
+ ];
5480
5529
  const graph = new FeatureDependencyGraph();
5481
5530
  const allViolations = [];
5482
5531
  const results = [];
5483
5532
  const detectedCycles = [];
5484
5533
  const addViolation = (v) => {
5485
5534
  allViolations.push(v);
5486
- const featureResult = results.find((r) => r.featureName === v.feature);
5487
- if (featureResult) {
5488
- featureResult.violations.push(v);
5489
- featureResult.passed = false;
5535
+ const moduleResult = results.find((r) => r.featureName === v.feature);
5536
+ if (moduleResult) {
5537
+ moduleResult.violations.push(v);
5538
+ moduleResult.passed = false;
5490
5539
  }
5491
5540
  if (failFast) {
5492
5541
  const errorMsg = `Dependency rule violation detected in ${v.relativePath}:${v.line}:
@@ -5495,28 +5544,156 @@ Code: ${v.code}`;
5495
5544
  throw new DependencyRuleError(errorMsg, [v], true);
5496
5545
  }
5497
5546
  };
5498
- for (const feat of features) {
5547
+ for (const mod of allModules) {
5499
5548
  results.push({
5500
- featureName: feat.name,
5501
- featureDir: feat.dir,
5549
+ moduleType: mod.type,
5550
+ moduleName: mod.name,
5551
+ featureName: mod.name,
5552
+ featureDir: mod.dir,
5553
+ moduleDir: mod.dir,
5502
5554
  scannedFilesCount: 0,
5503
5555
  passed: true,
5504
5556
  violations: [],
5505
- layersChecked: ["domain", "useCases", "services", "infrastructure", "presentation"]
5557
+ layersChecked: mod.type === "feature" ? ["domain", "useCases", "services", "infrastructure", "presentation"] : ["domain", "ports", "infrastructure", "root"]
5506
5558
  });
5507
5559
  }
5508
- for (const feat of features) {
5509
- const featResult = results.find((r) => r.featureName === feat.name);
5510
- const allFiles = scanDirFiles(feat.dir, projectRoot);
5511
- featResult.scannedFilesCount = allFiles.length;
5560
+ for (const mod of allModules) {
5561
+ const modResult = results.find((r) => r.featureName === mod.name);
5562
+ const allFiles = scanDirFiles(mod.dir, projectRoot);
5563
+ modResult.scannedFilesCount = allFiles.length;
5512
5564
  for (const file of allFiles) {
5513
5565
  const relPath = path2__default.default.relative(projectRoot, file).replace(/\\/g, "/");
5514
- const { layer } = classifyFile(file, projectRoot);
5515
- if (!layer) continue;
5566
+ const { moduleType, moduleName, layer } = classifyFile(file, projectRoot);
5567
+ if (!layer || !moduleType || !moduleName) continue;
5516
5568
  const content = fs4__default.default.readFileSync(file, "utf-8");
5517
5569
  const statements = extractImportStatements(content);
5518
5570
  const baseName = path2__default.default.basename(file);
5519
5571
  const isBarrel = baseName === "index.ts" || baseName === "index.js";
5572
+ if (moduleType === "shared") {
5573
+ if (layer === "domain") {
5574
+ const lines = content.split(/\r?\n/);
5575
+ for (let i = 0; i < lines.length; i++) {
5576
+ const lineText = lines[i].trim();
5577
+ if (lineText.includes("@MakeInjectable") || lineText.includes("public static deps =")) {
5578
+ addViolation({
5579
+ feature: moduleName,
5580
+ layer,
5581
+ file,
5582
+ relativePath: relPath,
5583
+ line: i + 1,
5584
+ category: "Domain Dependence Violation",
5585
+ code: lineText,
5586
+ ruleId: "D2_DI_DEPENDENCE",
5587
+ severity: "error",
5588
+ advice: "Domain layer must have zero DI dependence. Remove @MakeInjectable decorators and static deps declarations.",
5589
+ fixExample: {
5590
+ bad: lineText,
5591
+ good: "// Domain interfaces and value objects must remain plain TypeScript classes/interfaces."
5592
+ }
5593
+ });
5594
+ }
5595
+ }
5596
+ }
5597
+ for (const stmt of statements) {
5598
+ const target = classifyTarget(stmt.specifier, file, projectRoot);
5599
+ if (target.targetFeature) {
5600
+ addViolation({
5601
+ feature: moduleName,
5602
+ layer,
5603
+ file,
5604
+ relativePath: relPath,
5605
+ line: stmt.line,
5606
+ category: "Shared Architecture Violation",
5607
+ code: stmt.rawCode,
5608
+ ruleId: "SF1_FEATURE_DEPENDENCY",
5609
+ severity: "error",
5610
+ advice: `Shared modules ('${moduleName}') are foundational and must NEVER import from feature modules ('${target.targetFeature}').`,
5611
+ fixExample: {
5612
+ bad: stmt.rawCode,
5613
+ good: "// Move shared logic/types to shared/ or communicate via domain events/callbacks."
5614
+ }
5615
+ });
5616
+ }
5617
+ if (target.targetSharedModule && target.targetSharedModule !== moduleName) {
5618
+ if (target.isSharedInfra || target.isSharedPorts) {
5619
+ addViolation({
5620
+ feature: moduleName,
5621
+ layer,
5622
+ file,
5623
+ relativePath: relPath,
5624
+ line: stmt.line,
5625
+ category: "Shared Architecture Violation",
5626
+ code: stmt.rawCode,
5627
+ ruleId: "SS1_SHARED_ENCAPSULATION",
5628
+ severity: "error",
5629
+ advice: `Shared module '${moduleName}' cannot import internal details (ports/infra) of shared module '${target.targetSharedModule}'. Depend only on its root classes or domain types.`,
5630
+ fixExample: {
5631
+ bad: stmt.rawCode,
5632
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
5633
+ }
5634
+ });
5635
+ }
5636
+ }
5637
+ if (layer === "domain") {
5638
+ if (target.targetSharedModule === moduleName && (target.isSharedInfra || target.isSharedPorts || target.isSharedRoot)) {
5639
+ addViolation({
5640
+ feature: moduleName,
5641
+ layer,
5642
+ file,
5643
+ relativePath: relPath,
5644
+ line: stmt.line,
5645
+ category: "Domain Purity Violation",
5646
+ code: stmt.rawCode,
5647
+ ruleId: "SD1_OUTER_IMPORT",
5648
+ severity: "error",
5649
+ advice: `Shared domain layer must not import from its own ports, infrastructure, or root classes.`,
5650
+ fixExample: {
5651
+ bad: stmt.rawCode,
5652
+ good: "// Model pure business types and value objects directly within domain/."
5653
+ }
5654
+ });
5655
+ }
5656
+ if (target.isDiFramework) {
5657
+ addViolation({
5658
+ feature: moduleName,
5659
+ layer,
5660
+ file,
5661
+ relativePath: relPath,
5662
+ line: stmt.line,
5663
+ category: "Domain Dependence Violation",
5664
+ code: stmt.rawCode,
5665
+ ruleId: "D2_DI_DEPENDENCE",
5666
+ severity: "error",
5667
+ advice: "Domain layer must have zero DI dependence. Domain interfaces and value objects are plain classes/types.",
5668
+ fixExample: {
5669
+ bad: stmt.rawCode,
5670
+ good: "// Remove @solid-stack/di import. Domain entities are pure TypeScript."
5671
+ }
5672
+ });
5673
+ }
5674
+ }
5675
+ if (layer === "ports" && target.targetSharedModule === moduleName && target.isSharedInfra) {
5676
+ addViolation({
5677
+ feature: moduleName,
5678
+ layer,
5679
+ file,
5680
+ relativePath: relPath,
5681
+ line: stmt.line,
5682
+ category: "Shared Architecture Violation",
5683
+ code: stmt.rawCode,
5684
+ ruleId: "SP1_INFRA_IN_PORT",
5685
+ severity: "error",
5686
+ advice: `Shared ports cannot depend on infrastructure. Infrastructure implements ports, not the other way around.`,
5687
+ fixExample: {
5688
+ bad: stmt.rawCode,
5689
+ good: "// Ports define abstract interfaces without concrete infrastructure dependencies."
5690
+ }
5691
+ });
5692
+ }
5693
+ }
5694
+ continue;
5695
+ }
5696
+ const feat = mod;
5520
5697
  if (layer === "useCases" && !isBarrel) {
5521
5698
  checkUseCaseShape(feat.name, file, relPath, content, addViolation);
5522
5699
  }
@@ -5585,23 +5762,33 @@ Code: ${v.code}`;
5585
5762
  }
5586
5763
  });
5587
5764
  }
5588
- if (target.targetLayer === "domain" && !target.isTargetPort) {
5589
- addViolation({
5590
- feature: feat.name,
5591
- layer,
5592
- file,
5593
- relativePath: relPath,
5594
- line: stmt.line,
5595
- category: "Cross-Feature Domain Violation",
5596
- code: stmt.rawCode,
5597
- ruleId: "F1_CROSS_FEATURE_DOMAIN",
5598
- severity: "error",
5599
- advice: `Cannot import domain entities/factories directly from foreign feature '${target.targetFeature}'. This bypasses its use cases and breaks Bounded Context encapsulation. Communicate via ports instead.`,
5600
- fixExample: {
5601
- bad: stmt.rawCode,
5602
- good: `// Define a Port in ${feat.name}/domain/ and implement the cross-feature communication in ${feat.name}/infrastructure/ (which can then call ${target.targetFeature}'s Use Case).`
5765
+ if (target.targetLayer === "domain" && !target.isTargetPort && !target.isTargetError && !stmt.isTypeOnly) {
5766
+ for (const name of stmt.importedNames) {
5767
+ const createRegex = new RegExp(`\\b${name}\\.create\\s*\\(`, "g");
5768
+ const newRegex = new RegExp(`\\bnew\\s+${name}\\b`, "g");
5769
+ const execMatch = createRegex.exec(content) || newRegex.exec(content);
5770
+ if (execMatch) {
5771
+ const charIndex = execMatch.index;
5772
+ const lineNum = content.substring(0, charIndex).split("\n").length;
5773
+ const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || stmt.rawCode;
5774
+ addViolation({
5775
+ feature: feat.name,
5776
+ layer,
5777
+ file,
5778
+ relativePath: relPath,
5779
+ line: lineNum,
5780
+ category: "Cross-Feature Domain Violation",
5781
+ code: lineCode,
5782
+ ruleId: "F1_CROSS_FEATURE_DOMAIN",
5783
+ severity: "error",
5784
+ advice: `Cannot instantiate domain entities/factories directly from foreign feature '${target.targetFeature}' (found instantiation of '${name}'). This bypasses its use cases and breaks Bounded Context encapsulation. Importing for types or 'instanceof' checks is allowed.`,
5785
+ fixExample: {
5786
+ bad: lineCode,
5787
+ good: `// Define a Port in ${feat.name}/domain/ and implement the cross-feature communication in ${feat.name}/infrastructure/.`
5788
+ }
5789
+ });
5603
5790
  }
5604
- });
5791
+ }
5605
5792
  }
5606
5793
  }
5607
5794
  if (target.isGlobalConfig) {
@@ -5989,23 +6176,33 @@ Code: ${v.code}`;
5989
6176
  }
5990
6177
  });
5991
6178
  }
5992
- if (target.targetLayer === "domain" && !target.isTargetPort && !target.isTargetRepo) {
5993
- addViolation({
5994
- feature: feat.name,
5995
- layer,
5996
- file,
5997
- relativePath: relPath,
5998
- line: stmt.line,
5999
- category: "Presentation Layer Violation",
6000
- code: stmt.rawCode,
6001
- ruleId: "P5_DIRECT_DOMAIN_ENTITY",
6002
- severity: "error",
6003
- advice: "Presentation handlers must not directly instantiate or import domain entities/factories. They must map requests to flat DTOs and pass them to the Use Case.",
6004
- fixExample: {
6005
- bad: stmt.rawCode,
6006
- good: "// Construct a DTO and pass it into the injected Use Case's execute() method."
6179
+ if (target.targetLayer === "domain" && !target.isTargetPort && !target.isTargetRepo && !target.isTargetError && !stmt.isTypeOnly) {
6180
+ for (const name of stmt.importedNames) {
6181
+ const createRegex = new RegExp(`\\b${name}\\.create\\s*\\(`, "g");
6182
+ const newRegex = new RegExp(`\\bnew\\s+${name}\\b`, "g");
6183
+ const execMatch = createRegex.exec(content) || newRegex.exec(content);
6184
+ if (execMatch) {
6185
+ const charIndex = execMatch.index;
6186
+ const lineNum = content.substring(0, charIndex).split("\n").length;
6187
+ const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || stmt.rawCode;
6188
+ addViolation({
6189
+ feature: feat.name,
6190
+ layer,
6191
+ file,
6192
+ relativePath: relPath,
6193
+ line: lineNum,
6194
+ category: "Presentation Layer Violation",
6195
+ code: lineCode,
6196
+ ruleId: "P5_DIRECT_DOMAIN_ENTITY",
6197
+ severity: "error",
6198
+ advice: `Presentation handlers must not directly instantiate domain entities (found instantiation of '${name}'). They must map requests to flat DTOs and pass them to the Use Case. Importing for types or 'instanceof' checks is allowed.`,
6199
+ fixExample: {
6200
+ bad: lineCode,
6201
+ good: "// Construct a plain DTO object and pass it into the injected Use Case's execute() method."
6202
+ }
6203
+ });
6007
6204
  }
6008
- });
6205
+ }
6009
6206
  }
6010
6207
  }
6011
6208
  }