@mandujs/cli 0.9.22 β†’ 0.9.23

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 (40) hide show
  1. package/README.ko.md +57 -4
  2. package/README.md +47 -15
  3. package/package.json +1 -1
  4. package/src/commands/check.ts +41 -5
  5. package/src/commands/contract.ts +135 -9
  6. package/src/commands/dev.ts +155 -95
  7. package/src/commands/guard-arch.ts +39 -9
  8. package/src/commands/guard-check.ts +3 -3
  9. package/src/commands/init.ts +264 -9
  10. package/src/commands/monitor.ts +21 -0
  11. package/src/main.ts +386 -344
  12. package/templates/default/app/globals.css +37 -0
  13. package/templates/default/app/layout.tsx +27 -0
  14. package/templates/default/app/page.tsx +27 -49
  15. package/templates/default/package.json +15 -6
  16. package/templates/default/postcss.config.js +6 -0
  17. package/templates/default/src/client/app/index.ts +1 -0
  18. package/templates/default/src/client/entities/index.ts +1 -0
  19. package/templates/default/src/client/features/index.ts +1 -0
  20. package/templates/default/src/client/pages/index.ts +1 -0
  21. package/templates/default/src/client/shared/index.ts +1 -0
  22. package/templates/default/src/client/shared/lib/utils.ts +16 -0
  23. package/templates/default/src/client/shared/ui/button.tsx +57 -0
  24. package/templates/default/src/client/shared/ui/card.tsx +78 -0
  25. package/templates/default/src/client/shared/ui/index.ts +21 -0
  26. package/templates/default/src/client/shared/ui/input.tsx +24 -0
  27. package/templates/default/src/client/widgets/index.ts +1 -0
  28. package/templates/default/src/server/api/index.ts +1 -0
  29. package/templates/default/src/server/application/index.ts +1 -0
  30. package/templates/default/src/server/core/index.ts +1 -0
  31. package/templates/default/src/server/domain/index.ts +1 -0
  32. package/templates/default/src/server/infra/index.ts +1 -0
  33. package/templates/default/src/shared/contracts/index.ts +1 -0
  34. package/templates/default/src/shared/env/index.ts +1 -0
  35. package/templates/default/src/shared/schema/index.ts +1 -0
  36. package/templates/default/src/shared/types/index.ts +1 -0
  37. package/templates/default/src/shared/utils/client/index.ts +1 -0
  38. package/templates/default/src/shared/utils/server/index.ts +1 -0
  39. package/templates/default/tailwind.config.ts +64 -0
  40. package/templates/default/tsconfig.json +14 -3
package/src/main.ts CHANGED
@@ -1,74 +1,81 @@
1
- #!/usr/bin/env bun
2
-
1
+ #!/usr/bin/env bun
2
+
3
3
  import { specUpsert } from "./commands/spec-upsert";
4
4
  import { generateApply } from "./commands/generate-apply";
5
5
  import { guardCheck } from "./commands/guard-check";
6
6
  import { guardArch } from "./commands/guard-arch";
7
7
  import { check } from "./commands/check";
8
8
  import { dev } from "./commands/dev";
9
- import { init } from "./commands/init";
10
- import { build } from "./commands/build";
11
- import { contractCreate, contractValidate } from "./commands/contract";
12
- import { openAPIGenerate, openAPIServe } from "./commands/openapi";
13
- import {
14
- changeBegin,
15
- changeCommit,
16
- changeRollback,
17
- changeStatus,
18
- changeList,
19
- changePrune,
20
- } from "./commands/change";
21
- import { doctor } from "./commands/doctor";
9
+ import { init } from "./commands/init";
10
+ import { build } from "./commands/build";
11
+ import { contractCreate, contractValidate, contractBuild, contractDiff } from "./commands/contract";
12
+ import { openAPIGenerate, openAPIServe } from "./commands/openapi";
13
+ import {
14
+ changeBegin,
15
+ changeCommit,
16
+ changeRollback,
17
+ changeStatus,
18
+ changeList,
19
+ changePrune,
20
+ } from "./commands/change";
21
+ import { doctor } from "./commands/doctor";
22
22
  import { watch } from "./commands/watch";
23
23
  import { brainSetup, brainStatus } from "./commands/brain";
24
24
  import { routesGenerate, routesList, routesWatch } from "./commands/routes";
25
25
  import { monitor } from "./commands/monitor";
26
-
27
- const HELP_TEXT = `
28
- πŸ₯Ÿ Mandu CLI - Agent-Native Fullstack Framework
29
-
30
- Usage: bunx mandu <command> [options]
31
-
26
+
27
+ const HELP_TEXT = `
28
+ πŸ₯Ÿ Mandu CLI - Agent-Native Fullstack Framework
29
+
30
+ Usage: bunx mandu <command> [options]
31
+
32
32
  Commands:
33
- init μƒˆ ν”„λ‘œμ νŠΈ 생성
33
+ init μƒˆ ν”„λ‘œμ νŠΈ 생성 (Tailwind + shadcn/ui κΈ°λ³Έ 포함)
34
34
  check FS Routes + Guard 톡합 검사
35
35
  routes generate FS Routes μŠ€μΊ” 및 λ§€λ‹ˆνŽ˜μŠ€νŠΈ 생성
36
36
  routes list ν˜„μž¬ 라우트 λͺ©λ‘ 좜λ ₯
37
37
  routes watch μ‹€μ‹œκ°„ 라우트 κ°μ‹œ
38
38
  dev 개발 μ„œλ²„ μ‹€ν–‰ (FS Routes + Guard κΈ°λ³Έ)
39
39
  dev --no-guard Guard κ°μ‹œ λΉ„ν™œμ„±ν™”
40
- build ν΄λΌμ΄μ–ΈνŠΈ λ²ˆλ“€ λΉŒλ“œ (Hydration)
41
- guard Guard κ·œμΉ™ 검사 (λ ˆκ±°μ‹œ Spec 기반)
42
- guard arch μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (FSD/Clean/Hexagonal)
43
- guard arch --watch μ‹€μ‹œκ°„ μ•„ν‚€ν…μ²˜ κ°μ‹œ
44
- guard arch --list-presets μ‚¬μš© κ°€λŠ₯ν•œ 프리셋 λͺ©λ‘
45
- guard arch --output report.md 리포트 파일 생성
46
- guard arch --show-trend νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
47
- spec-upsert Spec 파일 검증 및 lock κ°±μ‹  (λ ˆκ±°μ‹œ)
48
- generate Specμ—μ„œ μ½”λ“œ 생성 (λ ˆκ±°μ‹œ)
49
-
50
- doctor Guard μ‹€νŒ¨ 뢄석 + 패치 μ œμ•ˆ (Brain)
40
+ build ν΄λΌμ΄μ–ΈνŠΈ λ²ˆλ“€ λΉŒλ“œ (Hydration)
41
+ guard μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (κΈ°λ³Έ)
42
+ guard arch μ•„ν‚€ν…μ²˜ μœ„λ°˜ 검사 (FSD/Clean/Hexagonal)
43
+ guard legacy λ ˆκ±°μ‹œ Spec Guard 검사
44
+ guard arch --watch μ‹€μ‹œκ°„ μ•„ν‚€ν…μ²˜ κ°μ‹œ
45
+ guard arch --list-presets μ‚¬μš© κ°€λŠ₯ν•œ 프리셋 λͺ©λ‘
46
+ guard arch --output report.md 리포트 파일 생성
47
+ guard arch --show-trend νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
48
+ spec-upsert Spec 파일 검증 및 lock κ°±μ‹  (λ ˆκ±°μ‹œ)
49
+ generate Specμ—μ„œ μ½”λ“œ 생성 (λ ˆκ±°μ‹œ)
50
+
51
+ doctor Guard μ‹€νŒ¨ 뢄석 + 패치 μ œμ•ˆ (Brain)
51
52
  watch μ‹€μ‹œκ°„ 파일 κ°μ‹œ - 경고만 (Brain)
52
53
  monitor MCP Activity Monitor 둜그 슀트림
53
54
 
54
55
  brain setup sLLM μ„€μ • (선택)
55
- brain status Brain μƒνƒœ 확인
56
-
57
- contract create <routeId> λΌμš°νŠΈμ— λŒ€ν•œ Contract 생성
58
- contract validate Contract-Slot 일관성 검증
59
-
60
- openapi generate OpenAPI 3.0 μŠ€νŽ™ 생성
61
- openapi serve Swagger UI 둜컬 μ„œλ²„ μ‹€ν–‰
62
-
63
- change begin λ³€κ²½ νŠΈλžœμž­μ…˜ μ‹œμž‘ (μŠ€λƒ…μƒ· 생성)
64
- change commit λ³€κ²½ ν™•μ •
65
- change rollback μŠ€λƒ…μƒ·μœΌλ‘œ 볡원
66
- change status ν˜„μž¬ νŠΈλžœμž­μ…˜ μƒνƒœ
67
- change list λ³€κ²½ 이λ ₯ 쑰회
68
- change prune 였래된 μŠ€λƒ…μƒ· 정리
69
-
56
+ brain status Brain μƒνƒœ 확인
57
+
58
+ contract create <routeId> λΌμš°νŠΈμ— λŒ€ν•œ Contract 생성
59
+ contract validate Contract-Slot 일관성 검증
60
+ contract build Contract λ ˆμ§€μŠ€νŠΈλ¦¬ 생성
61
+ contract diff Contract 변경사항 비ꡐ
62
+
63
+ openapi generate OpenAPI 3.0 μŠ€νŽ™ 생성
64
+ openapi serve Swagger UI 둜컬 μ„œλ²„ μ‹€ν–‰
65
+
66
+ change begin λ³€κ²½ νŠΈλžœμž­μ…˜ μ‹œμž‘ (μŠ€λƒ…μƒ· 생성)
67
+ change commit λ³€κ²½ ν™•μ •
68
+ change rollback μŠ€λƒ…μƒ·μœΌλ‘œ 볡원
69
+ change status ν˜„μž¬ νŠΈλžœμž­μ…˜ μƒνƒœ
70
+ change list λ³€κ²½ 이λ ₯ 쑰회
71
+ change prune 였래된 μŠ€λƒ…μƒ· 정리
72
+
70
73
  Options:
71
74
  --name <name> init μ‹œ ν”„λ‘œμ νŠΈ 이름 (κΈ°λ³Έ: my-mandu-app)
75
+ --css <framework> init μ‹œ CSS ν”„λ ˆμž„μ›Œν¬: tailwind, panda, none (κΈ°λ³Έ: tailwind)
76
+ --ui <library> init μ‹œ UI 라이브러리: shadcn, ark, none (κΈ°λ³Έ: shadcn)
77
+ --theme init μ‹œ 닀크λͺ¨λ“œ ν…Œλ§ˆ μ‹œμŠ€ν…œ μΆ”κ°€
78
+ --minimal init μ‹œ CSS/UI 없이 μ΅œμ†Œ ν…œν”Œλ¦Ώ 생성 (--css none --ui none)
72
79
  --file <path> spec-upsert μ‹œ μ‚¬μš©ν•  spec 파일 경둜
73
80
  --port <port> dev/openapi serve 포트 (κΈ°λ³Έ: 3000/8080)
74
81
  --guard dev μ‹œ Architecture Guard μ‹€μ‹œκ°„ κ°μ‹œ ν™œμ„±ν™” (κΈ°λ³Έ: ON)
@@ -78,131 +85,144 @@ Options:
78
85
  --legacy FS Routes λΉ„ν™œμ„±ν™” (λ ˆκ±°μ‹œ λͺ¨λ“œ)
79
86
  --no-auto-correct guard μ‹œ μžλ™ μˆ˜μ • λΉ„ν™œμ„±ν™”
80
87
  --preset <name> guard/check 프리셋 (κΈ°λ³Έ: mandu) - fsd, clean, hexagonal, atomic 선택 κ°€λŠ₯
81
- --ci guard/check CI λͺ¨λ“œ (μ—λŸ¬ μ‹œ exit 1)
88
+ --ci guard/check CI λͺ¨λ“œ (warning도 μ‹€νŒ¨ 처리)
82
89
  --quiet guard/check μš”μ•½λ§Œ 좜λ ₯
83
- --report-format guard arch 리포트 ν˜•μ‹: json, markdown, html
84
- --save-stats guard arch 톡계 μ €μž₯ (νŠΈλ Œλ“œ λΆ„μ„μš©)
85
- --show-trend guard arch νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
86
- --minify build μ‹œ μ½”λ“œ μ••μΆ•
87
- --sourcemap build μ‹œ μ†ŒμŠ€λ§΅ 생성
90
+ --report-format guard arch 리포트 ν˜•μ‹: json, markdown, html
91
+ --save-stats guard arch 톡계 μ €μž₯ (νŠΈλ Œλ“œ λΆ„μ„μš©)
92
+ --show-trend guard arch νŠΈλ Œλ“œ 뢄석 ν‘œμ‹œ
93
+ --minify build μ‹œ μ½”λ“œ μ••μΆ•
94
+ --sourcemap build μ‹œ μ†ŒμŠ€λ§΅ 생성
88
95
  --watch build/guard arch 파일 κ°μ‹œ λͺ¨λ“œ
89
96
  --summary monitor μš”μ•½ 좜λ ₯ (JSON λ‘œκ·Έμ—μ„œλ§Œ)
90
97
  --since <duration> monitor μš”μ•½ κΈ°κ°„ (예: 5m, 30s, 1h)
91
98
  --follow <bool> monitor follow λͺ¨λ“œ (κΈ°λ³Έ: true)
92
99
  --file <path> monitor 둜그 파일 직접 μ§€μ •
93
100
  --message <msg> change begin μ‹œ μ„€λͺ… λ©”μ‹œμ§€
94
- --id <id> change rollback μ‹œ νŠΉμ • λ³€κ²½ ID
95
- --keep <n> change prune μ‹œ μœ μ§€ν•  μŠ€λƒ…μƒ· 수 (κΈ°λ³Έ: 5)
96
- --output <path> openapi/doctor 좜λ ₯ 경둜
101
+ --id <id> change rollback μ‹œ νŠΉμ • λ³€κ²½ ID
102
+ --keep <n> change prune μ‹œ μœ μ§€ν•  μŠ€λƒ…μƒ· 수 (κΈ°λ³Έ: 5)
103
+ --output <path> openapi/doctor 좜λ ₯ 경둜
104
+ --from <path> contract diff κΈ°μ€€ λ ˆμ§€μŠ€νŠΈλ¦¬ 경둜
105
+ --to <path> contract diff λŒ€μƒ λ ˆμ§€μŠ€νŠΈλ¦¬ 경둜
106
+ --json contract diff κ²°κ³Ό JSON 좜λ ₯
97
107
  --format <fmt> guard/check 좜λ ₯ ν˜•μ‹: console, json, agent (κΈ°λ³Έ: μžλ™)
98
108
  --format <fmt> doctor 좜λ ₯ ν˜•μ‹: console, json, markdown (κΈ°λ³Έ: console)
99
- --no-llm doctorμ—μ„œ LLM μ‚¬μš© μ•ˆ 함 (ν…œν”Œλ¦Ώ λͺ¨λ“œ)
100
- --model <name> brain setup μ‹œ λͺ¨λΈ 이름 (κΈ°λ³Έ: llama3.2)
101
- --url <url> brain setup μ‹œ Ollama URL
102
- --verbose 상세 좜λ ₯
103
- --help, -h 도움말 ν‘œμ‹œ
104
-
109
+ --no-llm doctorμ—μ„œ LLM μ‚¬μš© μ•ˆ 함 (ν…œν”Œλ¦Ώ λͺ¨λ“œ)
110
+ --model <name> brain setup μ‹œ λͺ¨λΈ 이름 (κΈ°λ³Έ: llama3.2)
111
+ --url <url> brain setup μ‹œ Ollama URL
112
+ --verbose 상세 좜λ ₯
113
+ --help, -h 도움말 ν‘œμ‹œ
114
+
105
115
  Examples:
106
- bunx mandu init --name my-app
116
+ bunx mandu init --name my-app # Tailwind + shadcn/ui κΈ°λ³Έ
117
+ bunx mandu init my-app --minimal # CSS/UI 없이 μ΅œμ†Œ ν…œν”Œλ¦Ώ
118
+ bunx mandu init my-app --theme # 닀크λͺ¨λ“œ ν…Œλ§ˆ 포함
119
+ bunx mandu init my-app --ui none # UI 라이브러리 없이
107
120
  bunx mandu check
108
121
  bunx mandu routes list
109
122
  bunx mandu routes generate
110
123
  bunx mandu dev --port 3000
111
124
  bunx mandu dev --no-guard
112
125
  bunx mandu build --minify
113
- bunx mandu guard
114
- bunx mandu guard arch --preset fsd
115
- bunx mandu guard arch --watch
126
+ bunx mandu guard
127
+ bunx mandu guard arch --preset fsd
128
+ bunx mandu guard arch --watch
116
129
  bunx mandu guard arch --ci --format json
130
+ bunx mandu guard legacy
117
131
  bunx mandu monitor
118
132
  bunx mandu monitor --summary --since 5m
119
133
  bunx mandu doctor
120
- bunx mandu brain setup --model codellama
121
- bunx mandu contract create users
122
- bunx mandu openapi generate --output docs/api.json
123
- bunx mandu change begin --message "Add new route"
124
-
125
- FS Routes Workflow (ꢌμž₯):
126
- 1. init β†’ 2. app/ 폴더에 page.tsx 생성 β†’ 3. dev β†’ 4. build
127
-
128
- Legacy Workflow:
129
- 1. init β†’ 2. spec-upsert β†’ 3. generate β†’ 4. build β†’ 5. guard β†’ 6. dev
130
-
131
- Contract-first Workflow:
132
- 1. contract create β†’ 2. Edit contract β†’ 3. generate β†’ 4. Edit slot β†’ 5. contract validate
133
-
134
- Brain (sLLM) Workflow:
135
- 1. brain setup β†’ 2. doctor (뢄석) β†’ 3. watch (κ°μ‹œ)
136
- `;
137
-
138
- function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
139
- const command = args[0] || "";
140
- const options: Record<string, string> = {};
141
-
142
- for (let i = 1; i < args.length; i++) {
143
- const arg = args[i];
144
- if (arg.startsWith("--")) {
145
- const key = arg.slice(2);
146
- const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
147
- options[key] = value;
148
- } else if (arg === "-h") {
149
- options["help"] = "true";
150
- } else if (!options._positional) {
151
- // First non-flag argument after command is positional (e.g., project name)
152
- options._positional = arg;
153
- }
154
- }
155
-
156
- return { command, options };
157
- }
158
-
159
- /**
160
- * 포트 μ˜΅μ…˜ μ•ˆμ „ν•˜κ²Œ νŒŒμ‹±
161
- * - μˆ«μžκ°€ μ•„λ‹ˆλ©΄ undefined λ°˜ν™˜ (κΈ°λ³Έκ°’ μ‚¬μš©)
162
- * - 유효 λ²”μœ„: 1-65535
163
- */
164
- function parsePort(value: string | undefined, optionName = "port"): number | undefined {
165
- if (!value || value === "true") {
166
- return undefined; // κΈ°λ³Έκ°’ μ‚¬μš©
167
- }
168
-
169
- const port = Number(value);
170
-
171
- if (Number.isNaN(port)) {
172
- console.warn(`⚠️ Invalid --${optionName} value: "${value}" (using default)`);
173
- return undefined;
174
- }
175
-
176
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
177
- console.warn(`⚠️ Invalid --${optionName} range: ${port} (must be 1-65535, using default)`);
178
- return undefined;
179
- }
180
-
181
- return port;
182
- }
183
-
184
- async function main(): Promise<void> {
185
- const args = process.argv.slice(2);
186
- const { command, options } = parseArgs(args);
187
-
188
- if (options.help || command === "help" || !command) {
189
- console.log(HELP_TEXT);
190
- process.exit(0);
191
- }
192
-
193
- let success = true;
194
-
195
- switch (command) {
196
- case "init":
197
- success = await init({
198
- name: options.name || options._positional
199
- });
200
- break;
201
-
202
- case "spec-upsert":
203
- success = await specUpsert({ file: options.file });
204
- break;
205
-
134
+ bunx mandu brain setup --model codellama
135
+ bunx mandu contract create users
136
+ bunx mandu contract build
137
+ bunx mandu contract diff
138
+ bunx mandu openapi generate --output docs/api.json
139
+ bunx mandu change begin --message "Add new route"
140
+
141
+ FS Routes Workflow (ꢌμž₯):
142
+ 1. init β†’ 2. app/ 폴더에 page.tsx 생성 β†’ 3. dev β†’ 4. build
143
+
144
+ Legacy Workflow:
145
+ 1. init β†’ 2. spec-upsert β†’ 3. generate β†’ 4. build β†’ 5. guard β†’ 6. dev
146
+
147
+ Contract-first Workflow:
148
+ 1. contract create β†’ 2. Edit contract β†’ 3. generate β†’ 4. Edit slot β†’ 5. contract validate
149
+
150
+ Brain (sLLM) Workflow:
151
+ 1. brain setup β†’ 2. doctor (뢄석) β†’ 3. watch (κ°μ‹œ)
152
+ `;
153
+
154
+ function parseArgs(args: string[]): { command: string; options: Record<string, string> } {
155
+ const command = args[0] || "";
156
+ const options: Record<string, string> = {};
157
+
158
+ for (let i = 1; i < args.length; i++) {
159
+ const arg = args[i];
160
+ if (arg.startsWith("--")) {
161
+ const key = arg.slice(2);
162
+ const value = args[i + 1] && !args[i + 1].startsWith("--") ? args[++i] : "true";
163
+ options[key] = value;
164
+ } else if (arg === "-h") {
165
+ options["help"] = "true";
166
+ } else if (!options._positional) {
167
+ // First non-flag argument after command is positional (e.g., project name)
168
+ options._positional = arg;
169
+ }
170
+ }
171
+
172
+ return { command, options };
173
+ }
174
+
175
+ /**
176
+ * 포트 μ˜΅μ…˜ μ•ˆμ „ν•˜κ²Œ νŒŒμ‹±
177
+ * - μˆ«μžκ°€ μ•„λ‹ˆλ©΄ undefined λ°˜ν™˜ (κΈ°λ³Έκ°’ μ‚¬μš©)
178
+ * - 유효 λ²”μœ„: 1-65535
179
+ */
180
+ function parsePort(value: string | undefined, optionName = "port"): number | undefined {
181
+ if (!value || value === "true") {
182
+ return undefined; // κΈ°λ³Έκ°’ μ‚¬μš©
183
+ }
184
+
185
+ const port = Number(value);
186
+
187
+ if (Number.isNaN(port)) {
188
+ console.warn(`⚠️ Invalid --${optionName} value: "${value}" (using default)`);
189
+ return undefined;
190
+ }
191
+
192
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
193
+ console.warn(`⚠️ Invalid --${optionName} range: ${port} (must be 1-65535, using default)`);
194
+ return undefined;
195
+ }
196
+
197
+ return port;
198
+ }
199
+
200
+ async function main(): Promise<void> {
201
+ const args = process.argv.slice(2);
202
+ const { command, options } = parseArgs(args);
203
+
204
+ if (options.help || command === "help" || !command) {
205
+ console.log(HELP_TEXT);
206
+ process.exit(0);
207
+ }
208
+
209
+ let success = true;
210
+
211
+ switch (command) {
212
+ case "init":
213
+ success = await init({
214
+ name: options.name || options._positional,
215
+ css: options.css as any,
216
+ ui: options.ui as any,
217
+ theme: options.theme === "true",
218
+ minimal: options.minimal === "true",
219
+ });
220
+ break;
221
+
222
+ case "spec-upsert":
223
+ success = await specUpsert({ file: options.file });
224
+ break;
225
+
206
226
  case "generate":
207
227
  success = await generateApply();
208
228
  break;
@@ -219,39 +239,50 @@ async function main(): Promise<void> {
219
239
 
220
240
  case "guard": {
221
241
  const subCommand = args[1];
242
+ const hasSubCommand = subCommand && !subCommand.startsWith("--");
243
+ const guardArchOptions = {
244
+ preset: options.preset as any,
245
+ watch: options.watch === "true",
246
+ ci: options.ci === "true",
247
+ format: options.format as any,
248
+ quiet: options.quiet === "true",
249
+ srcDir: options["src-dir"],
250
+ listPresets: options["list-presets"] === "true",
251
+ output: options.output,
252
+ reportFormat: (options["report-format"] as any) || "markdown",
253
+ saveStats: options["save-stats"] === "true",
254
+ showTrend: options["show-trend"] === "true",
255
+ };
222
256
  switch (subCommand) {
223
257
  case "arch":
224
- success = await guardArch({
225
- preset: (options.preset as any) || "fsd",
226
- watch: options.watch === "true",
227
- ci: options.ci === "true",
228
- format: options.format as any,
229
- quiet: options.quiet === "true",
230
- srcDir: options["src-dir"],
231
- listPresets: options["list-presets"] === "true",
232
- output: options.output,
233
- reportFormat: (options["report-format"] as any) || "markdown",
234
- saveStats: options["save-stats"] === "true",
235
- showTrend: options["show-trend"] === "true",
236
- });
237
- break;
238
- default:
239
- // κΈ°λ³Έκ°’: λ ˆκ±°μ‹œ guard-check
240
- success = await guardCheck({
241
- autoCorrect: options["no-auto-correct"] !== "true",
242
- });
243
- }
244
- break;
245
- }
246
-
247
- case "build":
248
- success = await build({
249
- minify: options.minify === "true",
250
- sourcemap: options.sourcemap === "true",
251
- watch: options.watch === "true",
252
- });
253
- break;
254
-
258
+ success = await guardArch(guardArchOptions);
259
+ break;
260
+ case "legacy":
261
+ case "spec":
262
+ success = await guardCheck({
263
+ autoCorrect: options["no-auto-correct"] !== "true",
264
+ });
265
+ break;
266
+ default:
267
+ if (hasSubCommand) {
268
+ console.error(`❌ Unknown guard subcommand: ${subCommand}`);
269
+ console.log("\nUsage: bunx mandu guard <arch|legacy>");
270
+ process.exit(1);
271
+ }
272
+ // κΈ°λ³Έκ°’: architecture guard
273
+ success = await guardArch(guardArchOptions);
274
+ }
275
+ break;
276
+ }
277
+
278
+ case "build":
279
+ success = await build({
280
+ minify: options.minify === "true",
281
+ sourcemap: options.sourcemap === "true",
282
+ watch: options.watch === "true",
283
+ });
284
+ break;
285
+
255
286
  case "dev":
256
287
  await dev({
257
288
  port: parsePort(options.port),
@@ -261,128 +292,139 @@ async function main(): Promise<void> {
261
292
  legacy: options.legacy === "true",
262
293
  });
263
294
  break;
264
-
265
- case "routes": {
266
- const subCommand = args[1];
267
- switch (subCommand) {
268
- case "generate":
269
- success = await routesGenerate({
270
- output: options.output,
271
- verbose: options.verbose === "true",
272
- });
273
- break;
274
- case "list":
275
- success = await routesList({
276
- verbose: options.verbose === "true",
277
- });
278
- break;
279
- case "watch":
280
- success = await routesWatch({
281
- output: options.output,
282
- verbose: options.verbose === "true",
283
- });
284
- break;
285
- default:
286
- // κΈ°λ³Έκ°’: list
287
- if (!subCommand) {
288
- success = await routesList({
289
- verbose: options.verbose === "true",
290
- });
291
- } else {
292
- console.error(`❌ Unknown routes subcommand: ${subCommand}`);
293
- console.log("\nUsage: bunx mandu routes <generate|list|watch>");
294
- process.exit(1);
295
- }
296
- }
297
- break;
298
- }
299
-
300
- case "contract": {
301
- const subCommand = args[1];
302
- switch (subCommand) {
303
- case "create": {
304
- const routeId = args[2] || options._positional;
305
- if (!routeId) {
306
- console.error("❌ Route ID is required");
307
- console.log("\nUsage: bunx mandu contract create <routeId>");
308
- process.exit(1);
309
- }
310
- success = await contractCreate({ routeId });
311
- break;
312
- }
313
- case "validate":
314
- success = await contractValidate({ verbose: options.verbose === "true" });
315
- break;
316
- default:
317
- console.error(`❌ Unknown contract subcommand: ${subCommand}`);
318
- console.log("\nUsage: bunx mandu contract <create|validate>");
319
- process.exit(1);
320
- }
321
- break;
322
- }
323
-
324
- case "openapi": {
325
- const subCommand = args[1];
326
- switch (subCommand) {
327
- case "generate":
328
- success = await openAPIGenerate({
329
- output: options.output,
330
- title: options.title,
331
- version: options.version,
332
- });
333
- break;
334
- case "serve":
335
- success = await openAPIServe({
336
- port: parsePort(options.port),
337
- });
338
- break;
339
- default:
340
- console.error(`❌ Unknown openapi subcommand: ${subCommand}`);
341
- console.log("\nUsage: bunx mandu openapi <generate|serve>");
342
- process.exit(1);
343
- }
344
- break;
345
- }
346
-
347
- case "change": {
348
- const subCommand = args[1];
349
- switch (subCommand) {
350
- case "begin":
351
- success = await changeBegin({ message: options.message });
352
- break;
353
- case "commit":
354
- success = await changeCommit();
355
- break;
356
- case "rollback":
357
- success = await changeRollback({ id: options.id });
358
- break;
359
- case "status":
360
- success = await changeStatus();
361
- break;
362
- case "list":
363
- success = await changeList();
364
- break;
365
- case "prune":
366
- success = await changePrune({
367
- keep: options.keep ? Number(options.keep) : undefined,
368
- });
369
- break;
370
- default:
371
- console.error(`❌ Unknown change subcommand: ${subCommand}`);
372
- console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
373
- process.exit(1);
374
- }
375
- break;
376
- }
377
-
378
- case "doctor":
379
- success = await doctor({
380
- format: (options.format as "console" | "json" | "markdown") || "console",
381
- useLLM: options["no-llm"] !== "true",
382
- output: options.output,
383
- });
384
- break;
385
-
295
+
296
+ case "routes": {
297
+ const subCommand = args[1];
298
+ switch (subCommand) {
299
+ case "generate":
300
+ success = await routesGenerate({
301
+ output: options.output,
302
+ verbose: options.verbose === "true",
303
+ });
304
+ break;
305
+ case "list":
306
+ success = await routesList({
307
+ verbose: options.verbose === "true",
308
+ });
309
+ break;
310
+ case "watch":
311
+ success = await routesWatch({
312
+ output: options.output,
313
+ verbose: options.verbose === "true",
314
+ });
315
+ break;
316
+ default:
317
+ // κΈ°λ³Έκ°’: list
318
+ if (!subCommand) {
319
+ success = await routesList({
320
+ verbose: options.verbose === "true",
321
+ });
322
+ } else {
323
+ console.error(`❌ Unknown routes subcommand: ${subCommand}`);
324
+ console.log("\nUsage: bunx mandu routes <generate|list|watch>");
325
+ process.exit(1);
326
+ }
327
+ }
328
+ break;
329
+ }
330
+
331
+ case "contract": {
332
+ const subCommand = args[1];
333
+ switch (subCommand) {
334
+ case "create": {
335
+ const routeId = args[2] || options._positional;
336
+ if (!routeId) {
337
+ console.error("❌ Route ID is required");
338
+ console.log("\nUsage: bunx mandu contract create <routeId>");
339
+ process.exit(1);
340
+ }
341
+ success = await contractCreate({ routeId });
342
+ break;
343
+ }
344
+ case "validate":
345
+ success = await contractValidate({ verbose: options.verbose === "true" });
346
+ break;
347
+ case "build":
348
+ success = await contractBuild({ output: options.output });
349
+ break;
350
+ case "diff":
351
+ success = await contractDiff({
352
+ from: options.from,
353
+ to: options.to,
354
+ output: options.output,
355
+ json: options.json === "true",
356
+ });
357
+ break;
358
+ default:
359
+ console.error(`❌ Unknown contract subcommand: ${subCommand}`);
360
+ console.log("\nUsage: bunx mandu contract <create|validate|build|diff>");
361
+ process.exit(1);
362
+ }
363
+ break;
364
+ }
365
+
366
+ case "openapi": {
367
+ const subCommand = args[1];
368
+ switch (subCommand) {
369
+ case "generate":
370
+ success = await openAPIGenerate({
371
+ output: options.output,
372
+ title: options.title,
373
+ version: options.version,
374
+ });
375
+ break;
376
+ case "serve":
377
+ success = await openAPIServe({
378
+ port: parsePort(options.port),
379
+ });
380
+ break;
381
+ default:
382
+ console.error(`❌ Unknown openapi subcommand: ${subCommand}`);
383
+ console.log("\nUsage: bunx mandu openapi <generate|serve>");
384
+ process.exit(1);
385
+ }
386
+ break;
387
+ }
388
+
389
+ case "change": {
390
+ const subCommand = args[1];
391
+ switch (subCommand) {
392
+ case "begin":
393
+ success = await changeBegin({ message: options.message });
394
+ break;
395
+ case "commit":
396
+ success = await changeCommit();
397
+ break;
398
+ case "rollback":
399
+ success = await changeRollback({ id: options.id });
400
+ break;
401
+ case "status":
402
+ success = await changeStatus();
403
+ break;
404
+ case "list":
405
+ success = await changeList();
406
+ break;
407
+ case "prune":
408
+ success = await changePrune({
409
+ keep: options.keep ? Number(options.keep) : undefined,
410
+ });
411
+ break;
412
+ default:
413
+ console.error(`❌ Unknown change subcommand: ${subCommand}`);
414
+ console.log(`\nUsage: bunx mandu change <begin|commit|rollback|status|list|prune>`);
415
+ process.exit(1);
416
+ }
417
+ break;
418
+ }
419
+
420
+ case "doctor":
421
+ success = await doctor({
422
+ format: (options.format as "console" | "json" | "markdown") || "console",
423
+ useLLM: options["no-llm"] !== "true",
424
+ output: options.output,
425
+ });
426
+ break;
427
+
386
428
  case "watch":
387
429
  success = await watch({
388
430
  status: options.status === "true",
@@ -400,41 +442,41 @@ async function main(): Promise<void> {
400
442
  });
401
443
  break;
402
444
 
403
- case "brain": {
404
- const subCommand = args[1];
405
- switch (subCommand) {
406
- case "setup":
407
- success = await brainSetup({
408
- model: options.model,
409
- url: options.url,
410
- skipCheck: options["skip-check"] === "true",
411
- });
412
- break;
413
- case "status":
414
- success = await brainStatus({
415
- verbose: options.verbose === "true",
416
- });
417
- break;
418
- default:
419
- console.error(`❌ Unknown brain subcommand: ${subCommand}`);
420
- console.log("\nUsage: bunx mandu brain <setup|status>");
421
- process.exit(1);
422
- }
423
- break;
424
- }
425
-
426
- default:
427
- console.error(`❌ Unknown command: ${command}`);
428
- console.log(HELP_TEXT);
429
- process.exit(1);
430
- }
431
-
432
- if (!success) {
433
- process.exit(1);
434
- }
435
- }
436
-
437
- main().catch((error) => {
438
- console.error("❌ μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜:", error);
439
- process.exit(1);
440
- });
445
+ case "brain": {
446
+ const subCommand = args[1];
447
+ switch (subCommand) {
448
+ case "setup":
449
+ success = await brainSetup({
450
+ model: options.model,
451
+ url: options.url,
452
+ skipCheck: options["skip-check"] === "true",
453
+ });
454
+ break;
455
+ case "status":
456
+ success = await brainStatus({
457
+ verbose: options.verbose === "true",
458
+ });
459
+ break;
460
+ default:
461
+ console.error(`❌ Unknown brain subcommand: ${subCommand}`);
462
+ console.log("\nUsage: bunx mandu brain <setup|status>");
463
+ process.exit(1);
464
+ }
465
+ break;
466
+ }
467
+
468
+ default:
469
+ console.error(`❌ Unknown command: ${command}`);
470
+ console.log(HELP_TEXT);
471
+ process.exit(1);
472
+ }
473
+
474
+ if (!success) {
475
+ process.exit(1);
476
+ }
477
+ }
478
+
479
+ main().catch((error) => {
480
+ console.error("❌ μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜:", error);
481
+ process.exit(1);
482
+ });