claudeos-core 2.2.0 → 2.3.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/CHANGELOG.md +1649 -907
- package/CONTRIBUTING.md +92 -92
- package/README.de.md +32 -0
- package/README.es.md +32 -0
- package/README.fr.md +32 -0
- package/README.hi.md +32 -0
- package/README.ja.md +32 -0
- package/README.ko.md +1018 -986
- package/README.md +1020 -987
- package/README.ru.md +32 -0
- package/README.vi.md +1019 -987
- package/README.zh-CN.md +32 -0
- package/bin/cli.js +152 -148
- package/bin/commands/init.js +1673 -1554
- package/bin/commands/lint.js +62 -0
- package/bin/commands/memory.js +438 -438
- package/bin/lib/cli-utils.js +206 -206
- package/claude-md-validator/index.js +184 -0
- package/claude-md-validator/reporter.js +66 -0
- package/claude-md-validator/structural-checks.js +528 -0
- package/content-validator/index.js +666 -441
- package/lib/expected-guides.js +23 -23
- package/lib/expected-outputs.js +90 -90
- package/lib/language-config.js +35 -35
- package/lib/memory-scaffold.js +1058 -1054
- package/lib/plan-parser.js +165 -165
- package/lib/staged-rules.js +118 -118
- package/manifest-generator/index.js +174 -174
- package/package.json +90 -87
- package/pass-json-validator/index.js +337 -337
- package/pass-prompts/templates/common/claude-md-scaffold.md +52 -10
- package/pass-prompts/templates/common/pass3-footer.md +402 -224
- package/pass-prompts/templates/common/pass3b-core-header.md +43 -0
- package/pass-prompts/templates/common/pass4.md +375 -305
- package/pass-prompts/templates/common/staging-override.md +26 -26
- package/pass-prompts/templates/node-vite/pass1.md +117 -117
- package/pass-prompts/templates/node-vite/pass2.md +78 -78
- package/pass-prompts/templates/python-flask/pass1.md +119 -119
- package/pass-prompts/templates/python-flask/pass2.md +85 -85
- package/plan-installer/domain-grouper.js +76 -76
- package/plan-installer/index.js +137 -137
- package/plan-installer/prompt-generator.js +188 -145
- package/plan-installer/scanners/scan-frontend.js +505 -473
- package/plan-installer/scanners/scan-java.js +226 -226
- package/plan-installer/scanners/scan-node.js +57 -57
- package/plan-installer/scanners/scan-python.js +85 -85
- package/plan-installer/stack-detector.js +482 -482
- package/plan-installer/structure-scanner.js +65 -65
- package/sync-checker/index.js +177 -177
|
@@ -1,226 +1,226 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClaudeOS-Core — Java Structure Scanner
|
|
3
|
-
*
|
|
4
|
-
* Scans Java project directory structure to discover backend domains.
|
|
5
|
-
* Supports 5 patterns:
|
|
6
|
-
* A: controller/{domain}/*.java (layer-first)
|
|
7
|
-
* B: {domain}/controller/*.java (domain-first)
|
|
8
|
-
* C: controller/DomainController.java (flat, extract from class name)
|
|
9
|
-
* D: {module}/{domain}/controller/ (module/domain — auto-upgrade from B on conflict)
|
|
10
|
-
* E: {domain}/adapter/in/web/*.java (DDD/Hexagonal)
|
|
11
|
-
* Also includes supplementary service-only scan (all patterns) and full fallback.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const path = require("path");
|
|
15
|
-
const { glob } = require("glob");
|
|
16
|
-
|
|
17
|
-
// Normalize backslash paths from glob on Windows to forward slashes
|
|
18
|
-
const norm = (p) => p.replace(/\\/g, "/");
|
|
19
|
-
|
|
20
|
-
async function scanJavaDomains(stack, ROOT) {
|
|
21
|
-
const backendDomains = [];
|
|
22
|
-
let rootPackage = null;
|
|
23
|
-
|
|
24
|
-
const javaFiles = (await glob("src/main/java/**/*.java", { cwd: ROOT })).map(norm);
|
|
25
|
-
for (const f of javaFiles) {
|
|
26
|
-
const m = f.match(/src\/main\/java\/(.+?)\/(controller|aggregator|facade|usecase|orchestrator|service|mapper|dao|dto|entity|repository|adapter)/);
|
|
27
|
-
if (m) { rootPackage = m[1].replace(/\//g, "."); break; }
|
|
28
|
-
}
|
|
29
|
-
const domainMap = {};
|
|
30
|
-
let detectedPattern = null;
|
|
31
|
-
|
|
32
|
-
// Pattern A: controller/{domain}/*.java (layer-first — domain under controller)
|
|
33
|
-
const controllersA = (await glob("src/main/java/**/controller/*/*.java", { cwd: ROOT })).map(norm);
|
|
34
|
-
for (const f of controllersA) {
|
|
35
|
-
const m = f.match(/controller\/([^/]+)\//);
|
|
36
|
-
if (m) {
|
|
37
|
-
const d = m[1];
|
|
38
|
-
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "A" };
|
|
39
|
-
domainMap[d].controllers++;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (Object.keys(domainMap).length > 0) detectedPattern = "A";
|
|
43
|
-
|
|
44
|
-
// Pattern B/D: {domain}/controller/*.java (domain-first — controller under domain)
|
|
45
|
-
// D extends B: {module}/{domain}/controller/ — auto-upgrade to module/domain on name conflict
|
|
46
|
-
if (!detectedPattern) {
|
|
47
|
-
const controllersB = (await glob("src/main/java/**/*/controller/*.java", { cwd: ROOT })).map(norm);
|
|
48
|
-
const domainPaths = {};
|
|
49
|
-
for (const f of controllersB) {
|
|
50
|
-
const m = f.match(/\/([^/]+)\/controller\/[^/]+\.java$/);
|
|
51
|
-
if (m) {
|
|
52
|
-
const d = m[1];
|
|
53
|
-
const parentMatch = f.match(/\/([^/]+)\/([^/]+)\/controller\//);
|
|
54
|
-
const parentModule = parentMatch ? parentMatch[1] : null;
|
|
55
|
-
if (!domainPaths[d]) domainPaths[d] = [];
|
|
56
|
-
domainPaths[d].push({ file: f, module: parentModule });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// If same domain name found in multiple modules, use module/domain form (Pattern D)
|
|
61
|
-
for (const [d, entries] of Object.entries(domainPaths)) {
|
|
62
|
-
const modules = [...new Set(entries.map(e => e.module).filter(Boolean))];
|
|
63
|
-
if (modules.length > 1) {
|
|
64
|
-
// Pattern D: conflict — register as module/domain
|
|
65
|
-
for (const entry of entries) {
|
|
66
|
-
const fullName = entry.module ? `${entry.module}/${d}` : d;
|
|
67
|
-
if (!domainMap[fullName]) domainMap[fullName] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "D", modulePath: entry.module, domainName: d };
|
|
68
|
-
domainMap[fullName].controllers++;
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "B" };
|
|
72
|
-
domainMap[d].controllers += entries.length;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (Object.keys(domainMap).length > 0) {
|
|
76
|
-
// Determine pattern by majority vote (B vs D)
|
|
77
|
-
const patternCounts = {};
|
|
78
|
-
for (const v of Object.values(domainMap)) patternCounts[v.pattern] = (patternCounts[v.pattern] || 0) + 1;
|
|
79
|
-
detectedPattern = Object.entries(patternCounts).sort((a, b) => b[1] - a[1])[0][0];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Pattern E: DDD/Hexagonal — {domain}/adapter/in/web/*.java or {domain}/adapter/in/rest/*.java
|
|
84
|
-
if (!detectedPattern) {
|
|
85
|
-
const controllersE = (await glob("src/main/java/**/adapter/in/{web,rest}/*.java", { cwd: ROOT })).map(norm);
|
|
86
|
-
for (const f of controllersE) {
|
|
87
|
-
const m = f.match(/\/([^/]+)\/adapter\/in\/(web|rest)\/[^/]+\.java$/);
|
|
88
|
-
if (m) {
|
|
89
|
-
const d = m[1];
|
|
90
|
-
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "E" };
|
|
91
|
-
domainMap[d].controllers++;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (Object.keys(domainMap).length > 0) detectedPattern = "E";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Pattern C: Flat structure — controller/*.java (no domain directory, extract domain from class name)
|
|
98
|
-
if (!detectedPattern) {
|
|
99
|
-
const controllersC = (await glob("src/main/java/**/controller/*.java", { cwd: ROOT })).map(norm);
|
|
100
|
-
for (const f of controllersC) {
|
|
101
|
-
const m = f.match(/\/([A-Z][a-zA-Z]*)Controller\.java$/);
|
|
102
|
-
if (m) {
|
|
103
|
-
const d = m[1].toLowerCase();
|
|
104
|
-
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "C" };
|
|
105
|
-
domainMap[d].controllers++;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (Object.keys(domainMap).length > 0) detectedPattern = "C";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ── Supplementary scan: detect domains without controllers (service/dao/aggregator/facade/usecase only) ──
|
|
112
|
-
// Runs for ALL detected patterns (A/B/C/D/E) to catch core-only domains
|
|
113
|
-
{
|
|
114
|
-
const serviceDirs = (await glob("src/main/java/**/*/service/*.java", { cwd: ROOT })).map(norm);
|
|
115
|
-
const mapperDirs = (await glob("src/main/java/**/*/{mapper,repository,dao}/*.java", { cwd: ROOT })).map(norm);
|
|
116
|
-
const orchestrationDirs = (await glob("src/main/java/**/*/{aggregator,facade,usecase,orchestrator}/*.java", { cwd: ROOT })).map(norm);
|
|
117
|
-
const allServiceFiles = [...serviceDirs, ...mapperDirs, ...orchestrationDirs];
|
|
118
|
-
const skipDomains = ["common", "config", "util", "utils", "base", "core", "shared", "global", "framework", "infra", "front", "admin", "back", "internal", "external", "web", "app", "test", "tests", "main", "generated", "build"];
|
|
119
|
-
for (const f of allServiceFiles) {
|
|
120
|
-
const m = f.match(/\/([^/]+)\/(service|mapper|repository|dao|aggregator|facade|usecase|orchestrator)\/[^/]+\.java$/);
|
|
121
|
-
if (m) {
|
|
122
|
-
const d = m[1];
|
|
123
|
-
if (!domainMap[d] && !skipDomains.includes(d) && !/^v\d+$/.test(d)) {
|
|
124
|
-
domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: detectedPattern || "B" };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Scan service/mapper/dao/aggregator/facade/usecase/dto/xml files for each domain
|
|
131
|
-
for (const d of Object.keys(domainMap)) {
|
|
132
|
-
const p = domainMap[d].pattern;
|
|
133
|
-
const dn = domainMap[d].domainName || d;
|
|
134
|
-
let svcGlob, mprGlob, dtoGlob, aggGlob;
|
|
135
|
-
|
|
136
|
-
if (p === "A") {
|
|
137
|
-
svcGlob = `src/main/java/**/service/${d}/*.java`;
|
|
138
|
-
mprGlob = `src/main/java/**/{mapper,repository,dao}/${d}/*.java`;
|
|
139
|
-
dtoGlob = `src/main/java/**/dto/${d}/**/*.java`;
|
|
140
|
-
aggGlob = `src/main/java/**/{aggregator,facade,usecase,orchestrator}/${d}/*.java`;
|
|
141
|
-
} else if (p === "B" || p === "D") {
|
|
142
|
-
svcGlob = `src/main/java/**/${dn}/service/*.java`;
|
|
143
|
-
mprGlob = `src/main/java/**/${dn}/{mapper,repository,dao}/*.java`;
|
|
144
|
-
dtoGlob = `src/main/java/**/${dn}/dto/**/*.java`;
|
|
145
|
-
aggGlob = `src/main/java/**/${dn}/{aggregator,facade,usecase,orchestrator}/*.java`;
|
|
146
|
-
} else if (p === "E") {
|
|
147
|
-
svcGlob = `src/main/java/**/${d}/{application,domain}/**/*.java`;
|
|
148
|
-
mprGlob = `src/main/java/**/${d}/{adapter/out/{persistence,repository},infrastructure}/*.java`;
|
|
149
|
-
dtoGlob = `src/main/java/**/${d}/**/{dto,command,query}/**/*.java`;
|
|
150
|
-
aggGlob = null; // DDD/Hexagonal typically doesn't use aggregator layer
|
|
151
|
-
} else {
|
|
152
|
-
// Pattern C: Flat — match domain name from file name
|
|
153
|
-
const cap = d.charAt(0).toUpperCase() + d.slice(1);
|
|
154
|
-
svcGlob = `src/main/java/**/service/${cap}*.java`;
|
|
155
|
-
mprGlob = `src/main/java/**/{mapper,repository,dao}/${cap}*.java`;
|
|
156
|
-
dtoGlob = `src/main/java/**/dto/${cap}*.java`;
|
|
157
|
-
aggGlob = `src/main/java/**/{aggregator,facade,usecase,orchestrator}/${cap}*.java`;
|
|
158
|
-
}
|
|
159
|
-
// Pattern C (flat): XML may be in flat directory without domain subdirectory (e.g., mapper/OrderMapper.xml)
|
|
160
|
-
// Other patterns: XML is in domain subdirectory (e.g., mapper/order/OrderMapper.xml)
|
|
161
|
-
const capDn = dn.charAt(0).toUpperCase() + dn.slice(1);
|
|
162
|
-
const xmlGlob = p === "C"
|
|
163
|
-
? `src/main/resources/{mapper,mybatis}/**/{${dn}/${capDn}*.xml,${capDn}*.xml}`
|
|
164
|
-
: `src/main/resources/{mapper,mybatis}/**/${dn}/*.xml`;
|
|
165
|
-
|
|
166
|
-
const svc = await glob(svcGlob, { cwd: ROOT });
|
|
167
|
-
const mpr = await glob(mprGlob, { cwd: ROOT });
|
|
168
|
-
const dto = await glob(dtoGlob, { cwd: ROOT });
|
|
169
|
-
const xml = await glob(xmlGlob, { cwd: ROOT });
|
|
170
|
-
const agg = aggGlob ? await glob(aggGlob, { cwd: ROOT }) : [];
|
|
171
|
-
domainMap[d].services = svc.length + agg.length;
|
|
172
|
-
domainMap[d].mappers = mpr.length;
|
|
173
|
-
domainMap[d].dtos = dto.length;
|
|
174
|
-
domainMap[d].xmlMappers = xml.length;
|
|
175
|
-
const totalFiles = svc.length + agg.length + mpr.length + dto.length + xml.length + domainMap[d].controllers;
|
|
176
|
-
backendDomains.push({ name: d, type: "backend", ...domainMap[d], totalFiles });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ── Java fallback: extract domains directly from all .java files when glob returns 0 ──
|
|
180
|
-
if (backendDomains.length === 0) {
|
|
181
|
-
const allJava = (await glob("**/*.java", { cwd: ROOT, ignore: ["**/node_modules/**", "**/build/**", "**/target/**", "**/test/**", "**/generated/**"] })).map(norm);
|
|
182
|
-
const javaDomains = {};
|
|
183
|
-
const skipNames = ["common", "config", "util", "utils", "base", "shared", "global", "framework", "infra", "api", "main", "front", "admin", "back", "internal", "external", "web", "app", "test", "tests", "generated", "build"];
|
|
184
|
-
const versionPattern = /^v\d+$/;
|
|
185
|
-
const layerNames = ["controller", "aggregator", "facade", "usecase", "orchestrator", "service", "mapper", "repository", "dao", "dto", "vo", "entity", "adapter"];
|
|
186
|
-
|
|
187
|
-
for (const f of allJava) {
|
|
188
|
-
const parts = f.replace(/\\/g, "/").split("/");
|
|
189
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
190
|
-
if (layerNames.includes(parts[i])) {
|
|
191
|
-
const prevDir = parts[i - 1];
|
|
192
|
-
const nextDir = parts[i + 1];
|
|
193
|
-
|
|
194
|
-
// {domain}/layer/ pattern (domain before layer)
|
|
195
|
-
if (i > 0 && !skipNames.includes(prevDir) && !layerNames.includes(prevDir) && !prevDir.includes(".") && !versionPattern.test(prevDir)) {
|
|
196
|
-
if (!javaDomains[prevDir]) javaDomains[prevDir] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "B" };
|
|
197
|
-
if (parts[i] === "controller") javaDomains[prevDir].controllers++;
|
|
198
|
-
else if (["aggregator", "facade", "usecase", "orchestrator", "service"].includes(parts[i])) javaDomains[prevDir].services++;
|
|
199
|
-
else if (["mapper", "repository", "dao"].includes(parts[i])) javaDomains[prevDir].mappers++;
|
|
200
|
-
else if (["dto", "vo"].includes(parts[i])) javaDomains[prevDir].dtos++;
|
|
201
|
-
}
|
|
202
|
-
// layer/{domain}/ pattern (layer before domain)
|
|
203
|
-
if (nextDir && !nextDir.endsWith(".java") && !skipNames.includes(nextDir) && !layerNames.includes(nextDir) && !versionPattern.test(nextDir)) {
|
|
204
|
-
if (!javaDomains[nextDir]) javaDomains[nextDir] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "A" };
|
|
205
|
-
if (parts[i] === "controller") javaDomains[nextDir].controllers++;
|
|
206
|
-
else if (["aggregator", "facade", "usecase", "orchestrator", "service"].includes(parts[i])) javaDomains[nextDir].services++;
|
|
207
|
-
else if (["mapper", "repository", "dao"].includes(parts[i])) javaDomains[nextDir].mappers++;
|
|
208
|
-
else if (["dto", "vo"].includes(parts[i])) javaDomains[nextDir].dtos++;
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
for (const [d, data] of Object.entries(javaDomains)) {
|
|
216
|
-
const total = data.controllers + data.services + data.mappers + data.dtos;
|
|
217
|
-
if (total > 0) {
|
|
218
|
-
backendDomains.push({ name: d, type: "backend", ...data, totalFiles: total });
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { backendDomains, rootPackage };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
module.exports = { scanJavaDomains };
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — Java Structure Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans Java project directory structure to discover backend domains.
|
|
5
|
+
* Supports 5 patterns:
|
|
6
|
+
* A: controller/{domain}/*.java (layer-first)
|
|
7
|
+
* B: {domain}/controller/*.java (domain-first)
|
|
8
|
+
* C: controller/DomainController.java (flat, extract from class name)
|
|
9
|
+
* D: {module}/{domain}/controller/ (module/domain — auto-upgrade from B on conflict)
|
|
10
|
+
* E: {domain}/adapter/in/web/*.java (DDD/Hexagonal)
|
|
11
|
+
* Also includes supplementary service-only scan (all patterns) and full fallback.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const { glob } = require("glob");
|
|
16
|
+
|
|
17
|
+
// Normalize backslash paths from glob on Windows to forward slashes
|
|
18
|
+
const norm = (p) => p.replace(/\\/g, "/");
|
|
19
|
+
|
|
20
|
+
async function scanJavaDomains(stack, ROOT) {
|
|
21
|
+
const backendDomains = [];
|
|
22
|
+
let rootPackage = null;
|
|
23
|
+
|
|
24
|
+
const javaFiles = (await glob("src/main/java/**/*.java", { cwd: ROOT })).map(norm);
|
|
25
|
+
for (const f of javaFiles) {
|
|
26
|
+
const m = f.match(/src\/main\/java\/(.+?)\/(controller|aggregator|facade|usecase|orchestrator|service|mapper|dao|dto|entity|repository|adapter)/);
|
|
27
|
+
if (m) { rootPackage = m[1].replace(/\//g, "."); break; }
|
|
28
|
+
}
|
|
29
|
+
const domainMap = {};
|
|
30
|
+
let detectedPattern = null;
|
|
31
|
+
|
|
32
|
+
// Pattern A: controller/{domain}/*.java (layer-first — domain under controller)
|
|
33
|
+
const controllersA = (await glob("src/main/java/**/controller/*/*.java", { cwd: ROOT })).map(norm);
|
|
34
|
+
for (const f of controllersA) {
|
|
35
|
+
const m = f.match(/controller\/([^/]+)\//);
|
|
36
|
+
if (m) {
|
|
37
|
+
const d = m[1];
|
|
38
|
+
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "A" };
|
|
39
|
+
domainMap[d].controllers++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(domainMap).length > 0) detectedPattern = "A";
|
|
43
|
+
|
|
44
|
+
// Pattern B/D: {domain}/controller/*.java (domain-first — controller under domain)
|
|
45
|
+
// D extends B: {module}/{domain}/controller/ — auto-upgrade to module/domain on name conflict
|
|
46
|
+
if (!detectedPattern) {
|
|
47
|
+
const controllersB = (await glob("src/main/java/**/*/controller/*.java", { cwd: ROOT })).map(norm);
|
|
48
|
+
const domainPaths = {};
|
|
49
|
+
for (const f of controllersB) {
|
|
50
|
+
const m = f.match(/\/([^/]+)\/controller\/[^/]+\.java$/);
|
|
51
|
+
if (m) {
|
|
52
|
+
const d = m[1];
|
|
53
|
+
const parentMatch = f.match(/\/([^/]+)\/([^/]+)\/controller\//);
|
|
54
|
+
const parentModule = parentMatch ? parentMatch[1] : null;
|
|
55
|
+
if (!domainPaths[d]) domainPaths[d] = [];
|
|
56
|
+
domainPaths[d].push({ file: f, module: parentModule });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If same domain name found in multiple modules, use module/domain form (Pattern D)
|
|
61
|
+
for (const [d, entries] of Object.entries(domainPaths)) {
|
|
62
|
+
const modules = [...new Set(entries.map(e => e.module).filter(Boolean))];
|
|
63
|
+
if (modules.length > 1) {
|
|
64
|
+
// Pattern D: conflict — register as module/domain
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const fullName = entry.module ? `${entry.module}/${d}` : d;
|
|
67
|
+
if (!domainMap[fullName]) domainMap[fullName] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "D", modulePath: entry.module, domainName: d };
|
|
68
|
+
domainMap[fullName].controllers++;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "B" };
|
|
72
|
+
domainMap[d].controllers += entries.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(domainMap).length > 0) {
|
|
76
|
+
// Determine pattern by majority vote (B vs D)
|
|
77
|
+
const patternCounts = {};
|
|
78
|
+
for (const v of Object.values(domainMap)) patternCounts[v.pattern] = (patternCounts[v.pattern] || 0) + 1;
|
|
79
|
+
detectedPattern = Object.entries(patternCounts).sort((a, b) => b[1] - a[1])[0][0];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Pattern E: DDD/Hexagonal — {domain}/adapter/in/web/*.java or {domain}/adapter/in/rest/*.java
|
|
84
|
+
if (!detectedPattern) {
|
|
85
|
+
const controllersE = (await glob("src/main/java/**/adapter/in/{web,rest}/*.java", { cwd: ROOT })).map(norm);
|
|
86
|
+
for (const f of controllersE) {
|
|
87
|
+
const m = f.match(/\/([^/]+)\/adapter\/in\/(web|rest)\/[^/]+\.java$/);
|
|
88
|
+
if (m) {
|
|
89
|
+
const d = m[1];
|
|
90
|
+
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "E" };
|
|
91
|
+
domainMap[d].controllers++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (Object.keys(domainMap).length > 0) detectedPattern = "E";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Pattern C: Flat structure — controller/*.java (no domain directory, extract domain from class name)
|
|
98
|
+
if (!detectedPattern) {
|
|
99
|
+
const controllersC = (await glob("src/main/java/**/controller/*.java", { cwd: ROOT })).map(norm);
|
|
100
|
+
for (const f of controllersC) {
|
|
101
|
+
const m = f.match(/\/([A-Z][a-zA-Z]*)Controller\.java$/);
|
|
102
|
+
if (m) {
|
|
103
|
+
const d = m[1].toLowerCase();
|
|
104
|
+
if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "C" };
|
|
105
|
+
domainMap[d].controllers++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (Object.keys(domainMap).length > 0) detectedPattern = "C";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Supplementary scan: detect domains without controllers (service/dao/aggregator/facade/usecase only) ──
|
|
112
|
+
// Runs for ALL detected patterns (A/B/C/D/E) to catch core-only domains
|
|
113
|
+
{
|
|
114
|
+
const serviceDirs = (await glob("src/main/java/**/*/service/*.java", { cwd: ROOT })).map(norm);
|
|
115
|
+
const mapperDirs = (await glob("src/main/java/**/*/{mapper,repository,dao}/*.java", { cwd: ROOT })).map(norm);
|
|
116
|
+
const orchestrationDirs = (await glob("src/main/java/**/*/{aggregator,facade,usecase,orchestrator}/*.java", { cwd: ROOT })).map(norm);
|
|
117
|
+
const allServiceFiles = [...serviceDirs, ...mapperDirs, ...orchestrationDirs];
|
|
118
|
+
const skipDomains = ["common", "config", "util", "utils", "base", "core", "shared", "global", "framework", "infra", "front", "admin", "back", "internal", "external", "web", "app", "test", "tests", "main", "generated", "build"];
|
|
119
|
+
for (const f of allServiceFiles) {
|
|
120
|
+
const m = f.match(/\/([^/]+)\/(service|mapper|repository|dao|aggregator|facade|usecase|orchestrator)\/[^/]+\.java$/);
|
|
121
|
+
if (m) {
|
|
122
|
+
const d = m[1];
|
|
123
|
+
if (!domainMap[d] && !skipDomains.includes(d) && !/^v\d+$/.test(d)) {
|
|
124
|
+
domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: detectedPattern || "B" };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Scan service/mapper/dao/aggregator/facade/usecase/dto/xml files for each domain
|
|
131
|
+
for (const d of Object.keys(domainMap)) {
|
|
132
|
+
const p = domainMap[d].pattern;
|
|
133
|
+
const dn = domainMap[d].domainName || d;
|
|
134
|
+
let svcGlob, mprGlob, dtoGlob, aggGlob;
|
|
135
|
+
|
|
136
|
+
if (p === "A") {
|
|
137
|
+
svcGlob = `src/main/java/**/service/${d}/*.java`;
|
|
138
|
+
mprGlob = `src/main/java/**/{mapper,repository,dao}/${d}/*.java`;
|
|
139
|
+
dtoGlob = `src/main/java/**/dto/${d}/**/*.java`;
|
|
140
|
+
aggGlob = `src/main/java/**/{aggregator,facade,usecase,orchestrator}/${d}/*.java`;
|
|
141
|
+
} else if (p === "B" || p === "D") {
|
|
142
|
+
svcGlob = `src/main/java/**/${dn}/service/*.java`;
|
|
143
|
+
mprGlob = `src/main/java/**/${dn}/{mapper,repository,dao}/*.java`;
|
|
144
|
+
dtoGlob = `src/main/java/**/${dn}/dto/**/*.java`;
|
|
145
|
+
aggGlob = `src/main/java/**/${dn}/{aggregator,facade,usecase,orchestrator}/*.java`;
|
|
146
|
+
} else if (p === "E") {
|
|
147
|
+
svcGlob = `src/main/java/**/${d}/{application,domain}/**/*.java`;
|
|
148
|
+
mprGlob = `src/main/java/**/${d}/{adapter/out/{persistence,repository},infrastructure}/*.java`;
|
|
149
|
+
dtoGlob = `src/main/java/**/${d}/**/{dto,command,query}/**/*.java`;
|
|
150
|
+
aggGlob = null; // DDD/Hexagonal typically doesn't use aggregator layer
|
|
151
|
+
} else {
|
|
152
|
+
// Pattern C: Flat — match domain name from file name
|
|
153
|
+
const cap = d.charAt(0).toUpperCase() + d.slice(1);
|
|
154
|
+
svcGlob = `src/main/java/**/service/${cap}*.java`;
|
|
155
|
+
mprGlob = `src/main/java/**/{mapper,repository,dao}/${cap}*.java`;
|
|
156
|
+
dtoGlob = `src/main/java/**/dto/${cap}*.java`;
|
|
157
|
+
aggGlob = `src/main/java/**/{aggregator,facade,usecase,orchestrator}/${cap}*.java`;
|
|
158
|
+
}
|
|
159
|
+
// Pattern C (flat): XML may be in flat directory without domain subdirectory (e.g., mapper/OrderMapper.xml)
|
|
160
|
+
// Other patterns: XML is in domain subdirectory (e.g., mapper/order/OrderMapper.xml)
|
|
161
|
+
const capDn = dn.charAt(0).toUpperCase() + dn.slice(1);
|
|
162
|
+
const xmlGlob = p === "C"
|
|
163
|
+
? `src/main/resources/{mapper,mybatis}/**/{${dn}/${capDn}*.xml,${capDn}*.xml}`
|
|
164
|
+
: `src/main/resources/{mapper,mybatis}/**/${dn}/*.xml`;
|
|
165
|
+
|
|
166
|
+
const svc = await glob(svcGlob, { cwd: ROOT });
|
|
167
|
+
const mpr = await glob(mprGlob, { cwd: ROOT });
|
|
168
|
+
const dto = await glob(dtoGlob, { cwd: ROOT });
|
|
169
|
+
const xml = await glob(xmlGlob, { cwd: ROOT });
|
|
170
|
+
const agg = aggGlob ? await glob(aggGlob, { cwd: ROOT }) : [];
|
|
171
|
+
domainMap[d].services = svc.length + agg.length;
|
|
172
|
+
domainMap[d].mappers = mpr.length;
|
|
173
|
+
domainMap[d].dtos = dto.length;
|
|
174
|
+
domainMap[d].xmlMappers = xml.length;
|
|
175
|
+
const totalFiles = svc.length + agg.length + mpr.length + dto.length + xml.length + domainMap[d].controllers;
|
|
176
|
+
backendDomains.push({ name: d, type: "backend", ...domainMap[d], totalFiles });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Java fallback: extract domains directly from all .java files when glob returns 0 ──
|
|
180
|
+
if (backendDomains.length === 0) {
|
|
181
|
+
const allJava = (await glob("**/*.java", { cwd: ROOT, ignore: ["**/node_modules/**", "**/build/**", "**/target/**", "**/test/**", "**/generated/**"] })).map(norm);
|
|
182
|
+
const javaDomains = {};
|
|
183
|
+
const skipNames = ["common", "config", "util", "utils", "base", "shared", "global", "framework", "infra", "api", "main", "front", "admin", "back", "internal", "external", "web", "app", "test", "tests", "generated", "build"];
|
|
184
|
+
const versionPattern = /^v\d+$/;
|
|
185
|
+
const layerNames = ["controller", "aggregator", "facade", "usecase", "orchestrator", "service", "mapper", "repository", "dao", "dto", "vo", "entity", "adapter"];
|
|
186
|
+
|
|
187
|
+
for (const f of allJava) {
|
|
188
|
+
const parts = f.replace(/\\/g, "/").split("/");
|
|
189
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
190
|
+
if (layerNames.includes(parts[i])) {
|
|
191
|
+
const prevDir = parts[i - 1];
|
|
192
|
+
const nextDir = parts[i + 1];
|
|
193
|
+
|
|
194
|
+
// {domain}/layer/ pattern (domain before layer)
|
|
195
|
+
if (i > 0 && !skipNames.includes(prevDir) && !layerNames.includes(prevDir) && !prevDir.includes(".") && !versionPattern.test(prevDir)) {
|
|
196
|
+
if (!javaDomains[prevDir]) javaDomains[prevDir] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "B" };
|
|
197
|
+
if (parts[i] === "controller") javaDomains[prevDir].controllers++;
|
|
198
|
+
else if (["aggregator", "facade", "usecase", "orchestrator", "service"].includes(parts[i])) javaDomains[prevDir].services++;
|
|
199
|
+
else if (["mapper", "repository", "dao"].includes(parts[i])) javaDomains[prevDir].mappers++;
|
|
200
|
+
else if (["dto", "vo"].includes(parts[i])) javaDomains[prevDir].dtos++;
|
|
201
|
+
}
|
|
202
|
+
// layer/{domain}/ pattern (layer before domain)
|
|
203
|
+
if (nextDir && !nextDir.endsWith(".java") && !skipNames.includes(nextDir) && !layerNames.includes(nextDir) && !versionPattern.test(nextDir)) {
|
|
204
|
+
if (!javaDomains[nextDir]) javaDomains[nextDir] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "A" };
|
|
205
|
+
if (parts[i] === "controller") javaDomains[nextDir].controllers++;
|
|
206
|
+
else if (["aggregator", "facade", "usecase", "orchestrator", "service"].includes(parts[i])) javaDomains[nextDir].services++;
|
|
207
|
+
else if (["mapper", "repository", "dao"].includes(parts[i])) javaDomains[nextDir].mappers++;
|
|
208
|
+
else if (["dto", "vo"].includes(parts[i])) javaDomains[nextDir].dtos++;
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
for (const [d, data] of Object.entries(javaDomains)) {
|
|
216
|
+
const total = data.controllers + data.services + data.mappers + data.dtos;
|
|
217
|
+
if (total > 0) {
|
|
218
|
+
backendDomains.push({ name: d, type: "backend", ...data, totalFiles: total });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { backendDomains, rootPackage };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = { scanJavaDomains };
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClaudeOS-Core — Node.js Structure Scanner
|
|
3
|
-
*
|
|
4
|
-
* Scans Node.js backend (Express/NestJS/Fastify) project structure to discover domains.
|
|
5
|
-
* Supports monorepo layouts (apps/*, packages/*) in addition to single-project src/.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const path = require("path");
|
|
9
|
-
const { glob } = require("glob");
|
|
10
|
-
|
|
11
|
-
async function scanNodeDomains(stack, ROOT) {
|
|
12
|
-
const backendDomains = [];
|
|
13
|
-
const skipDirs = ["common", "shared", "config", "utils", "lib", "core", "main", "interfaces", "types", "constants", "guards", "decorators", "pipes", "filters", "interceptors"];
|
|
14
|
-
|
|
15
|
-
// Collect candidate directories: standard src/ + monorepo apps/*/src/
|
|
16
|
-
const nestModules = await glob("src/modules/*/", { cwd: ROOT });
|
|
17
|
-
let srcDirs = nestModules.length > 0 ? nestModules : await glob("src/*/", { cwd: ROOT });
|
|
18
|
-
|
|
19
|
-
// Monorepo: scan apps/*/src/ and packages/*/src/ when standard src/ yields nothing backend-relevant
|
|
20
|
-
if (stack.monorepo || srcDirs.length === 0) {
|
|
21
|
-
const monoModules = await glob("{apps,packages}/*/src/modules/*/", { cwd: ROOT, ignore: ["**/node_modules/**"] });
|
|
22
|
-
if (monoModules.length > 0) {
|
|
23
|
-
srcDirs = [...srcDirs, ...monoModules];
|
|
24
|
-
} else {
|
|
25
|
-
const monoDirs = await glob("{apps,packages}/*/src/*/", { cwd: ROOT, ignore: ["**/node_modules/**"] });
|
|
26
|
-
srcDirs = [...srcDirs, ...monoDirs];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
for (let dir of srcDirs) {
|
|
31
|
-
if (!dir.endsWith("/")) dir += "/";
|
|
32
|
-
const name = path.basename(dir.replace(/\/$/, ""));
|
|
33
|
-
if (skipDirs.includes(name)) continue;
|
|
34
|
-
const files = await glob(`${dir.replace(/\\/g, "/")}**/*.{ts,js}`, { cwd: ROOT, ignore: ["**/*.spec.*", "**/*.test.*"] });
|
|
35
|
-
if (files.length > 0) {
|
|
36
|
-
const controllers = files.filter(f => /controller|router|route|handler/.test(f)).length;
|
|
37
|
-
const services = files.filter(f => /service/.test(f)).length;
|
|
38
|
-
const dtos = files.filter(f => /dto|schema|type/.test(f)).length;
|
|
39
|
-
const entities = files.filter(f => /entity|model/.test(f) && !/controller|service|dto/.test(f)).length;
|
|
40
|
-
const modules = files.filter(f => /\.module\./.test(f)).length;
|
|
41
|
-
const guards = files.filter(f => /guard/.test(f)).length;
|
|
42
|
-
const pipes = files.filter(f => /pipe/.test(f)).length;
|
|
43
|
-
const interceptors = files.filter(f => /interceptor/.test(f)).length;
|
|
44
|
-
const domain = { name, type: "backend", controllers, services, dtos, totalFiles: files.length };
|
|
45
|
-
if (entities > 0) domain.entities = entities;
|
|
46
|
-
if (modules > 0) domain.modules = modules;
|
|
47
|
-
if (guards > 0) domain.guards = guards;
|
|
48
|
-
if (pipes > 0) domain.pipes = pipes;
|
|
49
|
-
if (interceptors > 0) domain.interceptors = interceptors;
|
|
50
|
-
backendDomains.push(domain);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { backendDomains };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
module.exports = { scanNodeDomains };
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — Node.js Structure Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans Node.js backend (Express/NestJS/Fastify) project structure to discover domains.
|
|
5
|
+
* Supports monorepo layouts (apps/*, packages/*) in addition to single-project src/.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { glob } = require("glob");
|
|
10
|
+
|
|
11
|
+
async function scanNodeDomains(stack, ROOT) {
|
|
12
|
+
const backendDomains = [];
|
|
13
|
+
const skipDirs = ["common", "shared", "config", "utils", "lib", "core", "main", "interfaces", "types", "constants", "guards", "decorators", "pipes", "filters", "interceptors"];
|
|
14
|
+
|
|
15
|
+
// Collect candidate directories: standard src/ + monorepo apps/*/src/
|
|
16
|
+
const nestModules = await glob("src/modules/*/", { cwd: ROOT });
|
|
17
|
+
let srcDirs = nestModules.length > 0 ? nestModules : await glob("src/*/", { cwd: ROOT });
|
|
18
|
+
|
|
19
|
+
// Monorepo: scan apps/*/src/ and packages/*/src/ when standard src/ yields nothing backend-relevant
|
|
20
|
+
if (stack.monorepo || srcDirs.length === 0) {
|
|
21
|
+
const monoModules = await glob("{apps,packages}/*/src/modules/*/", { cwd: ROOT, ignore: ["**/node_modules/**"] });
|
|
22
|
+
if (monoModules.length > 0) {
|
|
23
|
+
srcDirs = [...srcDirs, ...monoModules];
|
|
24
|
+
} else {
|
|
25
|
+
const monoDirs = await glob("{apps,packages}/*/src/*/", { cwd: ROOT, ignore: ["**/node_modules/**"] });
|
|
26
|
+
srcDirs = [...srcDirs, ...monoDirs];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (let dir of srcDirs) {
|
|
31
|
+
if (!dir.endsWith("/")) dir += "/";
|
|
32
|
+
const name = path.basename(dir.replace(/\/$/, ""));
|
|
33
|
+
if (skipDirs.includes(name)) continue;
|
|
34
|
+
const files = await glob(`${dir.replace(/\\/g, "/")}**/*.{ts,js}`, { cwd: ROOT, ignore: ["**/*.spec.*", "**/*.test.*"] });
|
|
35
|
+
if (files.length > 0) {
|
|
36
|
+
const controllers = files.filter(f => /controller|router|route|handler/.test(f)).length;
|
|
37
|
+
const services = files.filter(f => /service/.test(f)).length;
|
|
38
|
+
const dtos = files.filter(f => /dto|schema|type/.test(f)).length;
|
|
39
|
+
const entities = files.filter(f => /entity|model/.test(f) && !/controller|service|dto/.test(f)).length;
|
|
40
|
+
const modules = files.filter(f => /\.module\./.test(f)).length;
|
|
41
|
+
const guards = files.filter(f => /guard/.test(f)).length;
|
|
42
|
+
const pipes = files.filter(f => /pipe/.test(f)).length;
|
|
43
|
+
const interceptors = files.filter(f => /interceptor/.test(f)).length;
|
|
44
|
+
const domain = { name, type: "backend", controllers, services, dtos, totalFiles: files.length };
|
|
45
|
+
if (entities > 0) domain.entities = entities;
|
|
46
|
+
if (modules > 0) domain.modules = modules;
|
|
47
|
+
if (guards > 0) domain.guards = guards;
|
|
48
|
+
if (pipes > 0) domain.pipes = pipes;
|
|
49
|
+
if (interceptors > 0) domain.interceptors = interceptors;
|
|
50
|
+
backendDomains.push(domain);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { backendDomains };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { scanNodeDomains };
|