@girardelli/architect-core 8.1.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.
- package/dist/src/core/analyzer.d.ts +42 -0
- package/dist/src/core/analyzer.js +431 -0
- package/dist/src/core/analyzer.js.map +1 -0
- package/dist/src/core/analyzers/forecast.d.ts +84 -0
- package/dist/src/core/analyzers/forecast.js +338 -0
- package/dist/src/core/analyzers/forecast.js.map +1 -0
- package/dist/src/core/analyzers/index.d.ts +9 -0
- package/dist/src/core/analyzers/index.js +7 -0
- package/dist/src/core/analyzers/index.js.map +1 -0
- package/dist/src/core/analyzers/temporal-scorer.d.ts +71 -0
- package/dist/src/core/analyzers/temporal-scorer.js +141 -0
- package/dist/src/core/analyzers/temporal-scorer.js.map +1 -0
- package/dist/src/core/anti-patterns.d.ts +28 -0
- package/dist/src/core/anti-patterns.js +264 -0
- package/dist/src/core/anti-patterns.js.map +1 -0
- package/dist/src/core/ast/ast-parser.interface.d.ts +20 -0
- package/dist/src/core/ast/ast-parser.interface.js +2 -0
- package/dist/src/core/ast/ast-parser.interface.js.map +1 -0
- package/dist/src/core/ast/path-resolver.d.ts +13 -0
- package/dist/src/core/ast/path-resolver.js +54 -0
- package/dist/src/core/ast/path-resolver.js.map +1 -0
- package/dist/src/core/ast/tree-sitter-parser.d.ts +10 -0
- package/dist/src/core/ast/tree-sitter-parser.js +142 -0
- package/dist/src/core/ast/tree-sitter-parser.js.map +1 -0
- package/dist/src/core/config.d.ts +11 -0
- package/dist/src/core/config.js +112 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/diagram.d.ts +9 -0
- package/dist/src/core/diagram.js +101 -0
- package/dist/src/core/diagram.js.map +1 -0
- package/dist/src/core/i18n.d.ts +14 -0
- package/dist/src/core/i18n.js +54 -0
- package/dist/src/core/i18n.js.map +1 -0
- package/dist/src/core/locales/en.d.ts +2 -0
- package/dist/src/core/locales/en.js +337 -0
- package/dist/src/core/locales/en.js.map +1 -0
- package/dist/src/core/locales/pt-BR.d.ts +172 -0
- package/dist/src/core/locales/pt-BR.js +337 -0
- package/dist/src/core/locales/pt-BR.js.map +1 -0
- package/dist/src/core/locales/types.d.ts +86 -0
- package/dist/src/core/locales/types.js +2 -0
- package/dist/src/core/locales/types.js.map +1 -0
- package/dist/src/core/plugin-loader.d.ts +11 -0
- package/dist/src/core/plugin-loader.js +67 -0
- package/dist/src/core/plugin-loader.js.map +1 -0
- package/dist/src/core/project-summarizer.d.ts +16 -0
- package/dist/src/core/project-summarizer.js +37 -0
- package/dist/src/core/project-summarizer.js.map +1 -0
- package/dist/src/core/refactor-engine.d.ts +18 -0
- package/dist/src/core/refactor-engine.js +87 -0
- package/dist/src/core/refactor-engine.js.map +1 -0
- package/dist/src/core/rules/barrel-optimizer.d.ts +13 -0
- package/dist/src/core/rules/barrel-optimizer.js +76 -0
- package/dist/src/core/rules/barrel-optimizer.js.map +1 -0
- package/dist/src/core/rules/dead-code-detector.d.ts +21 -0
- package/dist/src/core/rules/dead-code-detector.js +116 -0
- package/dist/src/core/rules/dead-code-detector.js.map +1 -0
- package/dist/src/core/rules/hub-splitter.d.ts +13 -0
- package/dist/src/core/rules/hub-splitter.js +117 -0
- package/dist/src/core/rules/hub-splitter.js.map +1 -0
- package/dist/src/core/rules/import-organizer.d.ts +13 -0
- package/dist/src/core/rules/import-organizer.js +84 -0
- package/dist/src/core/rules/import-organizer.js.map +1 -0
- package/dist/src/core/rules/module-grouper.d.ts +13 -0
- package/dist/src/core/rules/module-grouper.js +116 -0
- package/dist/src/core/rules/module-grouper.js.map +1 -0
- package/dist/src/core/rules-engine.d.ts +7 -0
- package/dist/src/core/rules-engine.js +89 -0
- package/dist/src/core/rules-engine.js.map +1 -0
- package/dist/src/core/scorer.d.ts +15 -0
- package/dist/src/core/scorer.js +165 -0
- package/dist/src/core/scorer.js.map +1 -0
- package/dist/src/core/summarizer/keyword-extractor.d.ts +6 -0
- package/dist/src/core/summarizer/keyword-extractor.js +38 -0
- package/dist/src/core/summarizer/keyword-extractor.js.map +1 -0
- package/dist/src/core/summarizer/module-inferrer.d.ts +11 -0
- package/dist/src/core/summarizer/module-inferrer.js +171 -0
- package/dist/src/core/summarizer/module-inferrer.js.map +1 -0
- package/dist/src/core/summarizer/package-reader.d.ts +3 -0
- package/dist/src/core/summarizer/package-reader.js +33 -0
- package/dist/src/core/summarizer/package-reader.js.map +1 -0
- package/dist/src/core/summarizer/purpose-inferrer.d.ts +8 -0
- package/dist/src/core/summarizer/purpose-inferrer.js +179 -0
- package/dist/src/core/summarizer/purpose-inferrer.js.map +1 -0
- package/dist/src/core/summarizer/readme-reader.d.ts +3 -0
- package/dist/src/core/summarizer/readme-reader.js +24 -0
- package/dist/src/core/summarizer/readme-reader.js.map +1 -0
- package/dist/src/core/types/architect-rules.d.ts +27 -0
- package/dist/src/core/types/architect-rules.js +2 -0
- package/dist/src/core/types/architect-rules.js.map +1 -0
- package/dist/src/core/types/core.d.ts +87 -0
- package/dist/src/core/types/core.js +2 -0
- package/dist/src/core/types/core.js.map +1 -0
- package/dist/src/core/types/infrastructure.d.ts +38 -0
- package/dist/src/core/types/infrastructure.js +2 -0
- package/dist/src/core/types/infrastructure.js.map +1 -0
- package/dist/src/core/types/plugin.d.ts +12 -0
- package/dist/src/core/types/plugin.js +2 -0
- package/dist/src/core/types/plugin.js.map +1 -0
- package/dist/src/core/types/rules.d.ts +53 -0
- package/dist/src/core/types/rules.js +2 -0
- package/dist/src/core/types/rules.js.map +1 -0
- package/dist/src/core/types/summarizer.d.ts +12 -0
- package/dist/src/core/types/summarizer.js +2 -0
- package/dist/src/core/types/summarizer.js.map +1 -0
- package/dist/src/infrastructure/git-cache.d.ts +6 -0
- package/dist/src/infrastructure/git-cache.js +41 -0
- package/dist/src/infrastructure/git-cache.js.map +1 -0
- package/dist/src/infrastructure/git-history.d.ts +112 -0
- package/dist/src/infrastructure/git-history.js +340 -0
- package/dist/src/infrastructure/git-history.js.map +1 -0
- package/dist/src/infrastructure/logger.d.ts +20 -0
- package/dist/src/infrastructure/logger.js +57 -0
- package/dist/src/infrastructure/logger.js.map +1 -0
- package/dist/src/infrastructure/scanner.d.ts +31 -0
- package/dist/src/infrastructure/scanner.js +334 -0
- package/dist/src/infrastructure/scanner.js.map +1 -0
- package/dist/tests/analyzers-integration.test.d.ts +7 -0
- package/dist/tests/analyzers-integration.test.js +140 -0
- package/dist/tests/analyzers-integration.test.js.map +1 -0
- package/dist/tests/anti-patterns.test.d.ts +1 -0
- package/dist/tests/anti-patterns.test.js +81 -0
- package/dist/tests/anti-patterns.test.js.map +1 -0
- package/dist/tests/ast-parser.test.d.ts +1 -0
- package/dist/tests/ast-parser.test.js +94 -0
- package/dist/tests/ast-parser.test.js.map +1 -0
- package/dist/tests/fixtures/monorepo/packages/app/src/index.d.ts +1 -0
- package/dist/tests/fixtures/monorepo/packages/app/src/index.js +9 -0
- package/dist/tests/fixtures/monorepo/packages/app/src/index.js.map +1 -0
- package/dist/tests/fixtures/monorepo/packages/core/src/index.d.ts +2 -0
- package/dist/tests/fixtures/monorepo/packages/core/src/index.js +11 -0
- package/dist/tests/fixtures/monorepo/packages/core/src/index.js.map +1 -0
- package/dist/tests/forecast.test.d.ts +7 -0
- package/dist/tests/forecast.test.js +380 -0
- package/dist/tests/forecast.test.js.map +1 -0
- package/dist/tests/git-history.test.d.ts +7 -0
- package/dist/tests/git-history.test.js +193 -0
- package/dist/tests/git-history.test.js.map +1 -0
- package/dist/tests/i18n.test.d.ts +1 -0
- package/dist/tests/i18n.test.js +39 -0
- package/dist/tests/i18n.test.js.map +1 -0
- package/dist/tests/monorepo-scan.test.d.ts +11 -0
- package/dist/tests/monorepo-scan.test.js +143 -0
- package/dist/tests/monorepo-scan.test.js.map +1 -0
- package/dist/tests/plugin-loader.test.d.ts +1 -0
- package/dist/tests/plugin-loader.test.js +31 -0
- package/dist/tests/plugin-loader.test.js.map +1 -0
- package/dist/tests/rules-engine.test.d.ts +1 -0
- package/dist/tests/rules-engine.test.js +112 -0
- package/dist/tests/rules-engine.test.js.map +1 -0
- package/dist/tests/scanner.test.d.ts +1 -0
- package/dist/tests/scanner.test.js +44 -0
- package/dist/tests/scanner.test.js.map +1 -0
- package/dist/tests/scorer.test.d.ts +1 -0
- package/dist/tests/scorer.test.js +610 -0
- package/dist/tests/scorer.test.js.map +1 -0
- package/dist/tests/temporal-scorer.test.d.ts +7 -0
- package/dist/tests/temporal-scorer.test.js +239 -0
- package/dist/tests/temporal-scorer.test.js.map +1 -0
- package/package.json +29 -0
- package/src/core/analyzer.ts +499 -0
- package/src/core/analyzers/forecast.ts +497 -0
- package/src/core/analyzers/index.ts +33 -0
- package/src/core/analyzers/temporal-scorer.ts +227 -0
- package/src/core/anti-patterns.ts +324 -0
- package/src/core/ast/ast-parser.interface.ts +21 -0
- package/src/core/ast/path-resolver.ts +61 -0
- package/src/core/ast/tree-sitter-parser.ts +158 -0
- package/src/core/config.ts +125 -0
- package/src/core/diagram.ts +129 -0
- package/src/core/i18n.ts +64 -0
- package/src/core/locales/en.ts +340 -0
- package/src/core/locales/pt-BR.ts +341 -0
- package/src/core/locales/types.ts +95 -0
- package/src/core/plugin-loader.ts +80 -0
- package/src/core/project-summarizer.ts +42 -0
- package/src/core/refactor-engine.ts +112 -0
- package/src/core/rules/barrel-optimizer.ts +99 -0
- package/src/core/rules/dead-code-detector.ts +134 -0
- package/src/core/rules/hub-splitter.ts +135 -0
- package/src/core/rules/import-organizer.ts +100 -0
- package/src/core/rules/module-grouper.ts +133 -0
- package/src/core/rules-engine.ts +100 -0
- package/src/core/scorer.ts +181 -0
- package/src/core/summarizer/keyword-extractor.ts +53 -0
- package/src/core/summarizer/module-inferrer.ts +194 -0
- package/src/core/summarizer/package-reader.ts +34 -0
- package/src/core/summarizer/purpose-inferrer.ts +197 -0
- package/src/core/summarizer/readme-reader.ts +24 -0
- package/src/core/types/architect-rules.ts +29 -0
- package/src/core/types/core.ts +94 -0
- package/src/core/types/infrastructure.ts +41 -0
- package/src/core/types/plugin.ts +19 -0
- package/src/core/types/rules.ts +51 -0
- package/src/core/types/summarizer.ts +8 -0
- package/src/infrastructure/git-cache.ts +52 -0
- package/src/infrastructure/git-history.ts +496 -0
- package/src/infrastructure/logger.ts +68 -0
- package/src/infrastructure/scanner.ts +349 -0
- package/tests/analyzers-integration.test.ts +174 -0
- package/tests/anti-patterns.test.ts +95 -0
- package/tests/ast-parser.test.ts +102 -0
- package/tests/fixtures/monorepo/package.json +6 -0
- package/tests/fixtures/monorepo/packages/app/package.json +12 -0
- package/tests/fixtures/monorepo/packages/app/src/index.ts +6 -0
- package/tests/fixtures/monorepo/packages/core/package.json +7 -0
- package/tests/fixtures/monorepo/packages/core/src/index.ts +7 -0
- package/tests/forecast.test.ts +504 -0
- package/tests/git-history.test.ts +254 -0
- package/tests/i18n.test.ts +47 -0
- package/tests/monorepo-scan.test.ts +170 -0
- package/tests/plugin-loader.test.ts +40 -0
- package/tests/rules-engine.test.ts +131 -0
- package/tests/scanner.test.ts +54 -0
- package/tests/scorer.test.ts +675 -0
- package/tests/temporal-scorer.test.ts +306 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { ASTParser } from './ast-parser.interface.js';
|
|
3
|
+
|
|
4
|
+
export class TreeSitterParser implements ASTParser {
|
|
5
|
+
private parsers: Map<string, any> = new Map();
|
|
6
|
+
private queries: Map<string, any> = new Map();
|
|
7
|
+
|
|
8
|
+
// Store the classes dynamically to avoid static C++ addon evaluation leaks in Jest ESM VMs
|
|
9
|
+
private ParserClass: any = null;
|
|
10
|
+
private QueryClass: any = null;
|
|
11
|
+
|
|
12
|
+
async initialize(): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
// Defer loading the native C++ bindings until absolutely needed.
|
|
15
|
+
// This prevents "Cannot read properties of undefined (reading 'tree')"
|
|
16
|
+
// when Jest loads the file across multiple test suites concurrently.
|
|
17
|
+
const treeSitterCore = await import('tree-sitter');
|
|
18
|
+
this.ParserClass = treeSitterCore.default;
|
|
19
|
+
this.QueryClass = treeSitterCore.Query;
|
|
20
|
+
|
|
21
|
+
// Dynamic ESM imports to handle native bindings gracefully
|
|
22
|
+
const tsMod = await import('tree-sitter-typescript').catch(() => null);
|
|
23
|
+
const jsMod = await import('tree-sitter-javascript').catch(() => null);
|
|
24
|
+
const pyMod = await import('tree-sitter-python').catch(() => null);
|
|
25
|
+
const goMod = await import('tree-sitter-go').catch(() => null);
|
|
26
|
+
const javaMod = await import('tree-sitter-java').catch(() => null);
|
|
27
|
+
const rustMod = await import('tree-sitter-rust').catch(() => null);
|
|
28
|
+
|
|
29
|
+
if (tsMod) {
|
|
30
|
+
// Handle CJS vs ESM interop structures
|
|
31
|
+
const tsLang = tsMod.default?.typescript || tsMod.typescript || tsMod;
|
|
32
|
+
const tsxLang = tsMod.default?.tsx || tsMod.tsx;
|
|
33
|
+
this.initParser('.ts', tsLang);
|
|
34
|
+
this.initParser('.tsx', tsxLang);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (jsMod) {
|
|
38
|
+
const jsLang = jsMod.default || jsMod;
|
|
39
|
+
this.initParser('.js', jsLang);
|
|
40
|
+
this.initParser('.jsx', jsLang);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (pyMod) {
|
|
44
|
+
this.initParser('.py', pyMod.default || pyMod);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (goMod) {
|
|
48
|
+
this.initParser('.go', goMod.default || goMod);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (javaMod) {
|
|
52
|
+
this.initParser('.java', javaMod.default || javaMod);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (rustMod) {
|
|
56
|
+
this.initParser('.rs', rustMod.default || rustMod);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (this.parsers.size === 0) {
|
|
60
|
+
throw new Error('No Tree-Sitter grammars were successfully loaded.');
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw new Error(`AST Parsers failed to initialize: ${(err as Error).message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private initParser(ext: string, languageOption: any): void {
|
|
68
|
+
if (!languageOption || !this.ParserClass) return;
|
|
69
|
+
try {
|
|
70
|
+
const parser = new this.ParserClass();
|
|
71
|
+
parser.setLanguage(languageOption);
|
|
72
|
+
this.parsers.set(ext, parser);
|
|
73
|
+
|
|
74
|
+
// Pre-compile queries for performance based on language
|
|
75
|
+
let queryStr = '';
|
|
76
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx') {
|
|
77
|
+
// Matches "import X from 'path'", "import('path')", "require('path')", "export * from 'path'"
|
|
78
|
+
queryStr = `
|
|
79
|
+
(import_statement source: (string) @path)
|
|
80
|
+
(export_statement source: (string) @path)
|
|
81
|
+
(call_expression
|
|
82
|
+
function: [(identifier) (import)] @fn
|
|
83
|
+
arguments: (arguments (string) @path)
|
|
84
|
+
(#match? @fn "^(require|import)$")
|
|
85
|
+
)
|
|
86
|
+
`;
|
|
87
|
+
} else if (ext === '.py') {
|
|
88
|
+
// python imports
|
|
89
|
+
queryStr = `
|
|
90
|
+
(import_from_statement module_name: (_) @module)
|
|
91
|
+
(import_statement name: (_) @module)
|
|
92
|
+
`;
|
|
93
|
+
} else if (ext === '.go') {
|
|
94
|
+
queryStr = `
|
|
95
|
+
(import_spec path: (interpreted_string_literal) @path)
|
|
96
|
+
`;
|
|
97
|
+
} else if (ext === '.java') {
|
|
98
|
+
queryStr = `
|
|
99
|
+
(import_declaration (scoped_identifier) @path)
|
|
100
|
+
`;
|
|
101
|
+
} else if (ext === '.rs') {
|
|
102
|
+
queryStr = `
|
|
103
|
+
(use_declaration argument: (scoped_identifier) @path)
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (queryStr) {
|
|
108
|
+
const query = new this.QueryClass(languageOption, queryStr);
|
|
109
|
+
this.queries.set(ext, query);
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.error(`[TreeSitter] Query compilation failed for ${ext}:`, e);
|
|
113
|
+
// Ignore parser failure for a specific extension silently, letting fallback take over
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
parseImports(content: string, filePath: string): string[] {
|
|
118
|
+
const ext = path.extname(filePath);
|
|
119
|
+
const parser = this.parsers.get(ext);
|
|
120
|
+
const query = this.queries.get(ext);
|
|
121
|
+
|
|
122
|
+
if (!parser || !query) {
|
|
123
|
+
throw new Error(`Tree-Sitter parser not mapped/loaded for extension ${ext}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const tree = parser.parse(content);
|
|
127
|
+
const matches = query.matches(tree.rootNode);
|
|
128
|
+
|
|
129
|
+
const imports: string[] = [];
|
|
130
|
+
|
|
131
|
+
for (const match of matches) {
|
|
132
|
+
// Extract the captured module paths/names
|
|
133
|
+
for (const capture of match.captures) {
|
|
134
|
+
if (capture.name === 'path' || capture.name === 'module') {
|
|
135
|
+
// Remove quotes around literal strings (e.g., "'./module'" -> "./module")
|
|
136
|
+
let value = capture.node.text.replace(/['"]/g, '');
|
|
137
|
+
|
|
138
|
+
if (ext === '.py') {
|
|
139
|
+
value = value.split(' as ')[0].trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
imports.push(value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Anti-GC trick for V8 + node-tree-sitter:
|
|
148
|
+
// If the 'tree' object is not referenced after 'query.matches()', V8's aggressive GC
|
|
149
|
+
// might destroy the Tree before 'query.matches' fully executes its C++ bindings,
|
|
150
|
+
// resulting in "Cannot read properties of undefined (reading 'tree')" inside marshalNode.
|
|
151
|
+
// Calling tree.delete() explicitly fixes memory leaks AND keeps the reference alive.
|
|
152
|
+
if (typeof (tree as any).delete === 'function') {
|
|
153
|
+
(tree as any).delete();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return imports;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { ArchitectConfig } from './types/core.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONFIG: ArchitectConfig = {
|
|
6
|
+
ignore: [
|
|
7
|
+
'**/node_modules/**',
|
|
8
|
+
'**/dist/**',
|
|
9
|
+
'**/build/**',
|
|
10
|
+
'**/coverage/**',
|
|
11
|
+
'**/.git/**',
|
|
12
|
+
'**/.next/**',
|
|
13
|
+
'**/venv/**',
|
|
14
|
+
'**/__pycache__/**',
|
|
15
|
+
'**/target/**',
|
|
16
|
+
'**/out/**',
|
|
17
|
+
'**/.cache/**',
|
|
18
|
+
],
|
|
19
|
+
frameworks: {
|
|
20
|
+
detect: true,
|
|
21
|
+
},
|
|
22
|
+
antiPatterns: {
|
|
23
|
+
godClass: {
|
|
24
|
+
linesThreshold: 500,
|
|
25
|
+
methodsThreshold: 10,
|
|
26
|
+
},
|
|
27
|
+
shotgunSurgery: {
|
|
28
|
+
changePropagationThreshold: 5,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
score: {
|
|
32
|
+
modularity: 0.4,
|
|
33
|
+
coupling: 0.25,
|
|
34
|
+
cohesion: 0.2,
|
|
35
|
+
layering: 0.15,
|
|
36
|
+
},
|
|
37
|
+
monorepo: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
treatPackagesAsModules: true,
|
|
40
|
+
},
|
|
41
|
+
plugins: [],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Normalize ignore patterns to use proper glob syntax.
|
|
46
|
+
* Simple names like "node_modules" are expanded to cover nested directories:
|
|
47
|
+
* "node_modules" → ["node_modules", "node_modules/**", "** /node_modules", "** /node_modules/**"]
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeIgnorePatterns(patterns: string[]): string[] {
|
|
50
|
+
const normalized = new Set<string>();
|
|
51
|
+
|
|
52
|
+
for (const p of patterns) {
|
|
53
|
+
if (p.includes('*') || p.includes('/')) {
|
|
54
|
+
// Already a glob pattern — keep as-is
|
|
55
|
+
normalized.add(p);
|
|
56
|
+
} else {
|
|
57
|
+
// Simple directory name — expand to cover all nesting levels
|
|
58
|
+
normalized.add(p);
|
|
59
|
+
normalized.add(`${p}/**`);
|
|
60
|
+
normalized.add(`**/${p}`);
|
|
61
|
+
normalized.add(`**/${p}/**`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return [...normalized];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class ConfigLoader {
|
|
69
|
+
static loadConfig(projectPath: string): ArchitectConfig {
|
|
70
|
+
const configPath = join(projectPath, '.architect.json');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
74
|
+
const userConfig = JSON.parse(content) as ArchitectConfig;
|
|
75
|
+
return this.mergeConfigs(DEFAULT_CONFIG, userConfig);
|
|
76
|
+
} catch {
|
|
77
|
+
return { ...DEFAULT_CONFIG };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static mergeConfigs(
|
|
82
|
+
defaults: ArchitectConfig,
|
|
83
|
+
user: ArchitectConfig
|
|
84
|
+
): ArchitectConfig {
|
|
85
|
+
// If user provides ignore patterns, normalize them to proper glob format
|
|
86
|
+
const userIgnore = user.ignore
|
|
87
|
+
? normalizeIgnorePatterns(user.ignore)
|
|
88
|
+
: defaults.ignore;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
ignore: userIgnore,
|
|
92
|
+
plugins: user.plugins ?? defaults.plugins,
|
|
93
|
+
frameworks: {
|
|
94
|
+
detect: user.frameworks?.detect ?? defaults.frameworks?.detect,
|
|
95
|
+
},
|
|
96
|
+
antiPatterns: {
|
|
97
|
+
godClass: {
|
|
98
|
+
linesThreshold:
|
|
99
|
+
user.antiPatterns?.godClass?.linesThreshold ??
|
|
100
|
+
defaults.antiPatterns?.godClass?.linesThreshold,
|
|
101
|
+
methodsThreshold:
|
|
102
|
+
user.antiPatterns?.godClass?.methodsThreshold ??
|
|
103
|
+
defaults.antiPatterns?.godClass?.methodsThreshold,
|
|
104
|
+
},
|
|
105
|
+
shotgunSurgery: {
|
|
106
|
+
changePropagationThreshold:
|
|
107
|
+
user.antiPatterns?.shotgunSurgery?.changePropagationThreshold ??
|
|
108
|
+
defaults.antiPatterns?.shotgunSurgery?.changePropagationThreshold,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
score: {
|
|
112
|
+
modularity: user.score?.modularity ?? defaults.score?.modularity,
|
|
113
|
+
coupling: user.score?.coupling ?? defaults.score?.coupling,
|
|
114
|
+
cohesion: user.score?.cohesion ?? defaults.score?.cohesion,
|
|
115
|
+
layering: user.score?.layering ?? defaults.score?.layering,
|
|
116
|
+
},
|
|
117
|
+
monorepo: {
|
|
118
|
+
enabled: user.monorepo?.enabled ?? defaults.monorepo?.enabled,
|
|
119
|
+
treatPackagesAsModules:
|
|
120
|
+
user.monorepo?.treatPackagesAsModules ??
|
|
121
|
+
defaults.monorepo?.treatPackagesAsModules,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { DependencyEdge, Layer } from './types/core.js';
|
|
2
|
+
|
|
3
|
+
const LAYER_THEME: Record<string, { color: string; style: string }> = {
|
|
4
|
+
API: { color: '#FFB6C1', style: 'apiStyle' },
|
|
5
|
+
Service: { color: '#87CEEB', style: 'serviceStyle' },
|
|
6
|
+
Data: { color: '#90EE90', style: 'dataStyle' },
|
|
7
|
+
UI: { color: '#FFD700', style: 'uiStyle' },
|
|
8
|
+
Infrastructure: { color: '#D3D3D3', style: 'infraStyle' },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class DiagramGenerator {
|
|
12
|
+
generateComponentDiagram(
|
|
13
|
+
edges: DependencyEdge[],
|
|
14
|
+
layers: Layer[]
|
|
15
|
+
): string {
|
|
16
|
+
const nodes = new Set<string>();
|
|
17
|
+
edges.forEach((e) => {
|
|
18
|
+
nodes.add(e.from);
|
|
19
|
+
nodes.add(e.to);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const nodeColors: Record<string, string> = {};
|
|
23
|
+
for (const layer of layers) {
|
|
24
|
+
for (const file of layer.files) {
|
|
25
|
+
const moduleName = file.split('/').pop() || file;
|
|
26
|
+
nodeColors[moduleName] = LAYER_THEME[layer.name]?.color || '#FFFFFF';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let mermaid = 'graph TB\n';
|
|
31
|
+
|
|
32
|
+
for (const node of Array.from(nodes).slice(0, 20)) {
|
|
33
|
+
const moduleName = node.split('/').pop() || node;
|
|
34
|
+
const color = nodeColors[moduleName] || '#FFFFFF';
|
|
35
|
+
mermaid += ` ${this.sanitizeNodeName(node)}["${moduleName}"]:::${this.getStyleClass(color)}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
mermaid += '\n';
|
|
39
|
+
|
|
40
|
+
for (const edge of edges.slice(0, 30)) {
|
|
41
|
+
mermaid += ` ${this.sanitizeNodeName(edge.from)} --> ${this.sanitizeNodeName(edge.to)}\n`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this.appendCommonClassDefs(mermaid);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
generateLayerDiagram(layers: Layer[]): string {
|
|
48
|
+
let mermaid = 'graph LR\n';
|
|
49
|
+
|
|
50
|
+
const layerOrder = ['UI', 'API', 'Service', 'Data', 'Infrastructure'];
|
|
51
|
+
|
|
52
|
+
for (const layerName of layerOrder) {
|
|
53
|
+
const layer = layers.find((l) => l.name === layerName);
|
|
54
|
+
if (layer && layer.files.length > 0) {
|
|
55
|
+
const nodeId = layerName.replace(/\s+/g, '_');
|
|
56
|
+
const fileCount = layer.files.length;
|
|
57
|
+
const style = LAYER_THEME[layerName]?.style || 'defaultStyle';
|
|
58
|
+
mermaid += ` ${nodeId}["${layerName}<br/>(${fileCount} files)"]:::${style}\n`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < layerOrder.length - 1; i++) {
|
|
63
|
+
const from = layerOrder[i].replace(/\s+/g, '_');
|
|
64
|
+
const to = layerOrder[i + 1].replace(/\s+/g, '_');
|
|
65
|
+
mermaid += ` ${from} --> ${to}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return this.appendCommonClassDefs(mermaid);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
generateDependencyFlowDiagram(edges: DependencyEdge[]): string {
|
|
72
|
+
const flowMap: Record<string, number> = {};
|
|
73
|
+
|
|
74
|
+
for (const edge of edges) {
|
|
75
|
+
const flowKey = `${edge.from} -> ${edge.to}`;
|
|
76
|
+
flowMap[flowKey] = (flowMap[flowKey] || 0) + edge.weight;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const topFlows = Object.entries(flowMap)
|
|
80
|
+
.sort((a, b) => b[1] - a[1])
|
|
81
|
+
.slice(0, 15);
|
|
82
|
+
|
|
83
|
+
let mermaid = 'graph LR\n';
|
|
84
|
+
|
|
85
|
+
const nodes = new Set<string>();
|
|
86
|
+
for (const [flow] of topFlows) {
|
|
87
|
+
const [from, _, to] = flow.split(' ');
|
|
88
|
+
nodes.add(from);
|
|
89
|
+
nodes.add(to);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const node of nodes) {
|
|
93
|
+
const moduleName = node.split('/').pop() || node;
|
|
94
|
+
mermaid += ` ${this.sanitizeNodeName(node)}["${moduleName}"]\n`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
mermaid += '\n';
|
|
98
|
+
|
|
99
|
+
for (const [flow, weight] of topFlows) {
|
|
100
|
+
const [from, _, to] = flow.split(' ');
|
|
101
|
+
const label = weight > 1 ? ` | ${weight}` : '';
|
|
102
|
+
mermaid += ` ${this.sanitizeNodeName(from)} -->|${label}| ${this.sanitizeNodeName(to)}\n`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return mermaid; // specific diagram that doesn't use standard classDefs
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private sanitizeNodeName(path: string): string {
|
|
109
|
+
return path
|
|
110
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
111
|
+
.replace(/_+/g, '_')
|
|
112
|
+
.toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private getStyleClass(color: string): string {
|
|
116
|
+
const match = Object.values(LAYER_THEME).find(t => t.color === color);
|
|
117
|
+
return match ? match.style : 'defaultStyle';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private appendCommonClassDefs(mermaid: string): string {
|
|
121
|
+
let result = mermaid + '\n';
|
|
122
|
+
result += ' classDef apiStyle fill:#FFB6C1,stroke:#333,color:#000\n';
|
|
123
|
+
result += ' classDef serviceStyle fill:#87CEEB,stroke:#333,color:#000\n';
|
|
124
|
+
result += ' classDef dataStyle fill:#90EE90,stroke:#333,color:#000\n';
|
|
125
|
+
result += ' classDef uiStyle fill:#FFD700,stroke:#333,color:#000\n';
|
|
126
|
+
result += ' classDef infraStyle fill:#D3D3D3,stroke:#333,color:#000\n';
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/core/i18n.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { en } from './locales/en.js';
|
|
2
|
+
import { ptBR, AppTranslation } from './locales/pt-BR.js';
|
|
3
|
+
|
|
4
|
+
type Locale = 'en' | 'pt-BR';
|
|
5
|
+
|
|
6
|
+
class I18nEngine {
|
|
7
|
+
private currentLocale: Locale = 'en';
|
|
8
|
+
|
|
9
|
+
setLocale(locale: Locale) {
|
|
10
|
+
if (locale === 'en' || locale === 'pt-BR') {
|
|
11
|
+
this.currentLocale = locale;
|
|
12
|
+
} else {
|
|
13
|
+
this.currentLocale = 'en';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getLocale(): Locale {
|
|
18
|
+
return this.currentLocale;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private getDict(): AppTranslation {
|
|
22
|
+
return this.currentLocale === 'pt-BR' ? ptBR : en;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Obtém uma string de tradução navegando pela dot notation da AppTranslation.
|
|
27
|
+
* Exemplo: t('agents.backend.title', { lang: 'TypeScript' })
|
|
28
|
+
*/
|
|
29
|
+
t(path: string, params?: Record<string, string | number>): string {
|
|
30
|
+
const keys = path.split('.');
|
|
31
|
+
let current: any = this.getDict();
|
|
32
|
+
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
if (current[key] === undefined) {
|
|
35
|
+
// Fallback to EN if missing in current dict
|
|
36
|
+
let fallback: any = en;
|
|
37
|
+
for (const fKey of keys) {
|
|
38
|
+
if (fallback[fKey] === undefined) return `[Missing translation: ${path}]`;
|
|
39
|
+
fallback = fallback[fKey];
|
|
40
|
+
}
|
|
41
|
+
current = fallback;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
current = current[key];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof current !== 'string') {
|
|
48
|
+
return `[Invalid translation path: ${path}]`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let text = current as string;
|
|
52
|
+
|
|
53
|
+
// Replace params: {fw} -> value
|
|
54
|
+
if (params) {
|
|
55
|
+
for (const [key, value] of Object.entries(params)) {
|
|
56
|
+
text = text.replace(new RegExp(`{${key}}`, 'g'), String(value));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return text;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const i18n = new I18nEngine();
|