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 +8 -0
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +66 -12
- package/core/facts/detectors/typescript/index.ts +14 -12
- package/core/facts/extractHeuristicFacts.ts +1 -1
- package/core/rules/presets/heuristics/typescript.ts +2 -2
- package/docs/codex-skills/backend-enterprise-rules.md +1 -1
- package/docs/codex-skills/ios-enterprise-rules.md +1 -1
- package/integrations/config/skillsMarkdownRules.ts +1 -7
- package/package.json +1 -1
- package/vendor/skills/backend-enterprise-rules/SKILL.md +1 -1
- package/vendor/skills/ios-enterprise-rules/SKILL.md +1 -1
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.
|
|
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
|
|
860
|
-
const
|
|
859
|
+
test('hasLargeClassDeclaration detecta god class por mezcla semantica de responsabilidades', () => {
|
|
860
|
+
const godClassAst = {
|
|
861
861
|
type: 'ClassDeclaration',
|
|
862
862
|
loc: {
|
|
863
|
-
start: { line:
|
|
864
|
-
end: { line:
|
|
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
|
|
893
|
+
const oversizedButSingleResponsibilityAst = {
|
|
868
894
|
type: 'ClassDeclaration',
|
|
869
895
|
loc: {
|
|
870
|
-
start: { line:
|
|
871
|
-
end: { line:
|
|
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
|
|
911
|
+
const srpOnlyAst = {
|
|
875
912
|
type: 'ClassDeclaration',
|
|
876
913
|
loc: {
|
|
877
|
-
start: { line:
|
|
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(
|
|
883
|
-
assert.equal(hasLargeClassDeclaration(
|
|
884
|
-
assert.equal(hasLargeClassDeclaration(
|
|
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
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|