@mandujs/core 0.8.2 β†’ 0.9.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.
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Contract Glue Generator Tests
3
+ */
4
+
5
+ import { describe, test, expect } from "bun:test";
6
+ import {
7
+ generateContractTypeGlue,
8
+ generateContractTemplate,
9
+ generateContractTypesIndex,
10
+ } from "./contract-glue";
11
+ import type { RouteSpec } from "../spec/schema";
12
+
13
+ describe("generateContractTypeGlue", () => {
14
+ test("should generate type glue for route with contract", () => {
15
+ const route: RouteSpec = {
16
+ id: "users",
17
+ pattern: "/api/users",
18
+ kind: "api",
19
+ module: "generated/routes/api/users.ts",
20
+ contractModule: "spec/contracts/users.contract.ts",
21
+ slotModule: "spec/slots/users.slot.ts",
22
+ };
23
+
24
+ const result = generateContractTypeGlue(route);
25
+
26
+ expect(result).toContain("// Generated by Mandu");
27
+ expect(result).toContain("import type { InferContract");
28
+ expect(result).toContain("export type UsersContract");
29
+ expect(result).toContain("export type UsersGetQuery");
30
+ expect(result).toContain("export type UsersPostBody");
31
+ expect(result).toContain("export type UsersResponse200");
32
+ expect(result).toContain("export { contract }");
33
+ });
34
+
35
+ test("should return empty string for route without contract", () => {
36
+ const route: RouteSpec = {
37
+ id: "users",
38
+ pattern: "/api/users",
39
+ kind: "api",
40
+ module: "generated/routes/api/users.ts",
41
+ slotModule: "spec/slots/users.slot.ts",
42
+ };
43
+
44
+ const result = generateContractTypeGlue(route);
45
+
46
+ expect(result).toBe("");
47
+ });
48
+
49
+ test("should convert kebab-case to PascalCase", () => {
50
+ const route: RouteSpec = {
51
+ id: "user-profiles",
52
+ pattern: "/api/user-profiles",
53
+ kind: "api",
54
+ module: "generated/routes/api/user-profiles.ts",
55
+ contractModule: "spec/contracts/user-profiles.contract.ts",
56
+ };
57
+
58
+ const result = generateContractTypeGlue(route);
59
+
60
+ expect(result).toContain("export type UserProfilesContract");
61
+ expect(result).toContain("export type UserProfilesGetQuery");
62
+ });
63
+
64
+ test("should convert snake_case to PascalCase", () => {
65
+ const route: RouteSpec = {
66
+ id: "user_settings",
67
+ pattern: "/api/user_settings",
68
+ kind: "api",
69
+ module: "generated/routes/api/user_settings.ts",
70
+ contractModule: "spec/contracts/user_settings.contract.ts",
71
+ };
72
+
73
+ const result = generateContractTypeGlue(route);
74
+
75
+ expect(result).toContain("export type UserSettingsContract");
76
+ });
77
+ });
78
+
79
+ describe("generateContractTemplate", () => {
80
+ test("should generate template with GET method", () => {
81
+ const route: RouteSpec = {
82
+ id: "users",
83
+ pattern: "/api/users",
84
+ kind: "api",
85
+ module: "generated/routes/api/users.ts",
86
+ methods: ["GET"],
87
+ };
88
+
89
+ const result = generateContractTemplate(route);
90
+
91
+ expect(result).toContain("// πŸ“œ Mandu Contract - users");
92
+ expect(result).toContain("// Pattern: /api/users");
93
+ expect(result).toContain('import { z } from "zod"');
94
+ expect(result).toContain("Mandu.contract({");
95
+ expect(result).toContain("GET: {");
96
+ expect(result).toContain("query: z.object({");
97
+ expect(result).not.toContain("POST:");
98
+ });
99
+
100
+ test("should generate template with GET and POST methods", () => {
101
+ const route: RouteSpec = {
102
+ id: "users",
103
+ pattern: "/api/users",
104
+ kind: "api",
105
+ module: "generated/routes/api/users.ts",
106
+ methods: ["GET", "POST"],
107
+ };
108
+
109
+ const result = generateContractTemplate(route);
110
+
111
+ expect(result).toContain("GET: {");
112
+ expect(result).toContain("POST: {");
113
+ expect(result).toContain("body: z.object({");
114
+ expect(result).toContain("201: z.object({"); // 201 for POST
115
+ });
116
+
117
+ test("should generate template with all CRUD methods", () => {
118
+ const route: RouteSpec = {
119
+ id: "users",
120
+ pattern: "/api/users/:id",
121
+ kind: "api",
122
+ module: "generated/routes/api/users.ts",
123
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
124
+ };
125
+
126
+ const result = generateContractTemplate(route);
127
+
128
+ expect(result).toContain("GET: {");
129
+ expect(result).toContain("POST: {");
130
+ expect(result).toContain("PUT: {");
131
+ expect(result).toContain("PATCH: {");
132
+ expect(result).toContain("DELETE: {");
133
+ });
134
+
135
+ test("should include helpful comments", () => {
136
+ const route: RouteSpec = {
137
+ id: "users",
138
+ pattern: "/api/users",
139
+ kind: "api",
140
+ module: "generated/routes/api/users.ts",
141
+ methods: ["GET"],
142
+ };
143
+
144
+ const result = generateContractTemplate(route);
145
+
146
+ expect(result).toContain("// TODO:");
147
+ expect(result).toContain("πŸ’‘ Contract μ‚¬μš©λ²•:");
148
+ expect(result).toContain("mandu generate");
149
+ });
150
+
151
+ test("should set correct description and tags", () => {
152
+ const route: RouteSpec = {
153
+ id: "user-profiles",
154
+ pattern: "/api/user-profiles",
155
+ kind: "api",
156
+ module: "generated/routes/api/user-profiles.ts",
157
+ methods: ["GET"],
158
+ };
159
+
160
+ const result = generateContractTemplate(route);
161
+
162
+ expect(result).toContain('description: "UserProfiles API"');
163
+ expect(result).toContain('tags: ["user-profiles"]');
164
+ });
165
+
166
+ test("should include error response schemas", () => {
167
+ const route: RouteSpec = {
168
+ id: "users",
169
+ pattern: "/api/users",
170
+ kind: "api",
171
+ module: "generated/routes/api/users.ts",
172
+ methods: ["GET"],
173
+ };
174
+
175
+ const result = generateContractTemplate(route);
176
+
177
+ expect(result).toContain("400: z.object({");
178
+ expect(result).toContain("error: z.string()");
179
+ expect(result).toContain("404: z.object({");
180
+ });
181
+ });
182
+
183
+ describe("generateContractTypesIndex", () => {
184
+ test("should generate index file with all route exports", () => {
185
+ const routeIds = ["users", "posts", "comments"];
186
+
187
+ const result = generateContractTypesIndex(routeIds);
188
+
189
+ expect(result).toContain("// Generated by Mandu");
190
+ expect(result).toContain('export * from "./users.types";');
191
+ expect(result).toContain('export * from "./posts.types";');
192
+ expect(result).toContain('export * from "./comments.types";');
193
+ });
194
+
195
+ test("should handle empty route list", () => {
196
+ const routeIds: string[] = [];
197
+
198
+ const result = generateContractTypesIndex(routeIds);
199
+
200
+ expect(result).toContain("// Generated by Mandu");
201
+ expect(result).not.toContain("export *");
202
+ });
203
+
204
+ test("should handle single route", () => {
205
+ const routeIds = ["users"];
206
+
207
+ const result = generateContractTypesIndex(routeIds);
208
+
209
+ expect(result).toContain('export * from "./users.types";');
210
+ });
211
+ });
@@ -1,6 +1,7 @@
1
1
  import { GUARD_RULES, FORBIDDEN_IMPORTS, type GuardViolation } from "./rules";
2
2
  import { verifyLock, computeHash } from "../spec/lock";
3
3
  import { runContractGuardCheck } from "./contract-guard";
4
+ import { validateSlotContent } from "../slot/validator";
4
5
  import type { RoutesManifest } from "../spec/schema";
5
6
  import type { GeneratedMap } from "../generator/generate";
6
7
  import path from "path";
@@ -147,6 +148,51 @@ export async function checkSlotFileExists(
147
148
  return violations;
148
149
  }
149
150
 
151
+ // Rule 6: Slot content validation (μ‹ κ·œ)
152
+ export async function checkSlotContentValidation(
153
+ manifest: RoutesManifest,
154
+ rootDir: string
155
+ ): Promise<GuardViolation[]> {
156
+ const violations: GuardViolation[] = [];
157
+
158
+ for (const route of manifest.routes) {
159
+ if (!route.slotModule) continue;
160
+
161
+ const slotPath = path.join(rootDir, route.slotModule);
162
+ const content = await readFileContent(slotPath);
163
+
164
+ if (!content) continue; // File doesn't exist, handled by checkSlotFileExists
165
+
166
+ const validationResult = validateSlotContent(content);
167
+
168
+ // Convert slot validation issues to guard violations
169
+ for (const issue of validationResult.issues) {
170
+ if (issue.severity === "error") {
171
+ // Map slot issue codes to guard rule IDs
172
+ let ruleId = "SLOT_VALIDATION_ERROR";
173
+ if (issue.code === "MISSING_DEFAULT_EXPORT") {
174
+ ruleId = GUARD_RULES.SLOT_MISSING_DEFAULT_EXPORT?.id ?? "SLOT_MISSING_DEFAULT_EXPORT";
175
+ } else if (issue.code === "NO_RESPONSE_PATTERN" || issue.code === "INVALID_HANDLER_RETURN") {
176
+ ruleId = GUARD_RULES.SLOT_INVALID_RETURN?.id ?? "SLOT_INVALID_RETURN";
177
+ } else if (issue.code === "MISSING_FILLING_PATTERN") {
178
+ ruleId = GUARD_RULES.SLOT_MISSING_FILLING_PATTERN?.id ?? "SLOT_MISSING_FILLING_PATTERN";
179
+ }
180
+
181
+ violations.push({
182
+ ruleId,
183
+ file: route.slotModule,
184
+ message: `[${route.id}] ${issue.message}`,
185
+ suggestion: issue.suggestion,
186
+ line: issue.line,
187
+ severity: issue.severity,
188
+ });
189
+ }
190
+ }
191
+ }
192
+
193
+ return violations;
194
+ }
195
+
150
196
  // Rule 4: Forbidden imports in generated files
151
197
  export async function checkForbiddenImportsInGenerated(
152
198
  rootDir: string,
@@ -249,7 +295,11 @@ export async function runGuardCheck(
249
295
  const slotViolations = await checkSlotFileExists(manifest, rootDir);
250
296
  violations.push(...slotViolations);
251
297
 
252
- // Rule 6-9: Contract-related checks
298
+ // Rule 6: Slot content validation (μ‹ κ·œ - κ°•ν™”λœ 검증)
299
+ const slotContentViolations = await checkSlotContentValidation(manifest, rootDir);
300
+ violations.push(...slotContentViolations);
301
+
302
+ // Rule 7-10: Contract-related checks
253
303
  const contractViolations = await runContractGuardCheck(manifest, rootDir);
254
304
  violations.push(...contractViolations);
255
305
 
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Contract Guard Tests
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
6
+ import { mkdir, rm, writeFile } from "fs/promises";
7
+ import path from "path";
8
+ import {
9
+ checkMissingContract,
10
+ checkContractFileExists,
11
+ checkContractSlotConsistency,
12
+ runContractGuardCheck,
13
+ } from "./contract-guard";
14
+ import type { RoutesManifest } from "../spec/schema";
15
+
16
+ const TEST_DIR = path.join(process.cwd(), ".test-guard");
17
+
18
+ describe("Contract Guard", () => {
19
+ beforeEach(async () => {
20
+ await mkdir(TEST_DIR, { recursive: true });
21
+ await mkdir(path.join(TEST_DIR, "spec/contracts"), { recursive: true });
22
+ await mkdir(path.join(TEST_DIR, "spec/slots"), { recursive: true });
23
+ });
24
+
25
+ afterEach(async () => {
26
+ await rm(TEST_DIR, { recursive: true, force: true });
27
+ });
28
+
29
+ describe("checkMissingContract", () => {
30
+ test("should detect API routes without contracts", async () => {
31
+ const manifest: RoutesManifest = {
32
+ version: 1,
33
+ routes: [
34
+ {
35
+ id: "users",
36
+ pattern: "/api/users",
37
+ kind: "api",
38
+ module: "generated/routes/api/users.ts",
39
+ slotModule: "spec/slots/users.slot.ts",
40
+ // No contractModule
41
+ },
42
+ ],
43
+ };
44
+
45
+ const violations = await checkMissingContract(manifest, TEST_DIR);
46
+
47
+ expect(violations.length).toBe(1);
48
+ expect(violations[0].ruleId).toBe("CONTRACT_MISSING");
49
+ expect(violations[0].routeId).toBe("users");
50
+ });
51
+
52
+ test("should pass API routes with contracts", async () => {
53
+ const manifest: RoutesManifest = {
54
+ version: 1,
55
+ routes: [
56
+ {
57
+ id: "users",
58
+ pattern: "/api/users",
59
+ kind: "api",
60
+ module: "generated/routes/api/users.ts",
61
+ slotModule: "spec/slots/users.slot.ts",
62
+ contractModule: "spec/contracts/users.contract.ts",
63
+ },
64
+ ],
65
+ };
66
+
67
+ const violations = await checkMissingContract(manifest, TEST_DIR);
68
+
69
+ expect(violations.length).toBe(0);
70
+ });
71
+
72
+ test("should skip non-API routes", async () => {
73
+ const manifest: RoutesManifest = {
74
+ version: 1,
75
+ routes: [
76
+ {
77
+ id: "home",
78
+ pattern: "/",
79
+ kind: "page",
80
+ module: "generated/routes/home.ts",
81
+ slotModule: "spec/slots/home.slot.ts",
82
+ // No contractModule - but that's fine for pages
83
+ },
84
+ ],
85
+ };
86
+
87
+ const violations = await checkMissingContract(manifest, TEST_DIR);
88
+
89
+ expect(violations.length).toBe(0);
90
+ });
91
+ });
92
+
93
+ describe("checkContractFileExists", () => {
94
+ test("should detect missing contract files", async () => {
95
+ const manifest: RoutesManifest = {
96
+ version: 1,
97
+ routes: [
98
+ {
99
+ id: "users",
100
+ pattern: "/api/users",
101
+ kind: "api",
102
+ module: "generated/routes/api/users.ts",
103
+ contractModule: "spec/contracts/users.contract.ts",
104
+ },
105
+ ],
106
+ };
107
+
108
+ const violations = await checkContractFileExists(manifest, TEST_DIR);
109
+
110
+ expect(violations.length).toBe(1);
111
+ expect(violations[0].ruleId).toBe("CONTRACT_NOT_FOUND");
112
+ });
113
+
114
+ test("should pass when contract file exists", async () => {
115
+ // Create contract file
116
+ await writeFile(
117
+ path.join(TEST_DIR, "spec/contracts/users.contract.ts"),
118
+ `export default { request: {}, response: {} }`
119
+ );
120
+
121
+ const manifest: RoutesManifest = {
122
+ version: 1,
123
+ routes: [
124
+ {
125
+ id: "users",
126
+ pattern: "/api/users",
127
+ kind: "api",
128
+ module: "generated/routes/api/users.ts",
129
+ contractModule: "spec/contracts/users.contract.ts",
130
+ },
131
+ ],
132
+ };
133
+
134
+ const violations = await checkContractFileExists(manifest, TEST_DIR);
135
+
136
+ expect(violations.length).toBe(0);
137
+ });
138
+ });
139
+
140
+ describe("checkContractSlotConsistency", () => {
141
+ test("should detect methods in contract but not in slot", async () => {
142
+ // Create contract with GET and POST
143
+ await writeFile(
144
+ path.join(TEST_DIR, "spec/contracts/users.contract.ts"),
145
+ `
146
+ export default {
147
+ request: {
148
+ GET: { query: {} },
149
+ POST: { body: {} },
150
+ DELETE: {},
151
+ },
152
+ response: {},
153
+ };
154
+ `
155
+ );
156
+
157
+ // Create slot with only GET
158
+ await writeFile(
159
+ path.join(TEST_DIR, "spec/slots/users.slot.ts"),
160
+ `
161
+ export default Mandu.filling()
162
+ .get((ctx) => ctx.ok({ data: [] }));
163
+ `
164
+ );
165
+
166
+ const manifest: RoutesManifest = {
167
+ version: 1,
168
+ routes: [
169
+ {
170
+ id: "users",
171
+ pattern: "/api/users",
172
+ kind: "api",
173
+ module: "generated/routes/api/users.ts",
174
+ contractModule: "spec/contracts/users.contract.ts",
175
+ slotModule: "spec/slots/users.slot.ts",
176
+ },
177
+ ],
178
+ };
179
+
180
+ const violations = await checkContractSlotConsistency(manifest, TEST_DIR);
181
+
182
+ expect(violations.length).toBe(1);
183
+ expect(violations[0].ruleId).toBe("CONTRACT_METHOD_NOT_IMPLEMENTED");
184
+ expect(violations[0].missingMethods).toContain("POST");
185
+ expect(violations[0].missingMethods).toContain("DELETE");
186
+ });
187
+
188
+ test("should detect methods in slot but not in contract", async () => {
189
+ // Create contract with only GET
190
+ await writeFile(
191
+ path.join(TEST_DIR, "spec/contracts/users.contract.ts"),
192
+ `
193
+ export default {
194
+ request: {
195
+ GET: { query: {} },
196
+ },
197
+ response: {},
198
+ };
199
+ `
200
+ );
201
+
202
+ // Create slot with GET, POST, and DELETE
203
+ await writeFile(
204
+ path.join(TEST_DIR, "spec/slots/users.slot.ts"),
205
+ `
206
+ export default Mandu.filling()
207
+ .get((ctx) => ctx.ok({ data: [] }))
208
+ .post((ctx) => ctx.created({}))
209
+ .delete((ctx) => ctx.noContent());
210
+ `
211
+ );
212
+
213
+ const manifest: RoutesManifest = {
214
+ version: 1,
215
+ routes: [
216
+ {
217
+ id: "users",
218
+ pattern: "/api/users",
219
+ kind: "api",
220
+ module: "generated/routes/api/users.ts",
221
+ contractModule: "spec/contracts/users.contract.ts",
222
+ slotModule: "spec/slots/users.slot.ts",
223
+ },
224
+ ],
225
+ };
226
+
227
+ const violations = await checkContractSlotConsistency(manifest, TEST_DIR);
228
+
229
+ expect(violations.length).toBe(1);
230
+ expect(violations[0].ruleId).toBe("CONTRACT_METHOD_UNDOCUMENTED");
231
+ expect(violations[0].undocumentedMethods).toContain("POST");
232
+ expect(violations[0].undocumentedMethods).toContain("DELETE");
233
+ });
234
+
235
+ test("should pass when contract and slot are in sync", async () => {
236
+ // Create contract with GET and POST
237
+ await writeFile(
238
+ path.join(TEST_DIR, "spec/contracts/users.contract.ts"),
239
+ `
240
+ export default {
241
+ request: {
242
+ GET: { query: {} },
243
+ POST: { body: {} },
244
+ },
245
+ response: {},
246
+ };
247
+ `
248
+ );
249
+
250
+ // Create slot with GET and POST
251
+ await writeFile(
252
+ path.join(TEST_DIR, "spec/slots/users.slot.ts"),
253
+ `
254
+ export default Mandu.filling()
255
+ .get((ctx) => ctx.ok({ data: [] }))
256
+ .post((ctx) => ctx.created({}));
257
+ `
258
+ );
259
+
260
+ const manifest: RoutesManifest = {
261
+ version: 1,
262
+ routes: [
263
+ {
264
+ id: "users",
265
+ pattern: "/api/users",
266
+ kind: "api",
267
+ module: "generated/routes/api/users.ts",
268
+ contractModule: "spec/contracts/users.contract.ts",
269
+ slotModule: "spec/slots/users.slot.ts",
270
+ },
271
+ ],
272
+ };
273
+
274
+ const violations = await checkContractSlotConsistency(manifest, TEST_DIR);
275
+
276
+ expect(violations.length).toBe(0);
277
+ });
278
+ });
279
+
280
+ describe("runContractGuardCheck", () => {
281
+ test("should run all checks", async () => {
282
+ // Missing contract file
283
+ const manifest: RoutesManifest = {
284
+ version: 1,
285
+ routes: [
286
+ {
287
+ id: "users",
288
+ pattern: "/api/users",
289
+ kind: "api",
290
+ module: "generated/routes/api/users.ts",
291
+ contractModule: "spec/contracts/users.contract.ts",
292
+ slotModule: "spec/slots/users.slot.ts",
293
+ },
294
+ ],
295
+ };
296
+
297
+ const violations = await runContractGuardCheck(manifest, TEST_DIR);
298
+
299
+ // Should find CONTRACT_NOT_FOUND
300
+ expect(violations.some((v) => v.ruleId === "CONTRACT_NOT_FOUND")).toBe(true);
301
+ });
302
+ });
303
+ });
@@ -3,12 +3,15 @@ export interface GuardViolation {
3
3
  file: string;
4
4
  message: string;
5
5
  suggestion: string;
6
+ line?: number;
7
+ severity?: "error" | "warning";
6
8
  }
7
9
 
8
10
  export interface GuardRule {
9
11
  id: string;
10
12
  name: string;
11
13
  description: string;
14
+ severity: "error" | "warning";
12
15
  }
13
16
 
14
17
  export const GUARD_RULES: Record<string, GuardRule> = {
@@ -16,47 +19,81 @@ export const GUARD_RULES: Record<string, GuardRule> = {
16
19
  id: "SPEC_HASH_MISMATCH",
17
20
  name: "Spec Hash Mismatch",
18
21
  description: "spec.lock.json의 ν•΄μ‹œμ™€ ν˜„μž¬ spec이 μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€",
22
+ severity: "error",
19
23
  },
20
24
  GENERATED_MANUAL_EDIT: {
21
25
  id: "GENERATED_MANUAL_EDIT",
22
26
  name: "Generated File Manual Edit",
23
27
  description: "generated 파일이 μˆ˜λ™μœΌλ‘œ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€",
28
+ severity: "error",
24
29
  },
25
30
  INVALID_GENERATED_IMPORT: {
26
31
  id: "INVALID_GENERATED_IMPORT",
27
32
  name: "Invalid Generated Import",
28
33
  description: "non-generated νŒŒμΌμ—μ„œ generated νŒŒμΌμ„ 직접 import ν–ˆμŠ΅λ‹ˆλ‹€",
34
+ severity: "error",
29
35
  },
30
36
  FORBIDDEN_IMPORT_IN_GENERATED: {
31
37
  id: "FORBIDDEN_IMPORT_IN_GENERATED",
32
38
  name: "Forbidden Import in Generated",
33
39
  description: "generated νŒŒμΌμ—μ„œ κΈˆμ§€λœ λͺ¨λ“ˆμ„ import ν–ˆμŠ΅λ‹ˆλ‹€",
40
+ severity: "error",
34
41
  },
35
42
  SLOT_NOT_FOUND: {
36
43
  id: "SLOT_NOT_FOUND",
37
44
  name: "Slot File Not Found",
38
45
  description: "spec에 λͺ…μ‹œλœ slotModule νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€",
46
+ severity: "error",
47
+ },
48
+ // Slot validation rules (μ‹ κ·œ)
49
+ SLOT_MISSING_DEFAULT_EXPORT: {
50
+ id: "SLOT_MISSING_DEFAULT_EXPORT",
51
+ name: "Slot Missing Default Export",
52
+ description: "Slot νŒŒμΌμ— export defaultκ°€ μ—†μŠ΅λ‹ˆλ‹€",
53
+ severity: "error",
54
+ },
55
+ SLOT_INVALID_RETURN: {
56
+ id: "SLOT_INVALID_RETURN",
57
+ name: "Slot Invalid Handler Return",
58
+ description: "Slot ν•Έλ“€λŸ¬κ°€ μ˜¬λ°”λ₯Έ Responseλ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€",
59
+ severity: "error",
60
+ },
61
+ SLOT_NO_RESPONSE_PATTERN: {
62
+ id: "SLOT_NO_RESPONSE_PATTERN",
63
+ name: "Slot No Response Pattern",
64
+ description: "Slot ν•Έλ“€λŸ¬μ— ctx.ok(), ctx.json() λ“±μ˜ 응닡 νŒ¨ν„΄μ΄ μ—†μŠ΅λ‹ˆλ‹€",
65
+ severity: "error",
66
+ },
67
+ SLOT_MISSING_FILLING_PATTERN: {
68
+ id: "SLOT_MISSING_FILLING_PATTERN",
69
+ name: "Slot Missing Filling Pattern",
70
+ description: "Slot νŒŒμΌμ— Mandu.filling() νŒ¨ν„΄μ΄ μ—†μŠ΅λ‹ˆλ‹€",
71
+ severity: "error",
39
72
  },
40
73
  // Contract-related rules
41
74
  CONTRACT_MISSING: {
42
75
  id: "CONTRACT_MISSING",
43
76
  name: "Contract Missing",
44
77
  description: "API λΌμš°νŠΈμ— contractκ°€ μ •μ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€",
78
+ severity: "warning",
45
79
  },
46
80
  CONTRACT_NOT_FOUND: {
47
81
  id: "CONTRACT_NOT_FOUND",
48
82
  name: "Contract File Not Found",
49
83
  description: "spec에 λͺ…μ‹œλœ contractModule νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€",
84
+ severity: "error",
50
85
  },
51
86
  CONTRACT_METHOD_NOT_IMPLEMENTED: {
52
87
  id: "CONTRACT_METHOD_NOT_IMPLEMENTED",
53
88
  name: "Contract Method Not Implemented",
54
89
  description: "Contract에 μ •μ˜λœ λ©”μ„œλ“œκ°€ Slot에 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€",
90
+ severity: "error",
55
91
  },
56
92
  CONTRACT_METHOD_UNDOCUMENTED: {
57
93
  id: "CONTRACT_METHOD_UNDOCUMENTED",
58
94
  name: "Contract Method Undocumented",
59
95
  description: "Slot에 κ΅¬ν˜„λœ λ©”μ„œλ“œκ°€ Contract에 λ¬Έμ„œν™”λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€",
96
+ severity: "warning",
60
97
  },
61
98
  };
62
99
 
package/src/index.ts CHANGED
@@ -10,3 +10,5 @@ export * from "./slot";
10
10
  export * from "./bundler";
11
11
  export * from "./contract";
12
12
  export * from "./openapi";
13
+ export * from "./brain";
14
+ export * from "./watcher";