claudeos-core 2.5.0 → 2.5.2

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.
@@ -13,6 +13,7 @@
13
13
 
14
14
  const path = require("path");
15
15
  const { glob } = require("glob");
16
+ const { readFileSafe, existsSafe } = require("../../lib/safe-fs");
16
17
 
17
18
  // Normalize backslash paths from glob on Windows to forward slashes
18
19
  const norm = (p) => p.replace(/\\/g, "/");
@@ -29,7 +30,26 @@ const norm = (p) => p.replace(/\\/g, "/");
29
30
  // convention plugins are not application modules.
30
31
  const JAVA_ROOT_IGNORE = ["**/node_modules/**", "**/build/**", "**/target/**", "**/out/**", "**/.gradle/**", "**/generated/**", "**/.git/**", "**/src/test/**", "**/buildSrc/**"];
31
32
 
32
- async function discoverModulePrefixes(ROOT) {
33
+ // v2.5.x Source roots, not module prefixes. Every pattern below is written
34
+ // against the Maven/Gradle convention (`src/main/java`, `src/main/resources`)
35
+ // and is rewritten per discovered root, so the pattern set stays a single
36
+ // source of truth while legacy layouts become scannable:
37
+ //
38
+ // modern [<module>/]src/main/java (unchanged behaviour)
39
+ // Eclipse <classpathentry kind="src" path="src"/> ← consulted first
40
+ // Ant <javac srcdir="src"> (with <property> resolution)
41
+ // bare src/java, src, JavaSource, java, WebContent/WEB-INF/src holding *.java
42
+ //
43
+ // Candidates are tried in that order and every candidate that holds *.java
44
+ // becomes a root, except one nested inside (or enclosing) a root already
45
+ // accepted — the FIRST-listed of a nested pair wins, which is why the bare
46
+ // list names `src/java` before `src`.
47
+ //
48
+ // Legacy roots are consulted ONLY when no `src/main/java` exists anywhere in
49
+ // the tree, so a modern project with a stray top-level `src/` cannot be
50
+ // mis-rooted. For legacy roots the resources root is the java root itself:
51
+ // iBatis-era projects keep sqlmap XML next to the classes.
52
+ async function discoverSourceRoots(ROOT) {
33
53
  const javaRoots = (await glob("**/src/main/java/", { cwd: ROOT, ignore: JAVA_ROOT_IGNORE })).map(norm);
34
54
  const resRoots = (await glob("**/src/main/resources/", { cwd: ROOT, ignore: JAVA_ROOT_IGNORE })).map(norm);
35
55
  const prefixes = new Set();
@@ -37,16 +57,57 @@ async function discoverModulePrefixes(ROOT) {
37
57
  const m = r.replace(/\/$/, "").match(/^(.*?)src\/main\/(?:java|resources)$/);
38
58
  if (m) prefixes.add(m[1]); // "" for root, "api/" for a module
39
59
  }
40
- return [...prefixes].sort();
60
+ if (prefixes.size) {
61
+ return [...prefixes].sort().map(pre => ({ prefix: pre, javaRoot: pre + "src/main/java", resRoot: pre + "src/main/resources", legacy: false }));
62
+ }
63
+
64
+ // ── legacy fallbacks ──
65
+ const candidates = [];
66
+ const cpXml = readFileSafe(path.join(ROOT, ".classpath"));
67
+ if (cpXml) {
68
+ for (const m of cpXml.matchAll(/<classpathentry\b[^>]*\bkind\s*=\s*["']src["'][^>]*\bpath\s*=\s*["']([^"']+)["']/g)) {
69
+ const p = norm(m[1]).replace(/^\/|\/$/g, "");
70
+ if (p && !/(^|\/)test(s)?(\/|$)/i.test(p)) candidates.push(p);
71
+ }
72
+ }
73
+ const bx = readFileSafe(path.join(ROOT, "build.xml"));
74
+ if (bx) {
75
+ const props = {};
76
+ for (const m of bx.matchAll(/<property\b[^>]*\bname\s*=\s*["']([^"']+)["'][^>]*\bvalue\s*=\s*["']([^"']+)["']/g)) props[m[1]] = m[2];
77
+ for (const m of bx.matchAll(/<javac\b[^>]*\bsrcdir\s*=\s*["']([^"']+)["']/g)) {
78
+ const p = norm(m[1].replace(/\$\{([^}]+)\}/g, (_, k) => props[k] ?? "")).replace(/^\.?\/|\/$/g, "");
79
+ if (p && !/test/i.test(p)) candidates.push(p);
80
+ }
81
+ }
82
+ for (const c of ["src/java", "src", "JavaSource", "java", "WebContent/WEB-INF/src"]) candidates.push(c);
83
+
84
+ const roots = [];
85
+ const seen = new Set();
86
+ for (const c of candidates) {
87
+ if (seen.has(c)) continue;
88
+ seen.add(c);
89
+ if (!existsSafe(path.join(ROOT, c))) continue;
90
+ const hasJava = (await glob(c + "/**/*.java", { cwd: ROOT, ignore: JAVA_ROOT_IGNORE, nodir: true })).length > 0;
91
+ if (!hasJava) continue;
92
+ // Nested pair (`src` vs `src/java`) → keep the first-accepted one only.
93
+ if (roots.some(r => c.startsWith(r.javaRoot + "/") || r.javaRoot.startsWith(c + "/"))) continue;
94
+ roots.push({ prefix: "", javaRoot: c, resRoot: c, legacy: true });
95
+ }
96
+ return roots;
41
97
  }
42
98
 
43
- // Run one `src/main/...`-relative pattern against every discovered module
44
- // prefix and return the merged, normalized, de-duplicated file list.
45
- function makeModuleGlob(ROOT, prefixes) {
99
+ // Run one `src/main/...`-relative pattern against every discovered source
100
+ // root rewriting the conventional leading segment to that root's actual
101
+ // directory — and return the merged, normalized, de-duplicated file list.
102
+ function makeModuleGlob(ROOT, roots) {
46
103
  return async (pattern) => {
47
104
  const out = new Set();
48
- for (const pre of prefixes) {
49
- for (const f of await glob(pre + pattern, { cwd: ROOT })) out.add(norm(f));
105
+ for (const r of roots) {
106
+ // Replacement FUNCTIONS so a `$` in a discovered root path is literal.
107
+ const p = pattern
108
+ .replace(/^src\/main\/java(?=\/|$)/, () => r.javaRoot)
109
+ .replace(/^src\/main\/resources(?=\/|$)/, () => r.resRoot);
110
+ for (const f of await glob(p, { cwd: ROOT })) out.add(norm(f));
50
111
  }
51
112
  return [...out];
52
113
  };
@@ -56,8 +117,16 @@ async function scanJavaDomains(stack, ROOT) {
56
117
  const backendDomains = [];
57
118
  let rootPackage = null;
58
119
 
59
- const modulePrefixes = await discoverModulePrefixes(ROOT);
60
- const gj = makeModuleGlob(ROOT, modulePrefixes.length ? modulePrefixes : [""]);
120
+ const sourceRoots = await discoverSourceRoots(ROOT);
121
+ const rootsInUse = sourceRoots.length ? sourceRoots : [{ prefix: "", javaRoot: "src/main/java", resRoot: "src/main/resources", legacy: false }];
122
+ const modulePrefixes = rootsInUse.map(r => r.prefix).filter(Boolean);
123
+ const gj = makeModuleGlob(ROOT, rootsInUse);
124
+ // Regex fragment matching any java root — used where file PATHS (not
125
+ // glob patterns) are inspected below. Modern roots collapse to the
126
+ // conventional `src/main/java`; legacy roots contribute their own dir.
127
+ const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
128
+ const JAVA_ROOT_ALT = [...new Set(rootsInUse.map(r => r.legacy ? r.javaRoot : "src/main/java"))].map(escRe).join("|");
129
+ if (stack && rootsInUse.some(r => r.legacy)) stack.sourceLayout = "legacy";
61
130
 
62
131
  const javaFiles = (await gj("src/main/java/**/*.java"));
63
132
 
@@ -80,7 +149,7 @@ async function scanJavaDomains(stack, ROOT) {
80
149
  // not the minority `<root>.misc.*` location (no longer first-match).
81
150
  const pkgCounts = new Map();
82
151
  for (const f of javaFiles) {
83
- const m = f.match(/src\/main\/java\/(.+?)\/(controller|aggregator|facade|usecase|orchestrator|service|mapper|dao|dto|entity|repository|adapter)/);
152
+ const m = f.match(new RegExp(`(?:${JAVA_ROOT_ALT})/(.+?)/(controller|aggregator|facade|usecase|orchestrator|service|mapper|dao|dto|entity|repository|adapter)`));
84
153
  if (!m) continue;
85
154
  const segs = m[1].split("/");
86
155
  for (let len = Math.min(4, segs.length); len >= 1; len--) {
@@ -134,12 +203,11 @@ async function scanJavaDomains(stack, ROOT) {
134
203
  // controller/`), where the module's own sub-package plays the role of the
135
204
  // Initializr base package and the domains again come from class names.
136
205
  const rootPkgPath = rootPackage ? rootPackage.replace(/\./g, "/") : null;
137
- const escRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
138
206
  const flatDirCache = new Map();
139
207
  const LAYER_CLASS_RE = /^(?:controller|service|mapper|repository|dao|dto)\/([A-Za-z0-9]+?)(?:Controller|Service|Mapper|Repository|Dao|Dto)\.java$/;
140
208
  const isFlatBase = (base) => {
141
209
  if (!flatDirCache.has(base)) {
142
- const dirRe = new RegExp(`(^|/)src/main/java/${escRe(base)}/`);
210
+ const dirRe = new RegExp(`(^|/)(?:${JAVA_ROOT_ALT})/${escRe(base)}/`);
143
211
  const under = [];
144
212
  for (const x of javaFiles) {
145
213
  const m = x.match(dirRe);
@@ -157,7 +225,7 @@ async function scanJavaDomains(stack, ROOT) {
157
225
  // lives, independent of `rootPackage` (which is capped at 4 segments and
158
226
  // therefore misses `kr/co/<org>/<proj>/<app>` style base packages).
159
227
  const appBases = [...new Set(javaFiles
160
- .map(f => (f.match(/src\/main\/java\/(.+)\/[A-Za-z0-9]*Application\.java$/) || [])[1])
228
+ .map(f => (f.match(new RegExp(`(?:${JAVA_ROOT_ALT})/(.+)/[A-Za-z0-9]*Application\\.java$`)) || [])[1])
161
229
  .filter(Boolean))];
162
230
  const isFlatLayerPath = (f, layerSegment) => {
163
231
  if (!rootPkgPath && appBases.length === 0) return false;
@@ -165,7 +233,7 @@ async function scanJavaDomains(stack, ROOT) {
165
233
  const pre = modulePrefixes.find(p => p && f.startsWith(p));
166
234
  if (pre && rootPkgPath) bases.push(`${rootPkgPath}/${pre.replace(/\/$/, "").split("/").pop()}`);
167
235
  for (const base of bases) {
168
- const layerRe = new RegExp(`(^|/)src/main/java/${escRe(base)}/${escRe(layerSegment)}/[^/]+\\.java$`);
236
+ const layerRe = new RegExp(`(^|/)(?:${JAVA_ROOT_ALT})/${escRe(base)}/${escRe(layerSegment)}/[^/]+\\.java$`);
169
237
  if (layerRe.test(f) && isFlatBase(base)) return true;
170
238
  }
171
239
  return false;