pumuki 6.3.121 → 6.3.122

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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.122] - 2026-04-28
10
+
11
+ ### Fixed
12
+
13
+ - **Skills estructurales sin umbrales hardcodeados:** `skills.backend.no-god-classes` / `skills.frontend.no-god-classes` dejan de depender de `GOD_CLASS_MAX_LINES` y pasan a detectar mezcla semántica de responsabilidades por nodos AST.
14
+ - **Auditoría transversal iOS/Android/backend/frontend:** las skills estructurales `God/Massive/SRP/Clean Architecture` ya no expresan límites implícitos de líneas; una regresión dedicada falla si vuelven a aparecer umbrales `> N líneas` en las skills de las cuatro plataformas.
15
+ - **Cierre de `PUMUKI-INC-115`:** Pumuki mantiene hotspots por `max_lines` solo cuando el consumer los declara explícitamente, pero las skills hard vuelven a depender de nodos AST inteligentes o reglas declarativas.
16
+
9
17
  ## [6.3.121] - 2026-04-28
10
18
 
11
19
  ### Fixed
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.121
1
+ v6.3.122
@@ -856,32 +856,86 @@ test('findConcreteDependencyInstantiationMatch devuelve payload semantico para D
856
856
  assert.match(match.expected_fix, /adapter|puerto|abstracci/i);
857
857
  });
858
858
 
859
- test('hasLargeClassDeclaration detecta clases con 300 lineas o mas', () => {
860
- const oversizedClassAst = {
859
+ test('hasLargeClassDeclaration detecta god class por mezcla semantica de responsabilidades', () => {
860
+ const godClassAst = {
861
861
  type: 'ClassDeclaration',
862
862
  loc: {
863
- start: { line: 10 },
864
- end: { line: 320 },
863
+ start: { line: 1 },
864
+ end: { line: 80 },
865
+ },
866
+ body: {
867
+ type: 'ClassBody',
868
+ body: [
869
+ {
870
+ type: 'ClassProperty',
871
+ key: { type: 'Identifier', name: 'client' },
872
+ value: {
873
+ type: 'NewExpression',
874
+ callee: { type: 'Identifier', name: 'PrismaClient' },
875
+ arguments: [],
876
+ },
877
+ },
878
+ {
879
+ type: 'ClassMethod',
880
+ key: { type: 'Identifier', name: 'getOrder' },
881
+ loc: { start: { line: 20 }, end: { line: 24 } },
882
+ body: { type: 'BlockStatement', body: [] },
883
+ },
884
+ {
885
+ type: 'ClassMethod',
886
+ key: { type: 'Identifier', name: 'saveOrder' },
887
+ loc: { start: { line: 30 }, end: { line: 40 } },
888
+ body: { type: 'BlockStatement', body: [] },
889
+ },
890
+ ],
865
891
  },
866
892
  };
867
- const thresholdClassAst = {
893
+ const oversizedButSingleResponsibilityAst = {
868
894
  type: 'ClassDeclaration',
869
895
  loc: {
870
- start: { line: 10 },
871
- end: { line: 309 },
896
+ start: { line: 1 },
897
+ end: { line: 1_000 },
898
+ },
899
+ body: {
900
+ type: 'ClassBody',
901
+ body: [
902
+ {
903
+ type: 'ClassMethod',
904
+ key: { type: 'Identifier', name: 'getOrder' },
905
+ loc: { start: { line: 20 }, end: { line: 24 } },
906
+ body: { type: 'BlockStatement', body: [] },
907
+ },
908
+ ],
872
909
  },
873
910
  };
874
- const compactClassAst = {
911
+ const srpOnlyAst = {
875
912
  type: 'ClassDeclaration',
876
913
  loc: {
877
- start: { line: 10 },
914
+ start: { line: 1 },
878
915
  end: { line: 80 },
879
916
  },
917
+ body: {
918
+ type: 'ClassBody',
919
+ body: [
920
+ {
921
+ type: 'ClassMethod',
922
+ key: { type: 'Identifier', name: 'getOrder' },
923
+ loc: { start: { line: 20 }, end: { line: 24 } },
924
+ body: { type: 'BlockStatement', body: [] },
925
+ },
926
+ {
927
+ type: 'ClassMethod',
928
+ key: { type: 'Identifier', name: 'saveOrder' },
929
+ loc: { start: { line: 30 }, end: { line: 40 } },
930
+ body: { type: 'BlockStatement', body: [] },
931
+ },
932
+ ],
933
+ },
880
934
  };
881
935
 
882
- assert.equal(hasLargeClassDeclaration(oversizedClassAst), true);
883
- assert.equal(hasLargeClassDeclaration(thresholdClassAst), true);
884
- assert.equal(hasLargeClassDeclaration(compactClassAst), false);
936
+ assert.equal(hasLargeClassDeclaration(godClassAst), true);
937
+ assert.equal(hasLargeClassDeclaration(oversizedButSingleResponsibilityAst), false);
938
+ assert.equal(hasLargeClassDeclaration(srpOnlyAst), false);
885
939
  });
886
940
 
887
941
  test('hasRecordStringUnknownType detecta Record<string, unknown>', () => {
@@ -19,7 +19,6 @@ const concreteDependencyNames = new Set<string>([
19
19
  'ApolloClient',
20
20
  'Axios',
21
21
  ]);
22
- const GOD_CLASS_MAX_LINES = 300;
23
22
  const networkCallCalleePattern = /^(fetch|axios|get|post|put|patch|delete|request)$/i;
24
23
  type AstNode = Record<string, string | number | boolean | bigint | symbol | null | Date | object>;
25
24
  type TypeScriptSemanticNode = {
@@ -1747,16 +1746,19 @@ export const findConcreteDependencyInstantiationMatch = (
1747
1746
  return buildSolidDipMatch(node, 'concrete-instantiation');
1748
1747
  };
1749
1748
 
1750
- const nodeLineSpan = (node: unknown): number => {
1751
- if (!isObject(node) || !isObject(node.loc)) {
1752
- return 0;
1753
- }
1754
- const start = isObject(node.loc.start) ? node.loc.start.line : undefined;
1755
- const end = isObject(node.loc.end) ? node.loc.end.line : undefined;
1756
- if (typeof start !== 'number' || typeof end !== 'number') {
1757
- return 0;
1758
- }
1759
- return Math.max(0, end - start + 1);
1749
+ const hasSemanticGodClassResponsibilities = (classNode: AstNode): boolean => {
1750
+ const mixesCommandsAndQueries = typeof buildMixedCommandQueryClassMatch(classNode) !== 'undefined';
1751
+ const ownsConcreteInfrastructure = hasConcreteDependencyInstantiation(classNode);
1752
+ const ownsTypeBranching = hasTypeDiscriminatorSwitch(classNode);
1753
+ const weakensBaseContract = hasOverrideMethodThrowingNotImplemented(classNode);
1754
+
1755
+ return (
1756
+ (mixesCommandsAndQueries && ownsConcreteInfrastructure) ||
1757
+ (mixesCommandsAndQueries && ownsTypeBranching) ||
1758
+ (mixesCommandsAndQueries && weakensBaseContract) ||
1759
+ (ownsConcreteInfrastructure && ownsTypeBranching) ||
1760
+ (ownsConcreteInfrastructure && weakensBaseContract)
1761
+ );
1760
1762
  };
1761
1763
 
1762
1764
  export const hasLargeClassDeclaration = (node: unknown): boolean => {
@@ -1764,7 +1766,7 @@ export const hasLargeClassDeclaration = (node: unknown): boolean => {
1764
1766
  if (value.type !== 'ClassDeclaration' && value.type !== 'ClassExpression') {
1765
1767
  return false;
1766
1768
  }
1767
- return nodeLineSpan(value) >= GOD_CLASS_MAX_LINES;
1769
+ return hasSemanticGodClassResponsibilities(value);
1768
1770
  });
1769
1771
  };
1770
1772
 
@@ -410,7 +410,7 @@ const astDetectorRegistry: ReadonlyArray<ASTDetectorRegistryEntry> = [
410
410
  { detect: TS.hasOverrideMethodThrowingNotImplemented, ruleId: 'heuristics.ts.solid.lsp.override-not-implemented.ast', code: 'HEURISTICS_SOLID_LSP_OVERRIDE_NOT_IMPLEMENTED_AST', message: 'AST heuristic detected LSP risk: override throws not-implemented/unsupported.' },
411
411
  { detect: TS.hasFrameworkDependencyImport, ruleId: 'heuristics.ts.solid.dip.framework-import.ast', code: 'HEURISTICS_SOLID_DIP_FRAMEWORK_IMPORT_AST', message: 'AST heuristic detected DIP risk: framework dependency imported in domain/application code.', pathCheck: isTypeScriptDomainOrApplicationPath },
412
412
  { detect: TS.hasConcreteDependencyInstantiation, ruleId: 'heuristics.ts.solid.dip.concrete-instantiation.ast', code: 'HEURISTICS_SOLID_DIP_CONCRETE_INSTANTIATION_AST', message: 'AST heuristic detected DIP risk: direct instantiation of concrete framework dependency.', pathCheck: isTypeScriptDomainOrApplicationPath },
413
- { detect: TS.hasLargeClassDeclaration, ruleId: 'heuristics.ts.god-class-large-class.ast', code: 'HEURISTICS_GOD_CLASS_LARGE_CLASS_AST', message: 'AST heuristic detected God Class candidate (>=300 lines in a single class declaration).' },
413
+ { detect: TS.hasLargeClassDeclaration, ruleId: 'heuristics.ts.god-class-large-class.ast', code: 'HEURISTICS_GOD_CLASS_LARGE_CLASS_AST', message: 'AST heuristic detected God Class candidate by mixed responsibility nodes in a single class declaration.' },
414
414
  { detect: TS.hasRecordStringUnknownType, locateLines: TS.findRecordStringUnknownTypeLines, ruleId: 'common.types.record_unknown_requires_type', code: 'COMMON_TYPES_RECORD_UNKNOWN_REQUIRES_TYPE_AST', message: 'AST heuristic detected Record<string, unknown> without explicit value union.' },
415
415
  { detect: TS.hasUnknownWithoutGuard, locateLines: TS.findUnknownWithoutGuardLines, ruleId: 'common.types.unknown_without_guard', code: 'COMMON_TYPES_UNKNOWN_WITHOUT_GUARD_AST', message: 'AST heuristic detected unknown usage without explicit guard evidence.', pathCheck: isTypeScriptDomainOrApplicationPath },
416
416
  { detect: TS.hasUndefinedInBaseTypeUnion, locateLines: TS.findUndefinedInBaseTypeUnionLines, ruleId: 'common.types.undefined_in_base_type', code: 'COMMON_TYPES_UNDEFINED_IN_BASE_TYPE_AST', message: 'AST heuristic detected undefined inside base-type unions.' },
@@ -332,7 +332,7 @@ export const typescriptRules: RuleSet = [
332
332
  },
333
333
  {
334
334
  id: 'heuristics.ts.god-class-large-class.ast',
335
- description: 'Detects God Class candidates when a class declaration exceeds 500 lines.',
335
+ description: 'Detects God Class candidates when one class mixes multiple responsibility nodes.',
336
336
  severity: 'ERROR',
337
337
  platform: 'generic',
338
338
  locked: true,
@@ -344,7 +344,7 @@ export const typescriptRules: RuleSet = [
344
344
  },
345
345
  then: {
346
346
  kind: 'Finding',
347
- message: 'AST heuristic detected God Class candidate (>500 lines in one class declaration).',
347
+ message: 'AST heuristic detected God Class candidate by mixed responsibility nodes in one class declaration.',
348
348
  code: 'HEURISTICS_GOD_CLASS_LARGE_CLASS_AST',
349
349
  },
350
350
  },
@@ -249,7 +249,7 @@ const { data } = await supabase
249
249
  ✅ **i18n en error messages** - Mensajes traducibles
250
250
 
251
251
  ### Anti-patterns a EVITAR:
252
- ❌ **God classes** - Servicios con >500 líneas
252
+ ❌ **God classes** - Servicios que mezclan responsabilidades de dominio, aplicación, infraestructura, branching de tipos o contratos en una misma clase
253
253
  ❌ **Anemic domain models** - Entidades solo con getters/setters
254
254
  ❌ **Magic numbers** - Usar constantes con nombres descriptivos
255
255
  ❌ **Callback hell** - Usar async/await
@@ -806,7 +806,7 @@ final class APIClientSpy: @unchecked Sendable, APIClientProtocol {
806
806
  - Usar `@preconcurrency` solo como medida temporal de migración
807
807
 
808
808
  ### Anti-patterns a EVITAR:
809
- ❌ **Massive View Controllers** - ViewControllers >300 líneas
809
+ ❌ **Massive View Controllers** - ViewControllers que mezclan presentación, navegación, estado, acceso a datos o coordinación de infraestructura
810
810
  ❌ **Force unwrapping (!)** - Salvo IBOutlets y casos justificados
811
811
  ❌ **Singletons** - Dificultan testing
812
812
  ❌ **Storyboards grandes** - Merge conflicts, lentitud
@@ -446,13 +446,7 @@ const normalizeKnownRuleTarget = (
446
446
  if (includes('clean architecture')) {
447
447
  return `${prefix}.enforce-clean-architecture`;
448
448
  }
449
- if (
450
- includes('god classes') ||
451
- includes('god class') ||
452
- includes('500 lineas') ||
453
- includes('500 li neas') ||
454
- includes('500 lines')
455
- ) {
449
+ if (includes('god classes') || includes('god class')) {
456
450
  return `${prefix}.no-god-classes`;
457
451
  }
458
452
  if (includes('empty catch')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.121",
3
+ "version": "6.3.122",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -249,7 +249,7 @@ const { data } = await supabase
249
249
  ✅ **i18n en error messages** - Mensajes traducibles
250
250
 
251
251
  ### Anti-patterns a EVITAR:
252
- ❌ **God classes** - Servicios con >500 líneas
252
+ ❌ **God classes** - Servicios que mezclan responsabilidades de dominio, aplicación, infraestructura, branching de tipos o contratos en una misma clase
253
253
  ❌ **Anemic domain models** - Entidades solo con getters/setters
254
254
  ❌ **Magic numbers** - Usar constantes con nombres descriptivos
255
255
  ❌ **Callback hell** - Usar async/await
@@ -806,7 +806,7 @@ final class APIClientSpy: @unchecked Sendable, APIClientProtocol {
806
806
  - Usar `@preconcurrency` solo como medida temporal de migración
807
807
 
808
808
  ### Anti-patterns a EVITAR:
809
- ❌ **Massive View Controllers** - ViewControllers >300 líneas
809
+ ❌ **Massive View Controllers** - ViewControllers que mezclan presentación, navegación, estado, acceso a datos o coordinación de infraestructura
810
810
  ❌ **Force unwrapping (!)** - Salvo IBOutlets y casos justificados
811
811
  ❌ **Singletons** - Dificultan testing
812
812
  ❌ **Storyboards grandes** - Merge conflicts, lentitud