@gaonjs/cli 0.4.0 → 0.10.0

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 (120) hide show
  1. package/dist/commands/check.d.ts +50 -0
  2. package/dist/commands/check.js +286 -0
  3. package/dist/commands/console.d.ts +46 -0
  4. package/dist/commands/console.js +129 -0
  5. package/dist/commands/db.d.ts +3 -1
  6. package/dist/commands/db.js +8 -2
  7. package/dist/commands/g.d.ts +1 -1
  8. package/dist/commands/g.js +27 -3
  9. package/dist/commands/mcp.d.ts +15 -0
  10. package/dist/commands/mcp.js +78 -0
  11. package/dist/commands/new.d.ts +45 -0
  12. package/dist/commands/new.js +274 -0
  13. package/dist/commands/test.d.ts +11 -0
  14. package/dist/commands/test.js +119 -0
  15. package/dist/db/diff.js +5 -0
  16. package/dist/db/journal.d.ts +34 -0
  17. package/dist/db/journal.js +71 -0
  18. package/dist/db/migrate.d.ts +6 -1
  19. package/dist/db/migrate.js +120 -102
  20. package/dist/db/replay.d.ts +49 -0
  21. package/dist/db/replay.js +148 -0
  22. package/dist/db/status.d.ts +12 -0
  23. package/dist/db/status.js +61 -0
  24. package/dist/dev/index.d.ts +2 -0
  25. package/dist/dev/index.js +2 -0
  26. package/dist/dev/vite.d.ts +67 -0
  27. package/dist/dev/vite.js +126 -0
  28. package/dist/dev.d.ts +18 -0
  29. package/dist/dev.js +15 -0
  30. package/dist/doctor/agents-doc-index.d.ts +4 -0
  31. package/dist/doctor/agents-doc-index.js +80 -0
  32. package/dist/doctor/fixers/dependency-direction.d.ts +9 -0
  33. package/dist/doctor/fixers/dependency-direction.js +98 -0
  34. package/dist/doctor/fixers/index.d.ts +15 -0
  35. package/dist/doctor/fixers/index.js +66 -0
  36. package/dist/doctor/fixers/schema-filename.d.ts +14 -0
  37. package/dist/doctor/fixers/schema-filename.js +104 -0
  38. package/dist/doctor/fixers/types.d.ts +59 -0
  39. package/dist/doctor/fixers/types.js +15 -0
  40. package/dist/doctor/no-auto-import.d.ts +10 -0
  41. package/dist/doctor/no-auto-import.js +158 -0
  42. package/dist/doctor/schema-filename.d.ts +6 -0
  43. package/dist/doctor/schema-filename.js +81 -0
  44. package/dist/doctor/shared-composable-purity.d.ts +8 -0
  45. package/dist/doctor/shared-composable-purity.js +164 -0
  46. package/dist/doctor/types.d.ts +1 -1
  47. package/dist/doctor/types.js +6 -5
  48. package/dist/doctor.d.ts +51 -0
  49. package/dist/doctor.js +191 -7
  50. package/dist/generate.js +2 -2
  51. package/dist/hub.d.ts +1 -1
  52. package/dist/index.d.ts +6 -2
  53. package/dist/index.js +154 -16
  54. package/dist/mcp/index.d.ts +7 -0
  55. package/dist/mcp/index.js +7 -0
  56. package/dist/mcp/server.d.ts +50 -0
  57. package/dist/mcp/server.js +102 -0
  58. package/dist/mcp/tools.d.ts +109 -0
  59. package/dist/mcp/tools.js +485 -0
  60. package/dist/scaffold/app.d.ts +5 -0
  61. package/dist/scaffold/app.js +172 -0
  62. package/dist/scaffold/controller.js +2 -2
  63. package/dist/scaffold/index.d.ts +2 -1
  64. package/dist/scaffold/index.js +2 -1
  65. package/dist/scaffold/job.d.ts +5 -0
  66. package/dist/scaffold/job.js +35 -0
  67. package/dist/scaffold/model.js +8 -8
  68. package/dist/templates/auth/auth.wiring.ts.tpl +1 -1
  69. package/dist/templates/auth/registration.controller.ts.tpl +1 -1
  70. package/dist/templates/auth/session.controller.ts.tpl +1 -1
  71. package/dist/templates/auth/user.model.ts.tpl +1 -1
  72. package/dist/templates/index.d.ts +23 -0
  73. package/dist/templates/index.js +66 -0
  74. package/dist/templates/index.ts +85 -0
  75. package/dist/templates/project/.env.example.tpl +18 -0
  76. package/dist/templates/project/.gitignore.tpl +24 -0
  77. package/dist/templates/project/.npmrc.tpl +4 -0
  78. package/dist/templates/project/AGENTS.md.tpl +210 -0
  79. package/dist/templates/project/CLAUDE.md.tpl +119 -0
  80. package/dist/templates/project/agents/async.md.tpl +218 -0
  81. package/dist/templates/project/agents/data.md.tpl +532 -0
  82. package/dist/templates/project/agents/frontend.md.tpl +201 -0
  83. package/dist/templates/project/agents/realtime.md.tpl +157 -0
  84. package/dist/templates/project/agents/security.md.tpl +92 -0
  85. package/dist/templates/project/agents/testing.md.tpl +101 -0
  86. package/dist/templates/project/agents/web.md.tpl +177 -0
  87. package/dist/templates/project/apps/web/channels/.gitkeep.tpl +1 -0
  88. package/dist/templates/project/apps/web/components/.gitkeep.tpl +1 -0
  89. package/dist/templates/project/apps/web/composables/useApiPing.ts.tpl +25 -0
  90. package/dist/templates/project/apps/web/controllers/home.ts.tpl +19 -0
  91. package/dist/templates/project/apps/web/index.html.tpl +18 -0
  92. package/dist/templates/project/apps/web/layouts/Default.vue.tpl +43 -0
  93. package/dist/templates/project/apps/web/main.ts.tpl +24 -0
  94. package/dist/templates/project/apps/web/pages/Home/Index.vue.tpl +36 -0
  95. package/dist/templates/project/apps/web/routes.ts.tpl +8 -0
  96. package/dist/templates/project/docker-compose.yaml.tpl +73 -0
  97. package/dist/templates/project/domain/events/.gitkeep.tpl +1 -0
  98. package/dist/templates/project/domain/jobs/.gitkeep.tpl +1 -0
  99. package/dist/templates/project/domain/listeners/.gitkeep.tpl +1 -0
  100. package/dist/templates/project/domain/mails/.gitkeep.tpl +1 -0
  101. package/dist/templates/project/domain/models/.gitkeep.tpl +1 -0
  102. package/dist/templates/project/domain/schema/.gitkeep.tpl +1 -0
  103. package/dist/templates/project/domain/services/.gitkeep.tpl +1 -0
  104. package/dist/templates/project/gaon.config.ts.tpl +27 -0
  105. package/dist/templates/project/package.json.tpl +30 -0
  106. package/dist/templates/project/pnpm-workspace.yaml.tpl +11 -0
  107. package/dist/templates/project/shared/components/.gitkeep.tpl +1 -0
  108. package/dist/templates/project/shared/composables/useDebounce.ts.tpl +21 -0
  109. package/dist/templates/project/tsconfig.json.tpl +25 -0
  110. package/dist/templates/project/vite.config.ts.tpl +23 -0
  111. package/dist/tsResolve.js +1 -1
  112. package/dist/work.d.ts +2 -2
  113. package/dist/work.js +3 -1
  114. package/package.json +13 -11
  115. package/dist/__fixtures__/db-minimal/domain/schema/widgets.d.ts +0 -12
  116. package/dist/__fixtures__/db-minimal/domain/schema/widgets.js +0 -7
  117. package/dist/__fixtures__/db-minimal/gaon.config.d.ts +0 -2
  118. package/dist/__fixtures__/db-minimal/gaon.config.js +0 -11
  119. package/dist/check.d.ts +0 -29
  120. package/dist/check.js +0 -92
package/dist/doctor.js CHANGED
@@ -1,12 +1,15 @@
1
1
  /**
2
- * @gaonjs/cli · `gaon doctor` — 정적 검사 (M9-E · CLI DX 완성)
2
+ * @gaonjs/cli · `gaon doctor` — 정적 검사 (M9-E · CLI DX 완성 · E-5 확장)
3
3
  *
4
- * 5 검사를 조립한다:
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 규칙)
8
8
  * 4) connections (v0.15 §4.5)
9
9
  * 5) migration-diff (최소 감지 · 상세는 M9-D)
10
+ * 6) shared-composable-purity (errata E-5 §2.2 · 결정 25)
11
+ * 7) no-auto-import (errata E-5 §2.4 · v0.15 §1.2)
12
+ * 8) schema-filename (§1.1 · 결정 38 · 스키마 파일명 camelCase)
10
13
  *
11
14
  * 각 검사는 순수 함수(cwd → RuleReport). 상위 runDoctorCommand 가 조립해
12
15
  * DoctorResult 로 낸다. --json 은 자동화(CI)를 위해 반드시 파싱 가능한
@@ -15,25 +18,36 @@
15
18
  * 하위 호환: 기존 응답 혼용 API(`inspectControllerSource`·`runDoctor`)는
16
19
  * 그대로 export — 기존 테스트가 계속 동작한다.
17
20
  */
18
- import { resolve } from 'node:path';
21
+ import { rename, writeFile } from 'node:fs/promises';
22
+ import { existsSync } from 'node:fs';
23
+ import { join, resolve } from 'node:path';
19
24
  import ts from 'typescript';
20
25
  import { checkResponseMixing } from './doctor/response-mixing.js';
21
26
  import { checkNPlusOne } from './doctor/n-plus-one.js';
22
27
  import { checkDependencyDirection } from './doctor/dependency-direction.js';
23
28
  import { checkConnections } from './doctor/connections.js';
24
29
  import { checkMigrationDiff } from './doctor/migration-diff.js';
30
+ import { checkSharedComposablePurity } from './doctor/shared-composable-purity.js';
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';
25
34
  import { renderHuman, renderJson } from './doctor/reporter.js';
26
35
  import { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
27
36
  import { makeResult, } from './doctor/types.js';
37
+ import { FIXERS, FIXER_CAPABILITIES } from './doctor/fixers/index.js';
28
38
  export { inspectControllerSource, checkResponseMixing } from './doctor/response-mixing.js';
29
39
  export { inspectControllerForNPlusOne, checkNPlusOne } from './doctor/n-plus-one.js';
30
40
  export { extractRelativeImports, checkDependencyDirection } from './doctor/dependency-direction.js';
31
41
  export { extractConfigDbKeys, extractKeyUses, checkConnections } from './doctor/connections.js';
32
42
  export { scanSchema, checkMigrationDiff } from './doctor/migration-diff.js';
43
+ export { inspectSharedComposable, checkSharedComposablePurity, } from './doctor/shared-composable-purity.js';
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';
33
47
  export { renderHuman, renderJson } from './doctor/reporter.js';
34
48
  export { checkTypeScriptApi, detectProject, fatalNoProject, fatalTsApiMissing, } from './doctor/setup.js';
35
49
  /**
36
- * 실행할 검사 이름. 지정 없음(undefined) = 5개 모두.
50
+ * 실행할 검사 이름. 지정 없음(undefined) = 7개 모두.
37
51
  */
38
52
  const ALL_RULES = [
39
53
  'response-mixing',
@@ -41,6 +55,10 @@ const ALL_RULES = [
41
55
  'dependency-direction',
42
56
  'connections',
43
57
  'migration-diff',
58
+ 'shared-composable-purity',
59
+ 'no-auto-import',
60
+ 'schema-filename',
61
+ 'agents-doc-index',
44
62
  ];
45
63
  const CHECKERS = {
46
64
  'response-mixing': checkResponseMixing,
@@ -48,6 +66,10 @@ const CHECKERS = {
48
66
  'dependency-direction': checkDependencyDirection,
49
67
  connections: checkConnections,
50
68
  'migration-diff': checkMigrationDiff,
69
+ 'shared-composable-purity': checkSharedComposablePurity,
70
+ 'no-auto-import': checkNoAutoImport,
71
+ 'schema-filename': checkSchemaFilename,
72
+ 'agents-doc-index': checkAgentsDocIndex,
51
73
  };
52
74
  /**
53
75
  * `gaon doctor` 진입점 (M9-E · 확장 · M9-E-Fix 하드닝).
@@ -120,10 +142,172 @@ export async function runDoctorCommand(opts = {}) {
120
142
  });
121
143
  }
122
144
  }
123
- const result = makeResult(reports);
124
- const out = json ? renderJson(result) : renderHuman(result);
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);
125
159
  process.stdout.write(out + '\n');
126
- return result;
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}`;
127
311
  }
128
312
  /**
129
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/user.ts' },
30
- { tpl: 'user.model.ts.tpl', out: () => 'domain/models/user.ts' },
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, 그다음 기본(4222). */
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
@@ -2,7 +2,10 @@ import { type DoctorRule } from "./doctor.js";
2
2
  export { startDev, resolveDevLayout, type DevDeps, type DevLayout, type DevApp, type DevEvent, type DevHandle, } from "./dev.js";
3
3
  export { runDevCommand, type DevCommandOptions } from "./commands/dev.js";
4
4
  export { createDevConsole, findComposeFile, isDockerAvailable, inspectCompose, composeUp, composeDown, ensureInfra, startTscWatchers, killChild, startRestartWatcher, isRestartChange, resolveWatchRoots, type DevConsole, type DevConsoleOptions, type DevSource, type DevLevel, type ComposeStatus, type ComposeUpOptions, type EnsureInfraResult, type DockerLocateOptions, type TscWatcherOptions, type TscWatcherHandle, type RestartWatcherOptions, type RestartWatcherHandle, } from "./dev/index.js";
5
- export { runCheck, runCheckCommand, type CheckDeps, type CheckResult, type TypecheckResult, type CheckCommandOptions, } from "./check.js";
5
+ export { runCheckCommand, type CheckCommandOptions, type CheckStep, type CheckStepStatus, type CheckStepResult, } from "./commands/check.js";
6
+ export { runNewCommand, type NewCommandOptions, type NewCommandResult } from "./commands/new.js";
7
+ export { runConsoleCommand, type ConsoleCommandOptions } from "./commands/console.js";
8
+ export { runTestCommand, type TestCommandOptions, type TestScope } from "./commands/test.js";
6
9
  export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, type AuthScaffoldOptions, type ScaffoldFile, type ScaffoldResult, type GenerateAuthOptions, } from "./generate.js";
7
10
  export { runGenerateCommand, planScaffold, parseGenerateArgs, type GenerateType, type GenerateOptions, type GenerateResult, } from "./commands/g.js";
8
11
  export { runHubCommand, type HubCommandOptions } from "./hub.js";
@@ -12,7 +15,8 @@ export { runJobsCommand, type JobsCommandOptions } from "./jobs.js";
12
15
  export { runDbSeedCommand, loadSeed, type DbSeedOptions, type DbSeedResult } from "./db.js";
13
16
  export { runDbCommand, type DbSubcommand, type DbCommandOptions, } from "./commands/db.js";
14
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";
15
- 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";
16
20
  export { loadDomain, type LoadedDomain } from "./domain.js";
17
21
  export interface RoadmapReport {
18
22
  readonly name: "gaon";
package/dist/index.js CHANGED
@@ -12,7 +12,10 @@
12
12
  */
13
13
  import { MILESTONES, VERSION, HOMEPAGE } from "@gaonjs/core";
14
14
  import { runDevCommand } from "./commands/dev.js";
15
- import { runCheckCommand } from "./check.js";
15
+ import { runCheckCommand } from "./commands/check.js";
16
+ import { runNewCommand } from "./commands/new.js";
17
+ import { runConsoleCommand } from "./commands/console.js";
18
+ import { runTestCommand } from "./commands/test.js";
16
19
  import { runGenerateAuthCommand } from "./generate.js";
17
20
  import { runGenerateCommand } from "./commands/g.js";
18
21
  import { runHubCommand } from "./hub.js";
@@ -21,10 +24,14 @@ import { runWorkCommand } from "./work.js";
21
24
  import { runJobsCommand } from "./jobs.js";
22
25
  import { runDbCommand } from "./commands/db.js";
23
26
  import { runDoctorCommand } from "./doctor.js";
27
+ import { runMcpCommand } from "./commands/mcp.js";
24
28
  export { startDev, resolveDevLayout, } from "./dev.js";
25
29
  export { runDevCommand } from "./commands/dev.js";
26
30
  export { createDevConsole, findComposeFile, isDockerAvailable, inspectCompose, composeUp, composeDown, ensureInfra, startTscWatchers, killChild, startRestartWatcher, isRestartChange, resolveWatchRoots, } from "./dev/index.js";
27
- export { runCheck, runCheckCommand, } from "./check.js";
31
+ export { runCheckCommand, } from "./commands/check.js";
32
+ export { runNewCommand } from "./commands/new.js";
33
+ export { runConsoleCommand } from "./commands/console.js";
34
+ export { runTestCommand } from "./commands/test.js";
28
35
  export { writeAuthScaffold, authScaffoldFiles, patchRoutes, runGenerateAuthCommand, } from "./generate.js";
29
36
  export { runGenerateCommand, planScaffold, parseGenerateArgs, } from "./commands/g.js";
30
37
  export { runHubCommand } from "./hub.js";
@@ -34,7 +41,8 @@ export { runJobsCommand } from "./jobs.js";
34
41
  export { runDbSeedCommand, loadSeed } from "./db.js";
35
42
  export { runDbCommand, } from "./commands/db.js";
36
43
  export { runDbDiff, runDbMigrate, runDbReset, resolveDbTarget, } from "./db/index.js";
37
- 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";
38
46
  export { loadDomain } from "./domain.js";
39
47
  /** `--json` 출력용 구조화 리포트. */
40
48
  export function roadmapReport(version = VERSION) {
@@ -79,6 +87,7 @@ function renderHelp(version = VERSION) {
79
87
  "",
80
88
  " 사용법:",
81
89
  " gaon 로드맵과 개발 상태를 출력",
90
+ " gaon new <name> 새 프로젝트 스캐폴드 (파일 → 설치 → git · --skip-install · --skip-git · --pm <이름>)",
82
91
  " gaon dev 개발 스택 통합 (Docker · .gaon · serve · tsc/vue-tsc · 재시작 워처)",
83
92
  " gaon dev --stop-docker Ctrl+C 시 Docker Compose 도 down",
84
93
  " gaon dev --no-watch|--no-tsc|--no-vue-tsc|--no-docker 개별 debug 옵션",
@@ -86,23 +95,31 @@ function renderHelp(version = VERSION) {
86
95
  " gaon dev --json 통합 콘솔을 JSON 라인으로 출력(자동화)",
87
96
  " gaon serve 웹 서버 부팅 (gaon.config.ts 자동 배선 · Fastify listen)",
88
97
  " gaon serve --port <n> --host <h> 리슨 포트·호스트 (config 값을 덮음)",
89
- " gaon check .gaon 재생성 타입 검사 (CI·AI 정합)",
90
- " gaon doctor 정적 검사 (5 검사 · 응답 혼용·N+1·의존 방향·커넥션·마이그)",
98
+ " gaon check typecheck · vue-tsc · build 통합 검사 (--only <step> · --include-doctor)",
99
+ " gaon console 프로젝트 컨텍스트 REPL (--no-config)",
100
+ " gaon test 테스트 러너 (--scope unit|integration|all · -- vitest 인자)",
101
+ " gaon doctor 정적 검사 (7 검사 · 응답 혼용·N+1·의존·커넥션·마이그·컴포저블 순수·자동 import)",
91
102
  " gaon doctor --json 자동화용 JSON 출력",
92
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-<타임스탬프> 로 자동 백업)",
93
106
  " gaon g auth 인증 스캐폴드 생성 (회원가입·로그인·세션·보호 라우트)",
94
107
  " gaon g controller <name> 컨트롤러 스캐폴드 (Rails 관례 · 페이지+JSON 액션)",
95
108
  " gaon g model <Name> 모델 스캐폴드 (스키마+모델 · E-4 컬럼 예시)",
96
109
  " gaon g page <Path/Name> Vue 페이지 (Inertia SPA · pageProps 브리지)",
97
110
  " gaon g job <Name> 비동기 잡 (domain/jobs · later/in/at)",
111
+ " gaon g app <name> 앱 스캐폴드 (apps/<name>/ · routes·controllers·pages·layouts)",
98
112
  " gaon g <type> --overwrite 기존 파일 덮어쓰기 · --app <이름> · --json",
113
+ " gaon mcp 내장 MCP 서버 · AI 도구 4종 (list_routes·get_schema·run_migration·run_tests · --http)",
99
114
  " gaon hub 실시간 허브 프로세스 (프레즌스 권위·중계 · 리더 선출 HA)",
100
115
  " gaon work 워커 프로세스 (잡·리스너·스케줄·아웃박스 · graceful drain)",
101
116
  " gaon jobs list --failed DLQ(실패 잡) 목록",
102
117
  " gaon jobs retry <id> DLQ 잡 재적재",
103
- " gaon db diff 스키마 ↔ DB 차이 계산 (적용 X · --json · --db <키>)",
104
- " gaon db migrate 스키마 변경을 실제 적용 + _gaon_migrations 이력",
105
- " gaon db migrate --dry-run 적용 없이 up SQL 만 출력",
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",
106
123
  " gaon db reset --yes DROP ALL → 재마이그 → seed (--yes 필수 · production 거부)",
107
124
  " gaon db seed domain/seed.ts 실행 (M8)",
108
125
  " gaon --json 같은 정보를 JSON 으로 출력",
@@ -124,6 +141,8 @@ export function parseDoctorChecks(argv) {
124
141
  "dependency-direction",
125
142
  "connections",
126
143
  "migration-diff",
144
+ "shared-composable-purity",
145
+ "no-auto-import",
127
146
  ];
128
147
  const isKnown = (s) => known.includes(s);
129
148
  const out = [];
@@ -179,9 +198,21 @@ export function runCli(argv, opts = {}) {
179
198
  });
180
199
  return;
181
200
  }
182
- // `gaon check` — .gaon 재생성 타입 검사(§13.4-5). 종료 코드로 결과 전달.
201
+ // `gaon check` — typecheck · vue-tsc · build (·doctor) 통합 검사(M9-G).
202
+ // package.json 스크립트 관례 재사용. --only <step> 로 단일 단계, --include-doctor
203
+ // 로 doctor 포함. 종료 코드로 결과 전달.
183
204
  if (argv[0] === "check") {
184
- void runCheckCommand({ json: argv.includes("--json") })
205
+ const knownSteps = ["typecheck", "vue-tsc", "build", "doctor"];
206
+ const onlyIdx = argv.indexOf("--only");
207
+ const onlyRaw = onlyIdx >= 0 ? argv[onlyIdx + 1] : undefined;
208
+ const only = onlyRaw && knownSteps.includes(onlyRaw)
209
+ ? onlyRaw
210
+ : undefined;
211
+ void runCheckCommand({
212
+ json: argv.includes("--json"),
213
+ only,
214
+ includeDoctor: argv.includes("--include-doctor"),
215
+ })
185
216
  .then((code) => {
186
217
  process.exitCode = code;
187
218
  })
@@ -197,12 +228,18 @@ export function runCli(argv, opts = {}) {
197
228
  // exit code (M9-E-Fix): fatal → 2(사용자 오류) / errors > 0 → 1 / 그 외 → 0.
198
229
  if (argv[0] === "doctor") {
199
230
  const checks = parseDoctorChecks(argv);
200
- void runDoctorCommand({ json: argv.includes("--json"), checks })
231
+ const fix = argv.includes("--fix");
232
+ const yes = argv.includes("--yes");
233
+ void runDoctorCommand({ json: argv.includes("--json"), checks, fix, yes })
201
234
  .then((result) => {
202
235
  if (result.fatal) {
203
236
  process.exitCode = 2;
204
237
  return;
205
238
  }
239
+ // --fix --yes: 편집이 실제로 일어났고 나머지 위반이 없으면 0,
240
+ // 아직 수동 수정이 남아 있으면 1(자동화 정합).
241
+ // --fix (dry-run): errors > 0 이면 1(안 고침) · 계획만 있어도 1.
242
+ // 기본: errors > 0 이면 1.
206
243
  process.exitCode = result.errors.length > 0 ? 1 : 0;
207
244
  })
208
245
  .catch((err) => {
@@ -212,6 +249,25 @@ export function runCli(argv, opts = {}) {
212
249
  });
213
250
  return;
214
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
+ }
215
271
  // `gaon hub` — 실시간 허브 프로세스(§7 M6). 운영 프로세스 3종 중 하나.
216
272
  // NATS·리더 선출 대기로 프로세스를 살려 두고, SIGINT/SIGTERM 에 그레이스풀 종료.
217
273
  if (argv[0] === "hub") {
@@ -248,10 +304,10 @@ export function runCli(argv, opts = {}) {
248
304
  // `gaon db <sub>` — diff · migrate · reset · seed (§7 M8/M9-D).
249
305
  if (argv[0] === "db") {
250
306
  const sub = argv[1];
251
- const known = ["diff", "migrate", "reset", "seed"];
307
+ const known = ["diff", "migrate", "reset", "seed", "status"];
252
308
  if (!sub || !known.includes(sub)) {
253
309
  process.stderr.write(` ✗ 알 수 없는 db 서브커맨드: ${sub ?? "(없음)"}\n` +
254
- ` → 지원: gaon db diff | migrate | reset | seed\n` +
310
+ ` → 지원: gaon db diff | migrate | reset | seed | status\n` +
255
311
  ` → 옵션: --json · --db <키> · --config <path> · --yes · --dry-run\n`);
256
312
  process.exitCode = 1;
257
313
  return;
@@ -264,6 +320,8 @@ export function runCli(argv, opts = {}) {
264
320
  config: cfgIdx >= 0 ? argv[cfgIdx + 1] : undefined,
265
321
  yes: argv.includes("--yes"),
266
322
  dryRun: argv.includes("--dry-run"),
323
+ // `gaon db migrate down` — 위치 인자로 롤백 지시.
324
+ down: sub === "migrate" && argv[2] === "down",
267
325
  };
268
326
  void runDbCommand(sub, dbOpts)
269
327
  .then((code) => {
@@ -287,7 +345,7 @@ export function runCli(argv, opts = {}) {
287
345
  process.exitCode = code;
288
346
  return;
289
347
  }
290
- const known = ["controller", "model", "page", "job"];
348
+ const known = ["controller", "model", "page", "job", "app"];
291
349
  const type = argv[1];
292
350
  if (type && known.includes(type)) {
293
351
  const rest = argv.slice(2);
@@ -316,8 +374,13 @@ export function runCli(argv, opts = {}) {
316
374
  name = a;
317
375
  }
318
376
  if (!name) {
377
+ const example = type === "page"
378
+ ? "Posts/Index"
379
+ : type === "app"
380
+ ? "admin"
381
+ : "Post";
319
382
  process.stderr.write(` ✗ gaon g ${type}: 이름이 없습니다.\n` +
320
- ` → 예: gaon g ${type} ${type === "page" ? "Posts/Index" : "Post"}\n`);
383
+ ` → 예: gaon g ${type} ${example}\n`);
321
384
  process.exitCode = 1;
322
385
  return;
323
386
  }
@@ -326,11 +389,86 @@ export function runCli(argv, opts = {}) {
326
389
  return;
327
390
  }
328
391
  process.stderr.write(` ✗ 알 수 없는 제너레이터: ${argv[1] ?? "(없음)"}\n` +
329
- ` → 현재 지원: gaon g auth | controller | model | page | job\n` +
392
+ ` → 현재 지원: gaon g auth | controller | model | page | job | app\n` +
330
393
  ` → 옵션: --app <이름> · --overwrite · --json\n`);
331
394
  process.exitCode = 1;
332
395
  return;
333
396
  }
397
+ // `gaon new <name>` — 프로젝트 스캐폴드(M9-F). 파일 생성 → 의존성 설치 → git init.
398
+ if (argv[0] === "new") {
399
+ const rest = argv.slice(1);
400
+ let name;
401
+ for (const a of rest) {
402
+ if (!a.startsWith("--") && name === undefined)
403
+ name = a;
404
+ }
405
+ if (!name) {
406
+ process.stderr.write(" ✗ gaon new: 프로젝트 이름이 없습니다.\n → 예: gaon new demo\n");
407
+ process.exitCode = 1;
408
+ return;
409
+ }
410
+ const pmIdx = rest.indexOf("--pm");
411
+ const pmRaw = pmIdx >= 0 ? rest[pmIdx + 1] : undefined;
412
+ const packageManager = pmRaw === "pnpm" || pmRaw === "npm" || pmRaw === "yarn" ? pmRaw : undefined;
413
+ void runNewCommand(name, {
414
+ json: argv.includes("--json"),
415
+ skipInstall: argv.includes("--skip-install"),
416
+ skipGit: argv.includes("--skip-git"),
417
+ packageManager,
418
+ })
419
+ .then((code) => {
420
+ process.exitCode = code;
421
+ })
422
+ .catch((err) => {
423
+ const msg = err instanceof Error ? err.message : String(err);
424
+ process.stderr.write(` ✗ gaon new 실패: ${msg}\n`);
425
+ process.exitCode = 1;
426
+ });
427
+ return;
428
+ }
429
+ // `gaon console` — 프로젝트 컨텍스트 REPL(M9-G). --no-config 로 배선 없이 기동.
430
+ if (argv[0] === "console") {
431
+ void runConsoleCommand({
432
+ json: argv.includes("--json"),
433
+ noConfig: argv.includes("--no-config"),
434
+ }).catch((err) => {
435
+ const msg = err instanceof Error ? err.message : String(err);
436
+ process.stderr.write(` ✗ gaon console 실패: ${msg}\n`);
437
+ process.exitCode = 1;
438
+ });
439
+ return;
440
+ }
441
+ // `gaon test [--scope unit|integration|all] [-- vitest 인자]` — 테스트 러너(M9-G).
442
+ if (argv[0] === "test") {
443
+ const rest = argv.slice(1);
444
+ const scopeIdx = rest.indexOf("--scope");
445
+ const scopeRaw = scopeIdx >= 0 ? rest[scopeIdx + 1] : undefined;
446
+ const scope = scopeRaw === "unit" || scopeRaw === "integration" || scopeRaw === "all"
447
+ ? scopeRaw
448
+ : undefined;
449
+ const passthrough = [];
450
+ for (let i = 0; i < rest.length; i++) {
451
+ const a = rest[i];
452
+ if (a === "--scope") {
453
+ i++;
454
+ continue;
455
+ }
456
+ if (a === "--json")
457
+ continue;
458
+ if (a !== undefined)
459
+ passthrough.push(a);
460
+ }
461
+ void runTestCommand(passthrough, { json: argv.includes("--json"), scope })
462
+ .then((code) => {
463
+ process.exitCode = code;
464
+ })
465
+ .catch((err) => {
466
+ const msg = err instanceof Error ? err.message : String(err);
467
+ process.stderr.write(` ✗ gaon test 실패: ${msg}\n`);
468
+ process.exitCode = 1;
469
+ });
470
+ return;
471
+ }
334
472
  if (argv.includes("--help") || argv.includes("-h")) {
335
473
  process.stdout.write(renderHelp(version) + "\n");
336
474
  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';