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 +1 -1
- package/plan-installer/index.js +160 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeos-core",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|
package/plan-installer/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
255
|
-
|
|
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);
|