@gaonjs/cli 0.5.0 → 0.10.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.
- package/dist/commands/check.d.ts +21 -2
- package/dist/commands/check.js +70 -7
- package/dist/commands/db.d.ts +3 -1
- package/dist/commands/db.js +8 -2
- package/dist/commands/g.d.ts +1 -1
- package/dist/commands/g.js +27 -3
- package/dist/commands/mcp.d.ts +15 -0
- package/dist/commands/mcp.js +78 -0
- package/dist/db/diff.js +5 -0
- package/dist/db/journal.d.ts +34 -0
- package/dist/db/journal.js +71 -0
- package/dist/db/migrate.d.ts +6 -1
- package/dist/db/migrate.js +120 -102
- package/dist/db/replay.d.ts +49 -0
- package/dist/db/replay.js +148 -0
- package/dist/db/status.d.ts +12 -0
- package/dist/db/status.js +61 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/vite.d.ts +67 -0
- package/dist/dev/vite.js +126 -0
- package/dist/dev.d.ts +18 -0
- package/dist/dev.js +15 -0
- package/dist/doctor/agents-doc-index.d.ts +4 -0
- package/dist/doctor/agents-doc-index.js +80 -0
- package/dist/doctor/fixers/dependency-direction.d.ts +9 -0
- package/dist/doctor/fixers/dependency-direction.js +98 -0
- package/dist/doctor/fixers/index.d.ts +15 -0
- package/dist/doctor/fixers/index.js +66 -0
- package/dist/doctor/fixers/schema-filename.d.ts +14 -0
- package/dist/doctor/fixers/schema-filename.js +104 -0
- package/dist/doctor/fixers/types.d.ts +59 -0
- package/dist/doctor/fixers/types.js +15 -0
- package/dist/doctor/schema-filename.d.ts +6 -0
- package/dist/doctor/schema-filename.js +81 -0
- package/dist/doctor/types.d.ts +1 -1
- package/dist/doctor.d.ts +49 -0
- package/dist/doctor.js +179 -5
- package/dist/generate.js +2 -2
- package/dist/hub.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +50 -10
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/server.d.ts +50 -0
- package/dist/mcp/server.js +102 -0
- package/dist/mcp/tools.d.ts +109 -0
- package/dist/mcp/tools.js +485 -0
- package/dist/scaffold/app.d.ts +5 -0
- package/dist/scaffold/app.js +172 -0
- package/dist/scaffold/controller.js +2 -2
- package/dist/scaffold/index.d.ts +2 -1
- package/dist/scaffold/index.js +2 -1
- package/dist/scaffold/job.d.ts +5 -0
- package/dist/scaffold/job.js +35 -0
- package/dist/scaffold/model.js +8 -8
- package/dist/templates/auth/auth.wiring.ts.tpl +1 -1
- package/dist/templates/auth/registration.controller.ts.tpl +1 -1
- package/dist/templates/auth/session.controller.ts.tpl +1 -1
- package/dist/templates/auth/user.model.ts.tpl +1 -1
- package/dist/templates/project/AGENTS.md.tpl +214 -0
- package/dist/templates/project/agents/async.md.tpl +218 -0
- package/dist/templates/project/agents/data.md.tpl +556 -0
- package/dist/templates/project/agents/frontend.md.tpl +201 -0
- package/dist/templates/project/agents/realtime.md.tpl +157 -0
- package/dist/templates/project/agents/security.md.tpl +92 -0
- package/dist/templates/project/agents/testing.md.tpl +101 -0
- package/dist/templates/project/agents/web.md.tpl +177 -0
- package/dist/templates/project/apps/web/index.html.tpl +18 -0
- package/dist/templates/project/apps/web/main.ts.tpl +24 -0
- package/dist/templates/project/package.json.tpl +5 -2
- package/dist/templates/project/vite.config.ts.tpl +23 -0
- package/dist/tsResolve.js +1 -1
- package/dist/work.d.ts +2 -2
- package/dist/work.js +3 -1
- package/package.json +13 -11
- package/dist/__fixtures__/db-minimal/domain/schema/widgets.d.ts +0 -12
- package/dist/__fixtures__/db-minimal/domain/schema/widgets.js +0 -7
- package/dist/__fixtures__/db-minimal/gaon.config.d.ts +0 -2
- package/dist/__fixtures__/db-minimal/gaon.config.js +0 -11
- package/dist/check.d.ts +0 -29
- package/dist/check.js +0 -92
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// @gaonjs/cli · doctor · 스키마 파일명 관례 (결정 38 · 2026-07-25)
|
|
2
|
+
//
|
|
3
|
+
// 정본 §1.1: 스키마 파일명 = 테이블명을 **camelCase** 로 바꾼 것. 테이블명에
|
|
4
|
+
// `_` 가 있어도 파일명엔 `_` 를 쓰지 않는다 — 조인 테이블 `posts_tags` →
|
|
5
|
+
// `domain/schema/postsTags.ts` (파일 안 `table('posts_tags', …)` 는 스네이크
|
|
6
|
+
// 유지). 소형 모델(haiku)이 다단어 테이블에서 파일명을 스네이크로 쓰는
|
|
7
|
+
// 실측(feature-slice T4)이 있어, 잘못된 파일명을 검출해 §7.5.3 수리 안내를
|
|
8
|
+
// 준다. `The One Way` — 두 형태 허용 없이 camelCase 하나로 못박는다.
|
|
9
|
+
//
|
|
10
|
+
// 판정 범위: **파일명이 camelCase 가 아닌 경우만**(스네이크·케밥·Pascal) 잡는다.
|
|
11
|
+
// 단수/복수(`post.ts` vs `posts.ts`)는 이 결정의 대상이 아니라 건드리지 않는다
|
|
12
|
+
// — 기존 fixture 가 단수 파일명(`post.ts`)을 쓰므로 그것까지 실격시키면 결정
|
|
13
|
+
// 범위를 넘는다. 그래서 기대 파일명은 **실제 파일명 stem 을 camelCase 로 정규화**
|
|
14
|
+
// 한 값이다(테이블명 단수화가 아님). 이미 camelCase 면 정규화해도 그대로라 통과.
|
|
15
|
+
// table() 선언이 있는 파일만 대상으로 삼아 배럴·비스키마 파일 오탐을 막는다.
|
|
16
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
17
|
+
import { basename, join, relative } from 'node:path';
|
|
18
|
+
import { toCamel, toPascal } from '../scaffold/inflect.js';
|
|
19
|
+
/** 파일 stem 을 camelCase 로 정규화(`posts_tags`→`postsTags` · `Posts`→`posts` · `posts`→`posts`). */
|
|
20
|
+
export function expectedSchemaStem(stem) {
|
|
21
|
+
return toCamel(toPascal(stem));
|
|
22
|
+
}
|
|
23
|
+
/** 소스에서 첫 `table('<이름>', …)` 의 테이블명을 뽑는다(없으면 undefined). */
|
|
24
|
+
export function extractTableName(source) {
|
|
25
|
+
const m = source.match(/\btable\(\s*['"`]([^'"`]+)['"`]/);
|
|
26
|
+
return m ? m[1] : undefined;
|
|
27
|
+
}
|
|
28
|
+
export async function checkSchemaFilename(cwd) {
|
|
29
|
+
const schemaDir = join(cwd, 'domain', 'schema');
|
|
30
|
+
const files = [];
|
|
31
|
+
await collectTsFiles(schemaDir, files);
|
|
32
|
+
const issues = [];
|
|
33
|
+
for (const abs of files) {
|
|
34
|
+
const source = await readFile(abs, 'utf8');
|
|
35
|
+
const tableName = extractTableName(source);
|
|
36
|
+
if (!tableName)
|
|
37
|
+
continue; // table() 없음 = 스키마 테이블 파일 아님 · 건너뜀
|
|
38
|
+
const actualStem = basename(abs).replace(/\.ts$/, '');
|
|
39
|
+
const expected = expectedSchemaStem(actualStem);
|
|
40
|
+
if (actualStem === expected)
|
|
41
|
+
continue; // 이미 camelCase(단수/복수 무관) · 통과
|
|
42
|
+
const rel = relative(cwd, abs).split('\\').join('/');
|
|
43
|
+
const expectedRel = rel.replace(/[^/]+\.ts$/, `${expected}.ts`);
|
|
44
|
+
issues.push({
|
|
45
|
+
rule: 'schema-filename',
|
|
46
|
+
level: 'error',
|
|
47
|
+
file: rel,
|
|
48
|
+
message: `스키마 파일명이 관례(§1.1 · camelCase)와 어긋납니다: '${actualStem}.ts' ` +
|
|
49
|
+
`(테이블 '${tableName}' · 기대 파일명 '${expected}.ts').\n` +
|
|
50
|
+
`→ '${rel}' 를 '${expectedRel}' 로 이름을 바꾸고, 이 스키마를 import 하는 ` +
|
|
51
|
+
`모델(예: '../schema/${expected}.js')의 경로도 함께 고치세요. ` +
|
|
52
|
+
`테이블명 문자열('${tableName}')은 그대로 둡니다.`,
|
|
53
|
+
detail: { tableName, actual: `${actualStem}.ts`, expected: `${expected}.ts` },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { rule: 'schema-filename', issues };
|
|
57
|
+
}
|
|
58
|
+
/** domain/schema/ 아래 .ts(선언·테스트 제외)를 재귀 수집. migration-diff 와 동일 패턴. */
|
|
59
|
+
async function collectTsFiles(root, out) {
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = (await readdir(root, { withFileTypes: true }));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return; // domain/schema/ 없음 = 검사할 것 없음
|
|
66
|
+
}
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
const p = join(root, e.name);
|
|
69
|
+
if (e.isDirectory()) {
|
|
70
|
+
await collectTsFiles(p, out);
|
|
71
|
+
}
|
|
72
|
+
else if (e.isFile() &&
|
|
73
|
+
e.name.endsWith('.ts') &&
|
|
74
|
+
!e.name.endsWith('.d.ts') &&
|
|
75
|
+
!e.name.endsWith('.test.ts')) {
|
|
76
|
+
const s = await stat(p);
|
|
77
|
+
if (s.isFile())
|
|
78
|
+
out.push(p);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-composable-purity' | 'no-auto-import';
|
|
1
|
+
export type DoctorRule = 'response-mixing' | 'n-plus-one' | 'dependency-direction' | 'connections' | 'migration-diff' | 'shared-composable-purity' | 'no-auto-import' | 'schema-filename' | 'agents-doc-index';
|
|
2
2
|
export type DoctorLevel = 'passed' | 'warning' | 'error';
|
|
3
3
|
export interface DoctorCheck {
|
|
4
4
|
readonly rule: DoctorRule;
|
package/dist/doctor.d.ts
CHANGED
|
@@ -8,12 +8,45 @@ export { extractConfigDbKeys, extractKeyUses, checkConnections } from './doctor/
|
|
|
8
8
|
export { scanSchema, checkMigrationDiff } from './doctor/migration-diff.js';
|
|
9
9
|
export { inspectSharedComposable, checkSharedComposablePurity, } from './doctor/shared-composable-purity.js';
|
|
10
10
|
export { inspectConfigForAutoImport, inspectPackageJson, checkNoAutoImport, } from './doctor/no-auto-import.js';
|
|
11
|
+
export { expectedSchemaStem, extractTableName, checkSchemaFilename, } from './doctor/schema-filename.js';
|
|
12
|
+
export { extractAgentDocRefs, checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
11
13
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
12
14
|
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
13
15
|
export interface DoctorCommandOptions {
|
|
14
16
|
readonly cwd?: string;
|
|
15
17
|
readonly json?: boolean;
|
|
16
18
|
readonly checks?: readonly DoctorRule[];
|
|
19
|
+
/**
|
|
20
|
+
* 자동 정정 모드(v0.16 §7.5.3). true 면 각 규칙의 fixer 를 계획한다.
|
|
21
|
+
* `yes` 가 없으면 dry-run(계획만 보고 · 파일 편집 X), 있으면 실제 편집.
|
|
22
|
+
* 편집 전 원본은 `<파일>.bak-<타임스탬프>` 로 백업된다(--yes 여도 안전망).
|
|
23
|
+
*/
|
|
24
|
+
readonly fix?: boolean;
|
|
25
|
+
/** 실제 파일 편집 승인. `fix` 와 함께 줘야 편집이 일어난다. */
|
|
26
|
+
readonly yes?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** --fix 리포트의 파일별 결과. FixerPlan 위에 dry/applied 상태를 얹는다. */
|
|
29
|
+
export interface FixOutcome {
|
|
30
|
+
readonly rule: DoctorRule;
|
|
31
|
+
readonly file: string;
|
|
32
|
+
readonly summary: string;
|
|
33
|
+
/** true 이면 실제 편집됨, false 이면 dry-run(계획만 · --yes 없음). */
|
|
34
|
+
readonly applied: boolean;
|
|
35
|
+
/** 실제 편집 시 원본 백업 경로(프로젝트 상대). dry-run 은 undefined. */
|
|
36
|
+
readonly backup?: string;
|
|
37
|
+
}
|
|
38
|
+
/** --fix 실행 결과. runCli 는 fixesApplied · fixesPlanned 로 종료 코드 결정. */
|
|
39
|
+
export interface DoctorFixReport {
|
|
40
|
+
/** fixer 가 있는 규칙별 계획·적용 결과. */
|
|
41
|
+
readonly outcomes: readonly FixOutcome[];
|
|
42
|
+
/** fixer 가 없어서 수동 수정이 필요한 규칙별 위반 수(요약). */
|
|
43
|
+
readonly manualRequired: readonly {
|
|
44
|
+
rule: DoctorRule;
|
|
45
|
+
issueCount: number;
|
|
46
|
+
note: string;
|
|
47
|
+
}[];
|
|
48
|
+
/** 실제로 편집이 일어났는지(--yes 여부). */
|
|
49
|
+
readonly applied: boolean;
|
|
17
50
|
}
|
|
18
51
|
/**
|
|
19
52
|
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
@@ -35,6 +68,22 @@ export interface DoctorCommandOptions {
|
|
|
35
68
|
* 나머지 규칙은 계속 실행(부분 결과 확보).
|
|
36
69
|
*/
|
|
37
70
|
export declare function runDoctorCommand(opts?: DoctorCommandOptions): Promise<DoctorResult>;
|
|
71
|
+
/**
|
|
72
|
+
* fixer 를 계획·(승인 시)실행한다. 파일 편집은 여기서만 일어난다 —
|
|
73
|
+
* fixer 자체는 순수 계산 함수.
|
|
74
|
+
*
|
|
75
|
+
* 안전:
|
|
76
|
+
* 1) apply=false(=--yes 없음) 면 dry-run — 파일을 만지지 않는다.
|
|
77
|
+
* 2) apply=true 면 원본을 `<파일>.bak-<타임스탬프>` 로 백업 후 편집.
|
|
78
|
+
* 3) 편집 중 예외가 나면 그 파일만 실패로 리포트하고 다음 파일을 계속.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runDoctorFix(reports: readonly RuleReport[], cwd: string, apply: boolean): Promise<DoctorFixReport>;
|
|
81
|
+
/** 리포트에 함께 실려 나가는 확장 결과. runCli 가 종료 코드를 결정하는 근거. */
|
|
82
|
+
export interface DoctorResultWithFix extends DoctorResult {
|
|
83
|
+
readonly fix: DoctorFixReport;
|
|
84
|
+
}
|
|
85
|
+
/** 사람 친화 fix 리포트. json 은 상위 renderJson 이 그대로 직렬화. */
|
|
86
|
+
export declare function renderFixHuman(report: DoctorFixReport): string;
|
|
38
87
|
export interface DoctorIssue {
|
|
39
88
|
readonly file: string;
|
|
40
89
|
readonly line: number;
|
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @gaonjs/cli · `gaon doctor` — 정적 검사 (M9-E · CLI DX 완성 · E-5 확장)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 8 검사를 조립한다:
|
|
5
5
|
* 1) response-mixing (errata E-3 §C · 라이브)
|
|
6
6
|
* 2) n-plus-one (errata E-4 (e))
|
|
7
7
|
* 3) dependency-direction (CLAUDE.md §5 · 4 규칙)
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 5) migration-diff (최소 감지 · 상세는 M9-D)
|
|
10
10
|
* 6) shared-composable-purity (errata E-5 §2.2 · 결정 25)
|
|
11
11
|
* 7) no-auto-import (errata E-5 §2.4 · v0.15 §1.2)
|
|
12
|
+
* 8) schema-filename (§1.1 · 결정 38 · 스키마 파일명 camelCase)
|
|
12
13
|
*
|
|
13
14
|
* 각 검사는 순수 함수(cwd → RuleReport). 상위 runDoctorCommand 가 조립해
|
|
14
15
|
* DoctorResult 로 낸다. --json 은 자동화(CI)를 위해 반드시 파싱 가능한
|
|
@@ -17,7 +18,9 @@
|
|
|
17
18
|
* 하위 호환: 기존 응답 혼용 API(`inspectControllerSource`·`runDoctor`)는
|
|
18
19
|
* 그대로 export — 기존 테스트가 계속 동작한다.
|
|
19
20
|
*/
|
|
20
|
-
import {
|
|
21
|
+
import { rename, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { join, resolve } from 'node:path';
|
|
21
24
|
import ts from 'typescript';
|
|
22
25
|
import { checkResponseMixing } from './doctor/response-mixing.js';
|
|
23
26
|
import { checkNPlusOne } from './doctor/n-plus-one.js';
|
|
@@ -26,9 +29,12 @@ import { checkConnections } from './doctor/connections.js';
|
|
|
26
29
|
import { checkMigrationDiff } from './doctor/migration-diff.js';
|
|
27
30
|
import { checkSharedComposablePurity } from './doctor/shared-composable-purity.js';
|
|
28
31
|
import { checkNoAutoImport } from './doctor/no-auto-import.js';
|
|
32
|
+
import { checkSchemaFilename } from './doctor/schema-filename.js';
|
|
33
|
+
import { checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
29
34
|
import { renderHuman, renderJson } from './doctor/reporter.js';
|
|
30
35
|
import { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
31
36
|
import { makeResult, } from './doctor/types.js';
|
|
37
|
+
import { FIXERS, FIXER_CAPABILITIES } from './doctor/fixers/index.js';
|
|
32
38
|
export { inspectControllerSource, checkResponseMixing } from './doctor/response-mixing.js';
|
|
33
39
|
export { inspectControllerForNPlusOne, checkNPlusOne } from './doctor/n-plus-one.js';
|
|
34
40
|
export { extractRelativeImports, checkDependencyDirection } from './doctor/dependency-direction.js';
|
|
@@ -36,6 +42,8 @@ export { extractConfigDbKeys, extractKeyUses, checkConnections } from './doctor/
|
|
|
36
42
|
export { scanSchema, checkMigrationDiff } from './doctor/migration-diff.js';
|
|
37
43
|
export { inspectSharedComposable, checkSharedComposablePurity, } from './doctor/shared-composable-purity.js';
|
|
38
44
|
export { inspectConfigForAutoImport, inspectPackageJson, checkNoAutoImport, } from './doctor/no-auto-import.js';
|
|
45
|
+
export { expectedSchemaStem, extractTableName, checkSchemaFilename, } from './doctor/schema-filename.js';
|
|
46
|
+
export { extractAgentDocRefs, checkAgentsDocIndex } from './doctor/agents-doc-index.js';
|
|
39
47
|
export { renderHuman, renderJson } from './doctor/reporter.js';
|
|
40
48
|
export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
|
|
41
49
|
/**
|
|
@@ -49,6 +57,8 @@ const ALL_RULES = [
|
|
|
49
57
|
'migration-diff',
|
|
50
58
|
'shared-composable-purity',
|
|
51
59
|
'no-auto-import',
|
|
60
|
+
'schema-filename',
|
|
61
|
+
'agents-doc-index',
|
|
52
62
|
];
|
|
53
63
|
const CHECKERS = {
|
|
54
64
|
'response-mixing': checkResponseMixing,
|
|
@@ -58,6 +68,8 @@ const CHECKERS = {
|
|
|
58
68
|
'migration-diff': checkMigrationDiff,
|
|
59
69
|
'shared-composable-purity': checkSharedComposablePurity,
|
|
60
70
|
'no-auto-import': checkNoAutoImport,
|
|
71
|
+
'schema-filename': checkSchemaFilename,
|
|
72
|
+
'agents-doc-index': checkAgentsDocIndex,
|
|
61
73
|
};
|
|
62
74
|
/**
|
|
63
75
|
* `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
|
|
@@ -130,10 +142,172 @@ export async function runDoctorCommand(opts = {}) {
|
|
|
130
142
|
});
|
|
131
143
|
}
|
|
132
144
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
145
|
+
const baseResult = makeResult(reports);
|
|
146
|
+
// --fix 가 없으면 종전 동작 그대로.
|
|
147
|
+
if (!opts.fix) {
|
|
148
|
+
const out = json ? renderJson(baseResult) : renderHuman(baseResult);
|
|
149
|
+
process.stdout.write(out + '\n');
|
|
150
|
+
return baseResult;
|
|
151
|
+
}
|
|
152
|
+
// --fix: fixer 를 계획하고, --yes 이면 실제 편집한다. reports 를 그대로
|
|
153
|
+
// 넘겨 규칙별 위반을 fixer 가 판단한다(passed 는 건너뜀).
|
|
154
|
+
const fixReport = await runDoctorFix(reports, root, opts.yes === true);
|
|
155
|
+
const resultWithFix = { ...baseResult, fix: fixReport };
|
|
156
|
+
const out = json
|
|
157
|
+
? JSON.stringify(resultWithFix)
|
|
158
|
+
: renderHuman(baseResult) + '\n\n' + renderFixHuman(fixReport);
|
|
135
159
|
process.stdout.write(out + '\n');
|
|
136
|
-
return
|
|
160
|
+
return resultWithFix;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* fixer 를 계획·(승인 시)실행한다. 파일 편집은 여기서만 일어난다 —
|
|
164
|
+
* fixer 자체는 순수 계산 함수.
|
|
165
|
+
*
|
|
166
|
+
* 안전:
|
|
167
|
+
* 1) apply=false(=--yes 없음) 면 dry-run — 파일을 만지지 않는다.
|
|
168
|
+
* 2) apply=true 면 원본을 `<파일>.bak-<타임스탬프>` 로 백업 후 편집.
|
|
169
|
+
* 3) 편집 중 예외가 나면 그 파일만 실패로 리포트하고 다음 파일을 계속.
|
|
170
|
+
*/
|
|
171
|
+
export async function runDoctorFix(reports, cwd, apply) {
|
|
172
|
+
const outcomes = [];
|
|
173
|
+
const manualRequired = [];
|
|
174
|
+
const ts = timestampSuffix(new Date());
|
|
175
|
+
for (const report of reports) {
|
|
176
|
+
const issues = report.issues.filter((i) => i.level !== 'passed');
|
|
177
|
+
if (issues.length === 0)
|
|
178
|
+
continue;
|
|
179
|
+
const fixer = FIXERS[report.rule];
|
|
180
|
+
if (!fixer) {
|
|
181
|
+
const cap = FIXER_CAPABILITIES.find((c) => c.rule === report.rule);
|
|
182
|
+
manualRequired.push({
|
|
183
|
+
rule: report.rule,
|
|
184
|
+
issueCount: issues.length,
|
|
185
|
+
note: cap?.note ?? '수동 수정 필요',
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let plans;
|
|
190
|
+
try {
|
|
191
|
+
plans = await fixer(issues, cwd);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
195
|
+
outcomes.push({
|
|
196
|
+
rule: report.rule,
|
|
197
|
+
file: '(fixer error)',
|
|
198
|
+
summary: `fixer 실행 실패: ${msg}`,
|
|
199
|
+
applied: false,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (const plan of plans) {
|
|
204
|
+
const label = plan.kind === 'rename' ? `${plan.file} → ${plan.to}` : plan.file;
|
|
205
|
+
if (!apply) {
|
|
206
|
+
outcomes.push({
|
|
207
|
+
rule: report.rule,
|
|
208
|
+
file: label,
|
|
209
|
+
summary: plan.summary,
|
|
210
|
+
applied: false,
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
if (plan.kind === 'rename') {
|
|
216
|
+
// 이동 대상이 이미 있으면 데이터를 덮지 않고 건너뛴다(수동 확인).
|
|
217
|
+
if (existsSync(join(cwd, plan.to))) {
|
|
218
|
+
outcomes.push({
|
|
219
|
+
rule: report.rule,
|
|
220
|
+
file: label,
|
|
221
|
+
summary: `이동 대상이 이미 존재해 건너뜀(수동 확인 필요): ${plan.to}`,
|
|
222
|
+
applied: false,
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
// importer 참조를 먼저 갱신(각각 백업), 그다음 파일 이동(내용 불변).
|
|
227
|
+
const backups = [];
|
|
228
|
+
for (const ref of plan.refEdits) {
|
|
229
|
+
const bak = `${ref.file}.bak-${ts}`;
|
|
230
|
+
await writeFile(join(cwd, bak), ref.before, 'utf8');
|
|
231
|
+
await writeFile(join(cwd, ref.file), ref.after, 'utf8');
|
|
232
|
+
backups.push(bak);
|
|
233
|
+
}
|
|
234
|
+
await rename(join(cwd, plan.file), join(cwd, plan.to));
|
|
235
|
+
outcomes.push({
|
|
236
|
+
rule: report.rule,
|
|
237
|
+
file: label,
|
|
238
|
+
summary: plan.summary + (backups.length ? `\n ↳ importer 백업: ${backups.join(', ')}` : ''),
|
|
239
|
+
applied: true,
|
|
240
|
+
backup: backups[0],
|
|
241
|
+
});
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// 내용 재작성 — 원본 백업 먼저.
|
|
245
|
+
const abs = join(cwd, plan.file);
|
|
246
|
+
const backupRel = `${plan.file}.bak-${ts}`;
|
|
247
|
+
await writeFile(join(cwd, backupRel), plan.before, 'utf8');
|
|
248
|
+
await writeFile(abs, plan.after, 'utf8');
|
|
249
|
+
outcomes.push({
|
|
250
|
+
rule: report.rule,
|
|
251
|
+
file: plan.file,
|
|
252
|
+
summary: plan.summary,
|
|
253
|
+
applied: true,
|
|
254
|
+
backup: backupRel,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
259
|
+
outcomes.push({
|
|
260
|
+
rule: report.rule,
|
|
261
|
+
file: label,
|
|
262
|
+
summary: `편집 실패(원본 유지): ${msg}`,
|
|
263
|
+
applied: false,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { outcomes, manualRequired, applied: apply };
|
|
269
|
+
}
|
|
270
|
+
/** 사람 친화 fix 리포트. json 은 상위 renderJson 이 그대로 직렬화. */
|
|
271
|
+
export function renderFixHuman(report) {
|
|
272
|
+
const lines = [];
|
|
273
|
+
const mode = report.applied ? '적용' : 'dry-run(--yes 없음 · 계획만)';
|
|
274
|
+
lines.push(` gaon doctor --fix · ${mode}`);
|
|
275
|
+
if (report.outcomes.length === 0 && report.manualRequired.length === 0) {
|
|
276
|
+
lines.push(' (자동 정정할 대상이 없습니다.)');
|
|
277
|
+
return lines.join('\n');
|
|
278
|
+
}
|
|
279
|
+
for (const o of report.outcomes) {
|
|
280
|
+
const mark = o.applied ? '✓' : '·';
|
|
281
|
+
lines.push('');
|
|
282
|
+
lines.push(` ${mark} [${o.rule}] ${o.file}`);
|
|
283
|
+
for (const ln of o.summary.split('\n'))
|
|
284
|
+
lines.push(` ${ln}`);
|
|
285
|
+
if (o.backup)
|
|
286
|
+
lines.push(` ↳ 원본 백업: ${o.backup}`);
|
|
287
|
+
}
|
|
288
|
+
if (report.manualRequired.length > 0) {
|
|
289
|
+
lines.push('');
|
|
290
|
+
lines.push(' 수동 수정 필요:');
|
|
291
|
+
for (const m of report.manualRequired) {
|
|
292
|
+
lines.push(` · [${m.rule}] 위반 ${m.issueCount}건 · ${m.note}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!report.applied && report.outcomes.some((o) => !o.applied)) {
|
|
296
|
+
lines.push('');
|
|
297
|
+
lines.push(' → 실제 편집은 --fix --yes 로 진행하세요(원본은 자동 백업).');
|
|
298
|
+
}
|
|
299
|
+
return lines.join('\n');
|
|
300
|
+
}
|
|
301
|
+
/** 파일명 안전 문자만으로 만든 로컬 타임스탬프(YYYYMMDD-HHMMSS). */
|
|
302
|
+
function timestampSuffix(d) {
|
|
303
|
+
const pad = (n, w = 2) => String(n).padStart(w, '0');
|
|
304
|
+
const y = d.getFullYear();
|
|
305
|
+
const M = pad(d.getMonth() + 1);
|
|
306
|
+
const D = pad(d.getDate());
|
|
307
|
+
const h = pad(d.getHours());
|
|
308
|
+
const m = pad(d.getMinutes());
|
|
309
|
+
const s = pad(d.getSeconds());
|
|
310
|
+
return `${y}${M}${D}-${h}${m}${s}`;
|
|
137
311
|
}
|
|
138
312
|
/**
|
|
139
313
|
* 하위 호환: 응답 혼용만 검사해 legacy shape 을 낸다.
|
package/dist/generate.js
CHANGED
|
@@ -26,8 +26,8 @@ function renderTemplate(name, app) {
|
|
|
26
26
|
}
|
|
27
27
|
/** 템플릿 파일 → 생성 경로 매핑(라우트 제외 — 라우트는 패치로 처리). */
|
|
28
28
|
const TEMPLATES = [
|
|
29
|
-
{ tpl: 'user.schema.ts.tpl', out: () => 'domain/schema/
|
|
30
|
-
{ tpl: 'user.model.ts.tpl', out: () => 'domain/models/
|
|
29
|
+
{ tpl: 'user.schema.ts.tpl', out: () => 'domain/schema/users.ts' },
|
|
30
|
+
{ tpl: 'user.model.ts.tpl', out: () => 'domain/models/User.ts' },
|
|
31
31
|
{ tpl: 'auth.wiring.ts.tpl', out: (a) => `apps/${a}/auth.ts` },
|
|
32
32
|
{ tpl: 'session.controller.ts.tpl', out: (a) => `apps/${a}/controllers/session.ts` },
|
|
33
33
|
{ tpl: 'registration.controller.ts.tpl', out: (a) => `apps/${a}/controllers/registration.ts` },
|
package/dist/hub.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface HubCommandOptions {
|
|
2
2
|
readonly json?: boolean;
|
|
3
|
-
/** NATS 접속지. 생략 시 GAON_NATS_URL,
|
|
3
|
+
/** NATS 접속지. 생략 시 NATS_URL(그다음 하위호환 GAON_NATS_URL), 기본(4222). */
|
|
4
4
|
readonly natsUrl?: string;
|
|
5
5
|
/** 리스 TTL(ms). 생략 시 env GAON_HUB_TTL_MS, 그다음 허브 기본(5000). */
|
|
6
6
|
readonly ttlMs?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ export { runJobsCommand, type JobsCommandOptions } from "./jobs.js";
|
|
|
15
15
|
export { runDbSeedCommand, loadSeed, type DbSeedOptions, type DbSeedResult } from "./db.js";
|
|
16
16
|
export { runDbCommand, type DbSubcommand, type DbCommandOptions, } from "./commands/db.js";
|
|
17
17
|
export { runDbDiff, runDbMigrate, runDbReset, resolveDbTarget, type DbDiffOptions, type DbDiffResult, type DbMigrateOptions, type DbMigrateResult, type DbResetOptions, type DbResetResult, type ResolveDbOptions, type ResolvedDbTarget, } from "./db/index.js";
|
|
18
|
-
export { runDoctorCommand, runDoctor, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, type DoctorResult, type DoctorCheck, type DoctorLevel, type DoctorRule, type RuleReport, type DoctorIssue, type LegacyDoctorResult, type ActionUsage, type ResponseKind, type DoctorCommandOptions, } from "./doctor.js";
|
|
18
|
+
export { runDoctorCommand, runDoctor, runDoctorFix, renderFixHuman, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, type DoctorResult, type DoctorResultWithFix, type DoctorCheck, type DoctorLevel, type DoctorRule, type RuleReport, type DoctorIssue, type LegacyDoctorResult, type ActionUsage, type ResponseKind, type DoctorCommandOptions, type FixOutcome, type DoctorFixReport, } from "./doctor.js";
|
|
19
|
+
export { FIXERS, FIXER_CAPABILITIES, fixDependencyDirection, fixDomainToSharedTypeOnly, type Fixer, type FixerCapability, type FixerPlan, } from "./doctor/fixers/index.js";
|
|
19
20
|
export { loadDomain, type LoadedDomain } from "./domain.js";
|
|
20
21
|
export interface RoadmapReport {
|
|
21
22
|
readonly name: "gaon";
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { runWorkCommand } from "./work.js";
|
|
|
24
24
|
import { runJobsCommand } from "./jobs.js";
|
|
25
25
|
import { runDbCommand } from "./commands/db.js";
|
|
26
26
|
import { runDoctorCommand } from "./doctor.js";
|
|
27
|
+
import { runMcpCommand } from "./commands/mcp.js";
|
|
27
28
|
export { startDev, resolveDevLayout, } from "./dev.js";
|
|
28
29
|
export { runDevCommand } from "./commands/dev.js";
|
|
29
30
|
export { createDevConsole, findComposeFile, isDockerAvailable, inspectCompose, composeUp, composeDown, ensureInfra, startTscWatchers, killChild, startRestartWatcher, isRestartChange, resolveWatchRoots, } from "./dev/index.js";
|
|
@@ -40,7 +41,8 @@ export { runJobsCommand } from "./jobs.js";
|
|
|
40
41
|
export { runDbSeedCommand, loadSeed } from "./db.js";
|
|
41
42
|
export { runDbCommand, } from "./commands/db.js";
|
|
42
43
|
export { runDbDiff, runDbMigrate, runDbReset, resolveDbTarget, } from "./db/index.js";
|
|
43
|
-
export { runDoctorCommand, runDoctor, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, } from "./doctor.js";
|
|
44
|
+
export { runDoctorCommand, runDoctor, runDoctorFix, renderFixHuman, inspectControllerSource, checkResponseMixing, checkNPlusOne, checkDependencyDirection, checkConnections, checkMigrationDiff, renderHuman, renderJson, } from "./doctor.js";
|
|
45
|
+
export { FIXERS, FIXER_CAPABILITIES, fixDependencyDirection, fixDomainToSharedTypeOnly, } from "./doctor/fixers/index.js";
|
|
44
46
|
export { loadDomain } from "./domain.js";
|
|
45
47
|
/** `--json` 출력용 구조화 리포트. */
|
|
46
48
|
export function roadmapReport(version = VERSION) {
|
|
@@ -99,19 +101,25 @@ function renderHelp(version = VERSION) {
|
|
|
99
101
|
" gaon doctor 정적 검사 (7 검사 · 응답 혼용·N+1·의존·커넥션·마이그·컴포저블 순수·자동 import)",
|
|
100
102
|
" gaon doctor --json 자동화용 JSON 출력",
|
|
101
103
|
" gaon doctor --check=n-plus-one,connections 선택 검사만 실행",
|
|
104
|
+
" gaon doctor --fix 기계 정정 가능한 위반 계획(dry-run · v0.16 §7.5.3)",
|
|
105
|
+
" gaon doctor --fix --yes 실제 편집 적용(원본은 .bak-<타임스탬프> 로 자동 백업)",
|
|
102
106
|
" gaon g auth 인증 스캐폴드 생성 (회원가입·로그인·세션·보호 라우트)",
|
|
103
107
|
" gaon g controller <name> 컨트롤러 스캐폴드 (Rails 관례 · 페이지+JSON 액션)",
|
|
104
108
|
" gaon g model <Name> 모델 스캐폴드 (스키마+모델 · E-4 컬럼 예시)",
|
|
105
109
|
" gaon g page <Path/Name> Vue 페이지 (Inertia SPA · pageProps 브리지)",
|
|
106
110
|
" gaon g job <Name> 비동기 잡 (domain/jobs · later/in/at)",
|
|
111
|
+
" gaon g app <name> 앱 스캐폴드 (apps/<name>/ · routes·controllers·pages·layouts)",
|
|
107
112
|
" gaon g <type> --overwrite 기존 파일 덮어쓰기 · --app <이름> · --json",
|
|
113
|
+
" gaon mcp 내장 MCP 서버 · AI 도구 4종 (list_routes·get_schema·run_migration·run_tests · --http)",
|
|
108
114
|
" gaon hub 실시간 허브 프로세스 (프레즌스 권위·중계 · 리더 선출 HA)",
|
|
109
115
|
" gaon work 워커 프로세스 (잡·리스너·스케줄·아웃박스 · graceful drain)",
|
|
110
116
|
" gaon jobs list --failed DLQ(실패 잡) 목록",
|
|
111
117
|
" gaon jobs retry <id> DLQ 잡 재적재",
|
|
112
|
-
" gaon db diff 스키마 ↔ DB 차이
|
|
113
|
-
" gaon db migrate 스키마
|
|
114
|
-
" gaon db migrate
|
|
118
|
+
" gaon db diff 스키마 ↔ DB 차이 미리보기 (적용 X · --json · --db <키>)",
|
|
119
|
+
" gaon db migrate db/migrations/*.ts replay + 스키마 diff 적용 + 이력",
|
|
120
|
+
" gaon db migrate down 가장 최근 이력 한 건 롤백",
|
|
121
|
+
" gaon db migrate --dry-run 적용 없이 실행 예정 파일 + up SQL 만 출력",
|
|
122
|
+
" gaon db status 마이그레이션 파일 적용/대기 + 스키마 drift",
|
|
115
123
|
" gaon db reset --yes DROP ALL → 재마이그 → seed (--yes 필수 · production 거부)",
|
|
116
124
|
" gaon db seed domain/seed.ts 실행 (M8)",
|
|
117
125
|
" gaon --json 같은 정보를 JSON 으로 출력",
|
|
@@ -220,12 +228,18 @@ export function runCli(argv, opts = {}) {
|
|
|
220
228
|
// exit code (M9-E-Fix): fatal → 2(사용자 오류) / errors > 0 → 1 / 그 외 → 0.
|
|
221
229
|
if (argv[0] === "doctor") {
|
|
222
230
|
const checks = parseDoctorChecks(argv);
|
|
223
|
-
|
|
231
|
+
const fix = argv.includes("--fix");
|
|
232
|
+
const yes = argv.includes("--yes");
|
|
233
|
+
void runDoctorCommand({ json: argv.includes("--json"), checks, fix, yes })
|
|
224
234
|
.then((result) => {
|
|
225
235
|
if (result.fatal) {
|
|
226
236
|
process.exitCode = 2;
|
|
227
237
|
return;
|
|
228
238
|
}
|
|
239
|
+
// --fix --yes: 편집이 실제로 일어났고 나머지 위반이 없으면 0,
|
|
240
|
+
// 아직 수동 수정이 남아 있으면 1(자동화 정합).
|
|
241
|
+
// --fix (dry-run): errors > 0 이면 1(안 고침) · 계획만 있어도 1.
|
|
242
|
+
// 기본: errors > 0 이면 1.
|
|
229
243
|
process.exitCode = result.errors.length > 0 ? 1 : 0;
|
|
230
244
|
})
|
|
231
245
|
.catch((err) => {
|
|
@@ -235,6 +249,25 @@ export function runCli(argv, opts = {}) {
|
|
|
235
249
|
});
|
|
236
250
|
return;
|
|
237
251
|
}
|
|
252
|
+
// `gaon mcp` — 내장 MCP 서버(M10-B · 정본 §12 질문 8). AI 에이전트에게 4종
|
|
253
|
+
// 도구(list_routes·get_schema·run_migration·run_tests)를 노출한다. stdio(기본)
|
|
254
|
+
// 또는 --http 전송. 표시 버전은 파사드 버전을 주입한다.
|
|
255
|
+
if (argv[0] === "mcp") {
|
|
256
|
+
void runMcpCommand({
|
|
257
|
+
json: argv.includes("--json"),
|
|
258
|
+
transport: argv.includes("--http") ? "http" : "stdio",
|
|
259
|
+
version,
|
|
260
|
+
})
|
|
261
|
+
.then((code) => {
|
|
262
|
+
process.exitCode = code;
|
|
263
|
+
})
|
|
264
|
+
.catch((err) => {
|
|
265
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
266
|
+
process.stderr.write(` ✗ gaon mcp 실패: ${msg}\n`);
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
238
271
|
// `gaon hub` — 실시간 허브 프로세스(§7 M6). 운영 프로세스 3종 중 하나.
|
|
239
272
|
// NATS·리더 선출 대기로 프로세스를 살려 두고, SIGINT/SIGTERM 에 그레이스풀 종료.
|
|
240
273
|
if (argv[0] === "hub") {
|
|
@@ -271,10 +304,10 @@ export function runCli(argv, opts = {}) {
|
|
|
271
304
|
// `gaon db <sub>` — diff · migrate · reset · seed (§7 M8/M9-D).
|
|
272
305
|
if (argv[0] === "db") {
|
|
273
306
|
const sub = argv[1];
|
|
274
|
-
const known = ["diff", "migrate", "reset", "seed"];
|
|
307
|
+
const known = ["diff", "migrate", "reset", "seed", "status"];
|
|
275
308
|
if (!sub || !known.includes(sub)) {
|
|
276
309
|
process.stderr.write(` ✗ 알 수 없는 db 서브커맨드: ${sub ?? "(없음)"}\n` +
|
|
277
|
-
` → 지원: gaon db diff | migrate | reset | seed\n` +
|
|
310
|
+
` → 지원: gaon db diff | migrate | reset | seed | status\n` +
|
|
278
311
|
` → 옵션: --json · --db <키> · --config <path> · --yes · --dry-run\n`);
|
|
279
312
|
process.exitCode = 1;
|
|
280
313
|
return;
|
|
@@ -287,6 +320,8 @@ export function runCli(argv, opts = {}) {
|
|
|
287
320
|
config: cfgIdx >= 0 ? argv[cfgIdx + 1] : undefined,
|
|
288
321
|
yes: argv.includes("--yes"),
|
|
289
322
|
dryRun: argv.includes("--dry-run"),
|
|
323
|
+
// `gaon db migrate down` — 위치 인자로 롤백 지시.
|
|
324
|
+
down: sub === "migrate" && argv[2] === "down",
|
|
290
325
|
};
|
|
291
326
|
void runDbCommand(sub, dbOpts)
|
|
292
327
|
.then((code) => {
|
|
@@ -310,7 +345,7 @@ export function runCli(argv, opts = {}) {
|
|
|
310
345
|
process.exitCode = code;
|
|
311
346
|
return;
|
|
312
347
|
}
|
|
313
|
-
const known = ["controller", "model", "page", "job"];
|
|
348
|
+
const known = ["controller", "model", "page", "job", "app"];
|
|
314
349
|
const type = argv[1];
|
|
315
350
|
if (type && known.includes(type)) {
|
|
316
351
|
const rest = argv.slice(2);
|
|
@@ -339,8 +374,13 @@ export function runCli(argv, opts = {}) {
|
|
|
339
374
|
name = a;
|
|
340
375
|
}
|
|
341
376
|
if (!name) {
|
|
377
|
+
const example = type === "page"
|
|
378
|
+
? "Posts/Index"
|
|
379
|
+
: type === "app"
|
|
380
|
+
? "admin"
|
|
381
|
+
: "Post";
|
|
342
382
|
process.stderr.write(` ✗ gaon g ${type}: 이름이 없습니다.\n` +
|
|
343
|
-
` → 예: gaon g ${type} ${
|
|
383
|
+
` → 예: gaon g ${type} ${example}\n`);
|
|
344
384
|
process.exitCode = 1;
|
|
345
385
|
return;
|
|
346
386
|
}
|
|
@@ -349,7 +389,7 @@ export function runCli(argv, opts = {}) {
|
|
|
349
389
|
return;
|
|
350
390
|
}
|
|
351
391
|
process.stderr.write(` ✗ 알 수 없는 제너레이터: ${argv[1] ?? "(없음)"}\n` +
|
|
352
|
-
` → 현재 지원: gaon g auth | controller | model | page | job\n` +
|
|
392
|
+
` → 현재 지원: gaon g auth | controller | model | page | job | app\n` +
|
|
353
393
|
` → 옵션: --app <이름> · --overwrite · --json\n`);
|
|
354
394
|
process.exitCode = 1;
|
|
355
395
|
return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · MCP 모듈 public exports — v0.15 §13.5 M10.
|
|
3
|
+
*
|
|
4
|
+
* `gaon mcp` 명령이 소비하는 조립 API. tools.ts 의 TOOLS 카탈로그는 SSOT.
|
|
5
|
+
*/
|
|
6
|
+
export { createMcpServer, startMcpServer, SERVER_NAME, type McpServerOptions, type McpServerHandle, type McpServerVersion, } from './server.js';
|
|
7
|
+
export { TOOLS, findTool, listRoutesTool, getSchemaTool, runMigrationTool, runTestsTool, projectSummary, type McpToolSpec, type ToolResult, type ToolArgs, } from './tools.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · MCP 모듈 public exports — v0.15 §13.5 M10.
|
|
3
|
+
*
|
|
4
|
+
* `gaon mcp` 명령이 소비하는 조립 API. tools.ts 의 TOOLS 카탈로그는 SSOT.
|
|
5
|
+
*/
|
|
6
|
+
export { createMcpServer, startMcpServer, SERVER_NAME, } from './server.js';
|
|
7
|
+
export { TOOLS, findTool, listRoutesTool, getSchemaTool, runMigrationTool, runTestsTool, projectSummary, } from './tools.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gaonjs/cli · MCP 서버 배선 — v0.15 §13.5 M10 · 질문 8.
|
|
3
|
+
*
|
|
4
|
+
* `gaon mcp` 가 이 함수를 stdio 트랜스포트로 부트업한다. Node.js MCP SDK
|
|
5
|
+
* (@modelcontextprotocol/sdk) 의 Server + StdioServerTransport 를 사용해
|
|
6
|
+
* JSON-RPC 2.0 over stdio 로 4 도구를 노출한다(§9 실 프로토콜).
|
|
7
|
+
*
|
|
8
|
+
* 도구 목록/호출은 tools.ts 의 TOOLS 카탈로그를 그대로 소비 — 카탈로그가
|
|
9
|
+
* 단일 진실 원천(SSOT). 여기서 도구를 추가하지 않는다.
|
|
10
|
+
*
|
|
11
|
+
* transport:
|
|
12
|
+
* - stdio (기본 · MCP 표준)
|
|
13
|
+
* - http 는 v1 범위 외(§13.5 M10 명시 X · 필요 시 후속 마일스톤에서 추가)
|
|
14
|
+
*
|
|
15
|
+
* 종료: transport 가 닫히면(SIGINT / stdin 종료) close() 로 정리한다.
|
|
16
|
+
*/
|
|
17
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
18
|
+
/** 서버 이름·버전 — MCP initialize 응답의 serverInfo. */
|
|
19
|
+
export declare const SERVER_NAME = "gaon";
|
|
20
|
+
/**
|
|
21
|
+
* 서버 버전 — CLI 가 주입한다(호출자가 넘긴 값 · 미주입 시 'dev').
|
|
22
|
+
* 실제 값은 packages/cli/package.json 의 version(파사드 gaonjs 로 재수출됨).
|
|
23
|
+
*/
|
|
24
|
+
export type McpServerVersion = string;
|
|
25
|
+
export interface McpServerOptions {
|
|
26
|
+
/** 프로젝트 루트 — 각 도구가 이 cwd 기준으로 관례를 해석. */
|
|
27
|
+
readonly cwd: string;
|
|
28
|
+
/** 서버 버전 — 미주입 시 'dev'. */
|
|
29
|
+
readonly version?: McpServerVersion;
|
|
30
|
+
}
|
|
31
|
+
/** 서버 핸들 — 종료 대기·강제 종료용. */
|
|
32
|
+
export interface McpServerHandle {
|
|
33
|
+
/** transport 가 종료될 때까지 대기. stdin close / SIGINT 시 resolve. */
|
|
34
|
+
wait(): Promise<void>;
|
|
35
|
+
/** 강제 종료 — server + transport 정리. */
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* MCP 서버 인스턴스 만들기 + tool handlers 등록.
|
|
40
|
+
* transport 연결은 startMcpServer 에서 한다(테스트 격리를 위해 분리).
|
|
41
|
+
*/
|
|
42
|
+
export declare function createMcpServer(opts: McpServerOptions): Server;
|
|
43
|
+
/**
|
|
44
|
+
* `gaon mcp` 본체 — stdio transport 로 서버를 부트업하고 stdin close 를 기다린다.
|
|
45
|
+
* 반환 핸들의 wait() 는 transport 가 종료될 때 resolve.
|
|
46
|
+
*
|
|
47
|
+
* 테스트에서는 stdio 를 파이프로 감싼 자식 프로세스로 실행하고, 이 함수는
|
|
48
|
+
* 자식 프로세스 내부에서 호출된다(§9 실 프로토콜 왕복).
|
|
49
|
+
*/
|
|
50
|
+
export declare function startMcpServer(opts: McpServerOptions): Promise<McpServerHandle>;
|