claudeos-core 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeos-core",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Auto-generate Claude Code documentation from your actual source code — Standards, Rules, Skills, and Guides tailored to your project",
5
5
  "main": "health-checker/index.js",
6
6
  "bin": {
@@ -181,6 +181,27 @@ async function detectStack() {
181
181
  }
182
182
  }
183
183
 
184
+ // ── Config file fallback (모노레포: package.json에 없어도 설정 파일로 감지) ──
185
+ if (!stack.frontend) {
186
+ const nextConfigs = ["next.config.js", "next.config.mjs", "next.config.ts"];
187
+ if (nextConfigs.some(c => fs.existsSync(path.join(ROOT, c)))) {
188
+ stack.frontend = "nextjs"; stack.detected.push("next.config (fallback)");
189
+ if (!stack.language) stack.language = "typescript";
190
+ }
191
+ }
192
+ if (!stack.frontend) {
193
+ if (fs.existsSync(path.join(ROOT, "vite.config.ts")) || fs.existsSync(path.join(ROOT, "vite.config.js"))) {
194
+ if (!stack.frontend) { stack.frontend = "react"; stack.detected.push("vite.config (fallback)"); }
195
+ if (!stack.language) stack.language = "typescript";
196
+ }
197
+ }
198
+ if (!stack.frontend) {
199
+ if (fs.existsSync(path.join(ROOT, "nuxt.config.ts")) || fs.existsSync(path.join(ROOT, "nuxt.config.js"))) {
200
+ stack.frontend = "vue"; stack.detected.push("nuxt.config (fallback)");
201
+ if (!stack.language) stack.language = "typescript";
202
+ }
203
+ }
204
+
184
205
  return stack;
185
206
  }
186
207
 
@@ -194,24 +215,117 @@ async function scanStructure(stack) {
194
215
  if (stack.language === "java") {
195
216
  const javaFiles = await glob("src/main/java/**/*.java", { cwd: ROOT });
196
217
  for (const f of javaFiles) {
197
- const m = f.match(/src\/main\/java\/(.+?)\/(controller|service|mapper|dto|entity|repository)/);
218
+ const m = f.match(/src\/main\/java\/(.+?)\/(controller|service|mapper|dto|entity|repository|adapter)/);
198
219
  if (m) { rootPackage = m[1].replace(/\//g, "."); break; }
199
220
  }
200
- const controllers = await glob("src/main/java/**/controller/*/*.java", { cwd: ROOT });
201
221
  const domainMap = {};
202
- for (const f of controllers) {
222
+ let detectedPattern = null;
223
+
224
+ // Pattern A: controller/{domain}/*.java (레이어 우선 — controller 아래에 도메인)
225
+ const controllersA = await glob("src/main/java/**/controller/*/*.java", { cwd: ROOT });
226
+ for (const f of controllersA) {
203
227
  const m = f.match(/controller\/([^/]+)\//);
204
228
  if (m) {
205
229
  const d = m[1];
206
- if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0 };
230
+ if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "A" };
207
231
  domainMap[d].controllers++;
208
232
  }
209
233
  }
234
+ if (Object.keys(domainMap).length > 0) detectedPattern = "A";
235
+
236
+ // Pattern B/D: {domain}/controller/*.java (도메인 우선 — 도메인 아래에 controller)
237
+ // D는 B의 확장: {module}/{domain}/controller/ — 동일 도메인명 충돌 시 module/domain 형태로 전환
238
+ if (!detectedPattern) {
239
+ const controllersB = await glob("src/main/java/**/*/controller/*.java", { cwd: ROOT });
240
+ const domainPaths = {};
241
+ for (const f of controllersB) {
242
+ const m = f.match(/\/([^/]+)\/controller\/[^/]+\.java$/);
243
+ if (m) {
244
+ const d = m[1];
245
+ const parentMatch = f.match(/\/([^/]+)\/([^/]+)\/controller\//);
246
+ const parentModule = parentMatch ? parentMatch[1] : null;
247
+ if (!domainPaths[d]) domainPaths[d] = [];
248
+ domainPaths[d].push({ file: f, module: parentModule });
249
+ }
250
+ }
251
+
252
+ // 동일 도메인명이 여러 모듈에서 발견되면 module/domain 형태로 (Pattern D)
253
+ for (const [d, entries] of Object.entries(domainPaths)) {
254
+ const modules = [...new Set(entries.map(e => e.module).filter(Boolean))];
255
+ if (modules.length > 1) {
256
+ // Pattern D: 충돌 — module/domain 형태로 등록
257
+ for (const entry of entries) {
258
+ const fullName = entry.module ? `${entry.module}/${d}` : d;
259
+ if (!domainMap[fullName]) domainMap[fullName] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "D", modulePath: entry.module, domainName: d };
260
+ domainMap[fullName].controllers++;
261
+ }
262
+ } else {
263
+ if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "B" };
264
+ domainMap[d].controllers += entries.length;
265
+ }
266
+ }
267
+ if (Object.keys(domainMap).length > 0) detectedPattern = domainMap[Object.keys(domainMap)[0]].pattern;
268
+ }
269
+
270
+ // Pattern E: DDD/헥사고날 — {domain}/adapter/in/web/*.java 또는 {domain}/adapter/in/rest/*.java
271
+ if (!detectedPattern) {
272
+ const controllersE = await glob("src/main/java/**/adapter/in/{web,rest}/*.java", { cwd: ROOT });
273
+ for (const f of controllersE) {
274
+ const m = f.match(/\/([^/]+)\/adapter\/in\/(web|rest)\/[^/]+\.java$/);
275
+ if (m) {
276
+ const d = m[1];
277
+ if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "E" };
278
+ domainMap[d].controllers++;
279
+ }
280
+ }
281
+ if (Object.keys(domainMap).length > 0) detectedPattern = "E";
282
+ }
283
+
284
+ // Pattern C: 플랫 구조 — controller/*.java (도메인 디렉토리 없음, 클래스명에서 도메인 추출)
285
+ if (!detectedPattern) {
286
+ const controllersC = await glob("src/main/java/**/controller/*.java", { cwd: ROOT });
287
+ for (const f of controllersC) {
288
+ const m = f.match(/\/([A-Z][a-zA-Z]*)Controller\.java$/);
289
+ if (m) {
290
+ const d = m[1].toLowerCase();
291
+ if (!domainMap[d]) domainMap[d] = { controllers: 0, services: 0, mappers: 0, dtos: 0, xmlMappers: 0, pattern: "C" };
292
+ domainMap[d].controllers++;
293
+ }
294
+ }
295
+ if (Object.keys(domainMap).length > 0) detectedPattern = "C";
296
+ }
297
+
298
+ // 각 도메인의 service/mapper/dto/xml 파일 스캔
210
299
  for (const d of Object.keys(domainMap)) {
211
- const svc = await glob(`src/main/java/**/service/${d}/*.java`, { cwd: ROOT });
212
- const mpr = await glob(`src/main/java/**/{mapper,repository}/${d}/*.java`, { cwd: ROOT });
213
- const dto = await glob(`src/main/java/**/dto/${d}/**/*.java`, { cwd: ROOT });
214
- const xml = await glob(`src/main/resources/mapper/**/${d}/*.xml`, { cwd: ROOT });
300
+ const p = domainMap[d].pattern;
301
+ const dn = domainMap[d].domainName || d;
302
+ let svcGlob, mprGlob, dtoGlob;
303
+
304
+ if (p === "A") {
305
+ svcGlob = `src/main/java/**/service/${d}/*.java`;
306
+ mprGlob = `src/main/java/**/{mapper,repository}/${d}/*.java`;
307
+ dtoGlob = `src/main/java/**/dto/${d}/**/*.java`;
308
+ } else if (p === "B" || p === "D") {
309
+ svcGlob = `src/main/java/**/${dn}/service/*.java`;
310
+ mprGlob = `src/main/java/**/${dn}/{mapper,repository}/*.java`;
311
+ dtoGlob = `src/main/java/**/${dn}/dto/**/*.java`;
312
+ } else if (p === "E") {
313
+ svcGlob = `src/main/java/**/${d}/{application,domain}/**/*.java`;
314
+ mprGlob = `src/main/java/**/${d}/adapter/out/{persistence,repository}/*.java`;
315
+ dtoGlob = `src/main/java/**/${d}/**/{dto,command,query}/**/*.java`;
316
+ } else {
317
+ // Pattern C: 플랫 — 도메인명으로 파일명 매칭
318
+ const cap = d.charAt(0).toUpperCase() + d.slice(1);
319
+ svcGlob = `src/main/java/**/service/${cap}*.java`;
320
+ mprGlob = `src/main/java/**/{mapper,repository}/${cap}*.java`;
321
+ dtoGlob = `src/main/java/**/dto/${cap}*.java`;
322
+ }
323
+ const xmlGlob = `src/main/resources/mapper/**/${dn}/*.xml`;
324
+
325
+ const svc = await glob(svcGlob, { cwd: ROOT });
326
+ const mpr = await glob(mprGlob, { cwd: ROOT });
327
+ const dto = await glob(dtoGlob, { cwd: ROOT });
328
+ const xml = await glob(xmlGlob, { cwd: ROOT });
215
329
  domainMap[d].services = svc.length;
216
330
  domainMap[d].mappers = mpr.length;
217
331
  domainMap[d].dtos = dto.length;
@@ -240,6 +354,7 @@ async function scanStructure(stack) {
240
354
 
241
355
  // ── Next.js/React/Vue ──
242
356
  if (stack.frontend === "nextjs" || stack.frontend === "react" || stack.frontend === "vue") {
357
+ // App Router / Pages Router 도메인
243
358
  const allDirs = [
244
359
  ...await glob("{app,src/app}/*/", { cwd: ROOT }),
245
360
  ...await glob("{pages,src/pages}/*/", { cwd: ROOT }),
@@ -251,10 +366,45 @@ async function scanStructure(stack) {
251
366
  const files = await glob(`${dir}**/*.{tsx,jsx,ts,js}`, { cwd: ROOT });
252
367
  if (files.length > 0) {
253
368
  const pages = files.filter(f => /page\.|index\./.test(f)).length;
254
- const components = files.filter(f => !/page\.|layout\.|index\./.test(f)).length;
255
- frontendDomains.push({ name, type: "frontend", pages, components, totalFiles: files.length });
369
+ const layouts = files.filter(f => /layout\./.test(f)).length;
370
+ const clientFiles = files.filter(f => /client\./.test(f)).length;
371
+ const serverFiles = pages + layouts;
372
+ const components = files.filter(f => !/page\.|layout\.|index\.|client\./.test(f)).length;
373
+ frontendDomains.push({
374
+ name, type: "frontend", pages, layouts, clientFiles, serverFiles, components, totalFiles: files.length,
375
+ rscPattern: clientFiles > 0 ? "RSC+Client split" : "default",
376
+ });
256
377
  }
257
378
  }
379
+
380
+ // App Router RSC/Client 전체 통계 (project-analysis.json용)
381
+ if (stack.frontend === "nextjs") {
382
+ const allClientFiles = await glob("{app,src/app}/**/client.{tsx,ts,jsx,js}", { cwd: ROOT });
383
+ const allPageFiles = await glob("{app,src/app}/**/page.{tsx,ts,jsx,js}", { cwd: ROOT });
384
+ const allLayoutFiles = await glob("{app,src/app}/**/layout.{tsx,ts,jsx,js}", { cwd: ROOT });
385
+ frontend.clientComponents = allClientFiles.length;
386
+ frontend.serverPages = allPageFiles.length;
387
+ frontend.layouts = allLayoutFiles.length;
388
+ frontend.rscPattern = allClientFiles.length > 0;
389
+ }
390
+
391
+ // FSD (Feature-Sliced Design): features/*, widgets/*, entities/*
392
+ const fsdLayers = ["features", "widgets", "entities"];
393
+ for (const layer of fsdLayers) {
394
+ const fsdDirs = await glob(`{${layer},src/${layer}}/*/`, { cwd: ROOT });
395
+ for (const dir of fsdDirs) {
396
+ const name = path.basename(dir);
397
+ if (["ui", "common", "shared", "lib", "config", "index"].includes(name)) continue;
398
+ const files = await glob(`${dir}**/*.{tsx,jsx,ts,js}`, { cwd: ROOT, ignore: ["**/*.spec.*", "**/*.test.*", "**/*.stories.*"] });
399
+ if (files.length > 0) {
400
+ const uiFiles = files.filter(f => /\bui\b/.test(f)).length;
401
+ const modelFiles = files.filter(f => /model|store|hook/.test(f)).length;
402
+ frontendDomains.push({ name: `${layer}/${name}`, type: "frontend", components: uiFiles, models: modelFiles, totalFiles: files.length });
403
+ }
404
+ }
405
+ }
406
+
407
+ // components/* (기존)
258
408
  const compDirs = await glob("{src/,}components/*/", { cwd: ROOT });
259
409
  for (const dir of compDirs) {
260
410
  const name = path.basename(dir);