claudeos-core 2.2.0 → 2.3.1

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +1664 -907
  2. package/CONTRIBUTING.md +92 -92
  3. package/README.de.md +28 -0
  4. package/README.es.md +28 -0
  5. package/README.fr.md +28 -0
  6. package/README.hi.md +28 -0
  7. package/README.ja.md +28 -0
  8. package/README.ko.md +1014 -986
  9. package/README.md +1016 -987
  10. package/README.ru.md +28 -0
  11. package/README.vi.md +1015 -987
  12. package/README.zh-CN.md +28 -0
  13. package/bin/cli.js +152 -148
  14. package/bin/commands/init.js +1673 -1554
  15. package/bin/commands/lint.js +62 -0
  16. package/bin/commands/memory.js +438 -438
  17. package/bin/lib/cli-utils.js +206 -206
  18. package/claude-md-validator/index.js +184 -0
  19. package/claude-md-validator/reporter.js +66 -0
  20. package/claude-md-validator/structural-checks.js +528 -0
  21. package/content-validator/index.js +666 -441
  22. package/lib/expected-guides.js +23 -23
  23. package/lib/expected-outputs.js +90 -90
  24. package/lib/language-config.js +35 -35
  25. package/lib/memory-scaffold.js +1058 -1054
  26. package/lib/plan-parser.js +165 -165
  27. package/lib/staged-rules.js +118 -118
  28. package/manifest-generator/index.js +174 -174
  29. package/package.json +90 -87
  30. package/pass-json-validator/index.js +337 -337
  31. package/pass-prompts/templates/common/claude-md-scaffold.md +52 -10
  32. package/pass-prompts/templates/common/pass3-footer.md +402 -224
  33. package/pass-prompts/templates/common/pass3b-core-header.md +43 -0
  34. package/pass-prompts/templates/common/pass4.md +375 -305
  35. package/pass-prompts/templates/common/staging-override.md +26 -26
  36. package/pass-prompts/templates/node-vite/pass1.md +117 -117
  37. package/pass-prompts/templates/node-vite/pass2.md +78 -78
  38. package/pass-prompts/templates/python-flask/pass1.md +119 -119
  39. package/pass-prompts/templates/python-flask/pass2.md +85 -85
  40. package/plan-installer/domain-grouper.js +76 -76
  41. package/plan-installer/index.js +137 -137
  42. package/plan-installer/prompt-generator.js +188 -145
  43. package/plan-installer/scanners/scan-frontend.js +505 -473
  44. package/plan-installer/scanners/scan-java.js +226 -226
  45. package/plan-installer/scanners/scan-node.js +57 -57
  46. package/plan-installer/scanners/scan-python.js +85 -85
  47. package/plan-installer/stack-detector.js +482 -482
  48. package/plan-installer/structure-scanner.js +65 -65
  49. 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 };