@mandujs/cli 0.9.21 → 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 +62 -15
  3. package/package.json +2 -2
  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 +301 -0
  11. package/src/main.ts +421 -361
  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 +20 -11
  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/README.ko.md CHANGED
@@ -44,12 +44,35 @@ bunx @mandujs/cli init my-app
44
44
  생성되는 구조:
45
45
  ```
46
46
  my-app/
47
+ ├── app/ # FS Routes
48
+ │ └── page.tsx # /
49
+ ├── src/ # 아키텍처 레이어
50
+ │ ├── client/ # 클라이언트 (FSD)
51
+ │ │ ├── app/
52
+ │ │ ├── pages/
53
+ │ │ ├── widgets/
54
+ │ │ ├── features/
55
+ │ │ ├── entities/
56
+ │ │ └── shared/
57
+ │ ├── server/ # 서버 (Clean)
58
+ │ │ ├── api/
59
+ │ │ ├── application/
60
+ │ │ ├── domain/
61
+ │ │ ├── infra/
62
+ │ │ └── core/
63
+ │ └── shared/ # 공용
64
+ │ ├── contracts/ # client-safe 계약
65
+ │ ├── types/
66
+ │ ├── utils/
67
+ │ │ ├── client/ # 클라이언트 safe 유틸
68
+ │ │ └── server/ # 서버 전용 유틸
69
+ │ ├── schema/ # 서버 전용 스키마
70
+ │ └── env/ # 서버 전용 환경
47
71
  ├── apps/
48
72
  │ ├── server/main.ts # 서버 진입점
49
73
  │ └── web/entry.tsx # 클라이언트 진입점
50
74
  ├── spec/
51
75
  │ └── routes.manifest.json # SSOT - 라우트 정의
52
- ├── tests/ # 테스트 템플릿
53
76
  ├── package.json
54
77
  └── tsconfig.json
55
78
  ```
@@ -82,20 +105,50 @@ bun run generate
82
105
 
83
106
  ### `mandu guard`
84
107
 
85
- 아키텍처 규칙을 검사하고 위반 사항을 자동 수정합니다.
108
+ 아키텍처 규칙을 검사합니다. (기본: mandu 프리셋)
86
109
 
87
110
  ```bash
88
111
  bun run guard
89
112
 
113
+ # 프리셋 변경
114
+ bunx mandu guard --preset fsd
115
+
116
+ # CI 모드 (warning도 실패 처리)
117
+ bunx mandu guard --ci
118
+ ```
119
+
120
+ ### `mandu guard legacy`
121
+
122
+ 레거시 Spec Guard 검사 + 자동 수정입니다.
123
+
124
+ ```bash
125
+ bunx mandu guard legacy
126
+
90
127
  # 자동 수정 비활성화
91
- bunx mandu guard --no-auto-correct
128
+ bunx mandu guard legacy --no-auto-correct
92
129
  ```
93
130
 
94
- 자동 수정 가능한 규칙:
131
+ 자동 수정 가능한 규칙(legacy):
95
132
  - `SPEC_HASH_MISMATCH` → lock 파일 갱신
96
133
  - `GENERATED_MANUAL_EDIT` → 코드 재생성
97
134
  - `SLOT_NOT_FOUND` → slot 파일 생성
98
135
 
136
+ ### `mandu contract build`
137
+
138
+ 계약 레지스트리(`.mandu/contracts.json`)를 생성합니다.
139
+
140
+ ```bash
141
+ bunx mandu contract build
142
+ ```
143
+
144
+ ### `mandu contract diff`
145
+
146
+ 계약 변경사항(major/minor/patch)을 비교합니다.
147
+
148
+ ```bash
149
+ bunx mandu contract diff
150
+ ```
151
+
99
152
  ## Spec 파일 작성
100
153
 
101
154
  `spec/routes.manifest.json`이 모든 라우트의 단일 진실 공급원(SSOT)입니다.
package/README.md CHANGED
@@ -61,6 +61,34 @@ app/
61
61
  bunx mandu build
62
62
  ```
63
63
 
64
+ ### Default Architecture Layout
65
+
66
+ ```
67
+ app/ # FS Routes
68
+ src/
69
+ client/ # Client (FSD)
70
+ app/
71
+ pages/
72
+ widgets/
73
+ features/
74
+ entities/
75
+ shared/
76
+ server/ # Server (Clean)
77
+ api/
78
+ application/
79
+ domain/
80
+ infra/
81
+ core/
82
+ shared/ # Universal shared
83
+ contracts/ # Client-safe contracts
84
+ types/
85
+ utils/
86
+ client/ # Client-safe utils
87
+ server/ # Server-only utils
88
+ schema/ # Server-only schema
89
+ env/ # Server-only env
90
+ ```
91
+
64
92
  That's it!
65
93
 
66
94
  ---
@@ -87,11 +115,12 @@ That's it!
87
115
 
88
116
  | Command | Description |
89
117
  |---------|-------------|
90
- | `mandu guard arch` | Run architecture check (default: mandu preset) |
91
- | `mandu guard arch --watch` | Watch mode |
92
- | `mandu guard arch --ci` | CI mode (exit 1 on errors) |
93
- | `mandu guard arch --preset fsd` | Use specific preset |
94
- | `mandu guard arch --output report.md` | Generate report |
118
+ | `mandu guard` | Run architecture check (default: mandu preset) |
119
+ | `mandu guard --watch` | Watch mode |
120
+ | `mandu guard --ci` | CI mode (exit 1 on errors/warnings) |
121
+ | `mandu guard --preset fsd` | Use specific preset |
122
+ | `mandu guard --output report.md` | Generate report |
123
+ | `mandu guard legacy` | Legacy Spec guard (auto-correct) |
95
124
 
96
125
  ### Transaction Commands
97
126
 
@@ -109,6 +138,7 @@ That's it!
109
138
  |---------|-------------|
110
139
  | `mandu doctor` | Analyze Guard failures + suggest patches |
111
140
  | `mandu watch` | Real-time file monitoring |
141
+ | `mandu monitor` | MCP Activity Monitor log stream |
112
142
  | `mandu brain setup` | Configure sLLM (optional) |
113
143
  | `mandu brain status` | Check Brain status |
114
144
 
@@ -118,6 +148,8 @@ That's it!
118
148
  |---------|-------------|
119
149
  | `mandu contract create <routeId>` | Create contract for route |
120
150
  | `mandu contract validate` | Validate contract-slot consistency |
151
+ | `mandu contract build` | Build contract registry |
152
+ | `mandu contract diff` | Diff contracts against registry |
121
153
  | `mandu openapi generate` | Generate OpenAPI 3.0 spec |
122
154
  | `mandu openapi serve` | Start Swagger UI server |
123
155
 
@@ -148,7 +180,7 @@ bun run dev
148
180
  bunx mandu dev --guard
149
181
 
150
182
  # Or run Guard separately
151
- bunx mandu guard arch --watch
183
+ bunx mandu guard --watch
152
184
  ```
153
185
 
154
186
  ### CI/CD Integration
@@ -156,7 +188,7 @@ bunx mandu guard arch --watch
156
188
  ```bash
157
189
  # Build and check
158
190
  bunx mandu build --minify
159
- bunx mandu guard arch --ci --format json
191
+ bunx mandu guard --ci --format json
160
192
  ```
161
193
 
162
194
  ---
@@ -208,10 +240,10 @@ app/
208
240
 
209
241
  ```bash
210
242
  # List all presets
211
- bunx mandu guard arch --list-presets
243
+ bunx mandu guard --list-presets
212
244
 
213
245
  # Use specific preset
214
- bunx mandu guard arch --preset fsd
246
+ bunx mandu guard --preset fsd
215
247
  ```
216
248
 
217
249
  ---
@@ -234,13 +266,13 @@ bunx mandu guard arch --preset fsd
234
266
  | `--sourcemap` | Generate sourcemaps |
235
267
  | `--watch` | Watch mode |
236
268
 
237
- ### `mandu guard arch`
269
+ ### `mandu guard`
238
270
 
239
271
  | Option | Description |
240
272
  |--------|-------------|
241
273
  | `--preset <p>` | Preset: fsd, clean, hexagonal, atomic, mandu |
242
274
  | `--watch` | Watch mode |
243
- | `--ci` | CI mode (exit 1 on errors) |
275
+ | `--ci` | CI mode (exit 1 on errors/warnings) |
244
276
  | `--quiet` | Summary only |
245
277
  | `--format <f>` | Output: console, agent, json |
246
278
  | `--output <path>` | Report file path |
@@ -256,6 +288,16 @@ bunx mandu guard arch --preset fsd
256
288
  | `--no-llm` | Template mode (no LLM) |
257
289
  | `--output <path>` | Output file path |
258
290
 
291
+ ### `mandu monitor`
292
+
293
+ | Option | Description |
294
+ |--------|-------------|
295
+ | `--format <f>` | Output: console, agent, json |
296
+ | `--summary` | Print summary (JSON log only) |
297
+ | `--since <d>` | Summary window: 5m, 30s, 1h |
298
+ | `--follow <bool>` | Follow mode (default: true) |
299
+ | `--file <path>` | Use custom log file |
300
+
259
301
  ---
260
302
 
261
303
  ## Examples
@@ -273,10 +315,11 @@ bunx mandu routes list
273
315
  bunx mandu routes generate
274
316
 
275
317
  # Guard
276
- bunx mandu guard arch
277
- bunx mandu guard arch --watch
278
- bunx mandu guard arch --ci --format json
279
- bunx mandu guard arch --output report.md
318
+ bunx mandu guard
319
+ bunx mandu guard --watch
320
+ bunx mandu guard --ci --format json
321
+ bunx mandu guard --output report.md
322
+ bunx mandu guard legacy
280
323
 
281
324
  # Transactions
282
325
  bunx mandu change begin --message "Add users API"
@@ -287,6 +330,10 @@ bunx mandu change rollback
287
330
  bunx mandu doctor
288
331
  bunx mandu doctor --format json
289
332
 
333
+ # Monitor
334
+ bunx mandu monitor
335
+ bunx mandu monitor --summary --since 5m
336
+
290
337
  # Build
291
338
  bunx mandu build --minify --sourcemap
292
339
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/cli",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
5
5
  "type": "module",
6
6
  "main": "./src/main.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@mandujs/core": "0.9.38"
35
+ "@mandujs/core": "0.9.39"
36
36
  },
37
37
  "engines": {
38
38
  "bun": ">=1.0.0"
@@ -34,6 +34,7 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
34
34
  const preset = options.preset ?? "mandu";
35
35
  const format = resolveOutputFormat(options.format);
36
36
  const quiet = options.quiet === true;
37
+ const strictWarnings = options.ci === true;
37
38
  const enableFsRoutes = !options.legacy && await isDirectory(path.resolve(rootDir, "app"));
38
39
  const specPath = resolveFromCwd("spec/routes.manifest.json");
39
40
  const hasSpec = await pathExists(specPath);
@@ -122,14 +123,45 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
122
123
  fsRoutes: enableFsRoutes
123
124
  ? {
124
125
  noPageToPage: true,
125
- pageCanImport: ["widgets", "features", "entities", "shared"],
126
- layoutCanImport: ["widgets", "shared"],
126
+ pageCanImport: [
127
+ "client/pages",
128
+ "client/widgets",
129
+ "client/features",
130
+ "client/entities",
131
+ "client/shared",
132
+ "shared/contracts",
133
+ "shared/types",
134
+ "shared/utils/client",
135
+ ],
136
+ layoutCanImport: [
137
+ "client/app",
138
+ "client/widgets",
139
+ "client/shared",
140
+ "shared/contracts",
141
+ "shared/types",
142
+ "shared/utils/client",
143
+ ],
144
+ routeCanImport: [
145
+ "server/api",
146
+ "server/application",
147
+ "server/domain",
148
+ "server/infra",
149
+ "server/core",
150
+ "shared/contracts",
151
+ "shared/schema",
152
+ "shared/types",
153
+ "shared/utils/client",
154
+ "shared/utils/server",
155
+ "shared/env",
156
+ ],
127
157
  }
128
158
  : undefined,
129
159
  };
130
160
 
131
161
  const report = await checkDirectory(guardConfig, rootDir);
132
- if (report.bySeverity.error > 0) {
162
+ const hasArchErrors = report.bySeverity.error > 0;
163
+ const hasArchWarnings = report.bySeverity.warn > 0;
164
+ if (hasArchErrors || (strictWarnings && hasArchWarnings)) {
133
165
  success = false;
134
166
  }
135
167
 
@@ -166,7 +198,11 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
166
198
  const checkResult = await runGuardCheck(manifestResult.data, rootDir);
167
199
  legacySummary.passed = checkResult.passed;
168
200
  legacySummary.violations = checkResult.violations.length;
169
- success = success && checkResult.passed;
201
+ if (strictWarnings && checkResult.violations.length > 0) {
202
+ success = false;
203
+ } else {
204
+ success = success && checkResult.passed;
205
+ }
170
206
 
171
207
  if (format === "console") {
172
208
  const legacyReport = buildGuardReport(checkResult);
@@ -200,5 +236,5 @@ export async function check(options: CheckOptions = {}): Promise<boolean> {
200
236
  console.log(JSON.stringify(summary, null, 2));
201
237
  }
202
238
 
203
- return options.ci ? success : true;
239
+ return success;
204
240
  }
@@ -3,17 +3,36 @@
3
3
  * Contract 생성 및 검증 명령어
4
4
  */
5
5
 
6
- import { loadManifest, runContractGuardCheck, generateContractTemplate } from "@mandujs/core";
7
- import path from "path";
8
- import fs from "fs/promises";
6
+ import {
7
+ loadManifest,
8
+ runContractGuardCheck,
9
+ generateContractTemplate,
10
+ buildContractRegistry,
11
+ writeContractRegistry,
12
+ readContractRegistry,
13
+ diffContractRegistry,
14
+ } from "@mandujs/core";
15
+ import path from "path";
16
+ import fs from "fs/promises";
9
17
 
10
18
  interface ContractCreateOptions {
11
19
  routeId: string;
12
20
  }
13
21
 
14
- interface ContractValidateOptions {
15
- verbose?: boolean;
16
- }
22
+ interface ContractValidateOptions {
23
+ verbose?: boolean;
24
+ }
25
+
26
+ interface ContractBuildOptions {
27
+ output?: string;
28
+ }
29
+
30
+ interface ContractDiffOptions {
31
+ from?: string;
32
+ to?: string;
33
+ output?: string;
34
+ json?: boolean;
35
+ }
17
36
 
18
37
  /**
19
38
  * Create a new contract file for a route
@@ -85,7 +104,7 @@ export async function contractCreate(options: ContractCreateOptions): Promise<bo
85
104
  /**
86
105
  * Validate all contracts against their slot implementations
87
106
  */
88
- export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
107
+ export async function contractValidate(options: ContractValidateOptions = {}): Promise<boolean> {
89
108
  const rootDir = process.cwd();
90
109
  const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
91
110
 
@@ -148,5 +167,112 @@ export async function contractValidate(options: ContractValidateOptions = {}): P
148
167
  console.log(`💡 Use --verbose for fix suggestions\n`);
149
168
  }
150
169
 
151
- return false;
152
- }
170
+ return false;
171
+ }
172
+
173
+ /**
174
+ * Build contract registry (.mandu/contracts.json)
175
+ */
176
+ export async function contractBuild(options: ContractBuildOptions = {}): Promise<boolean> {
177
+ const rootDir = process.cwd();
178
+ const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
179
+ const outputPath = options.output || path.join(rootDir, ".mandu", "contracts.json");
180
+
181
+ console.log(`\n📦 Building contract registry...\n`);
182
+
183
+ const manifestResult = await loadManifest(manifestPath);
184
+ if (!manifestResult.success) {
185
+ console.error("❌ Failed to load manifest:", manifestResult.errors);
186
+ return false;
187
+ }
188
+
189
+ const manifest = manifestResult.data!;
190
+ const { registry, warnings } = await buildContractRegistry(manifest, rootDir);
191
+
192
+ if (warnings.length > 0) {
193
+ console.log(`⚠️ ${warnings.length} warning(s):`);
194
+ for (const warning of warnings) {
195
+ console.log(` - ${warning}`);
196
+ }
197
+ console.log();
198
+ }
199
+
200
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
201
+ await writeContractRegistry(outputPath, registry);
202
+
203
+ console.log(`✅ Registry generated: ${path.relative(rootDir, outputPath)}`);
204
+ console.log(`📊 Contracts: ${registry.contracts.length}`);
205
+
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Diff current contracts against a registry
211
+ */
212
+ export async function contractDiff(options: ContractDiffOptions = {}): Promise<boolean> {
213
+ const rootDir = process.cwd();
214
+ const manifestPath = path.join(rootDir, "spec/routes.manifest.json");
215
+ const fromPath = options.from || path.join(rootDir, ".mandu", "contracts.json");
216
+
217
+ console.log(`\n🔍 Diffing contracts...\n`);
218
+
219
+ const fromRegistry = await readContractRegistry(fromPath);
220
+ if (!fromRegistry) {
221
+ console.error(`❌ Registry not found: ${path.relative(rootDir, fromPath)}`);
222
+ console.log(`💡 Run \`mandu contract build\` first.`);
223
+ return false;
224
+ }
225
+
226
+ let toRegistry = options.to ? await readContractRegistry(options.to) : null;
227
+
228
+ if (!toRegistry) {
229
+ const manifestResult = await loadManifest(manifestPath);
230
+ if (!manifestResult.success) {
231
+ console.error("❌ Failed to load manifest:", manifestResult.errors);
232
+ return false;
233
+ }
234
+ const { registry } = await buildContractRegistry(manifestResult.data!, rootDir);
235
+ toRegistry = registry;
236
+ }
237
+
238
+ const diff = diffContractRegistry(fromRegistry, toRegistry);
239
+
240
+ if (options.output) {
241
+ await fs.mkdir(path.dirname(options.output), { recursive: true });
242
+ await Bun.write(options.output, JSON.stringify(diff, null, 2));
243
+ console.log(`✅ Diff saved: ${path.relative(rootDir, options.output)}`);
244
+ }
245
+
246
+ if (options.json) {
247
+ console.log(JSON.stringify(diff, null, 2));
248
+ return diff.summary.major === 0;
249
+ }
250
+
251
+ console.log(`📊 Summary: major ${diff.summary.major}, minor ${diff.summary.minor}, patch ${diff.summary.patch}`);
252
+
253
+ if (diff.added.length > 0) {
254
+ console.log(`\n🟢 Added (${diff.added.length})`);
255
+ for (const entry of diff.added) {
256
+ console.log(` - ${entry.id} (${entry.routeId})`);
257
+ }
258
+ }
259
+
260
+ if (diff.removed.length > 0) {
261
+ console.log(`\n🔴 Removed (${diff.removed.length})`);
262
+ for (const entry of diff.removed) {
263
+ console.log(` - ${entry.id} (${entry.routeId})`);
264
+ }
265
+ }
266
+
267
+ if (diff.changed.length > 0) {
268
+ console.log(`\n🟡 Changed (${diff.changed.length})`);
269
+ for (const change of diff.changed) {
270
+ console.log(` - ${change.id} (${change.routeId}) [${change.severity}]`);
271
+ for (const detail of change.changes) {
272
+ console.log(` • ${detail}`);
273
+ }
274
+ }
275
+ }
276
+
277
+ return diff.summary.major === 0;
278
+ }