@mandujs/core 0.9.46 → 0.11.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 (56) hide show
  1. package/README.md +79 -10
  2. package/package.json +1 -1
  3. package/src/brain/doctor/config-analyzer.ts +498 -0
  4. package/src/brain/doctor/index.ts +10 -0
  5. package/src/change/snapshot.ts +46 -1
  6. package/src/change/types.ts +13 -0
  7. package/src/config/index.ts +9 -2
  8. package/src/config/mcp-ref.ts +348 -0
  9. package/src/config/mcp-status.ts +348 -0
  10. package/src/config/metadata.test.ts +308 -0
  11. package/src/config/metadata.ts +293 -0
  12. package/src/config/symbols.ts +144 -0
  13. package/src/config/validate.ts +122 -65
  14. package/src/config/watcher.ts +311 -0
  15. package/src/contract/index.ts +26 -25
  16. package/src/contract/protection.ts +364 -0
  17. package/src/error/domains.ts +265 -0
  18. package/src/error/index.ts +25 -13
  19. package/src/errors/extractor.ts +409 -0
  20. package/src/errors/index.ts +19 -0
  21. package/src/filling/context.ts +29 -1
  22. package/src/filling/deps.ts +238 -0
  23. package/src/filling/filling.ts +94 -8
  24. package/src/filling/index.ts +18 -0
  25. package/src/guard/analyzer.ts +7 -2
  26. package/src/guard/config-guard.ts +281 -0
  27. package/src/guard/decision-memory.test.ts +293 -0
  28. package/src/guard/decision-memory.ts +532 -0
  29. package/src/guard/healing.test.ts +259 -0
  30. package/src/guard/healing.ts +874 -0
  31. package/src/guard/index.ts +119 -0
  32. package/src/guard/negotiation.test.ts +282 -0
  33. package/src/guard/negotiation.ts +975 -0
  34. package/src/guard/semantic-slots.test.ts +379 -0
  35. package/src/guard/semantic-slots.ts +796 -0
  36. package/src/index.ts +4 -1
  37. package/src/lockfile/generate.ts +259 -0
  38. package/src/lockfile/index.ts +186 -0
  39. package/src/lockfile/lockfile.test.ts +410 -0
  40. package/src/lockfile/types.ts +184 -0
  41. package/src/lockfile/validate.ts +308 -0
  42. package/src/logging/index.ts +22 -0
  43. package/src/logging/transports.ts +365 -0
  44. package/src/plugins/index.ts +38 -0
  45. package/src/plugins/registry.ts +377 -0
  46. package/src/plugins/types.ts +363 -0
  47. package/src/runtime/security.ts +155 -0
  48. package/src/runtime/server.ts +318 -256
  49. package/src/runtime/session-key.ts +328 -0
  50. package/src/utils/differ.test.ts +342 -0
  51. package/src/utils/differ.ts +482 -0
  52. package/src/utils/hasher.test.ts +326 -0
  53. package/src/utils/hasher.ts +319 -0
  54. package/src/utils/index.ts +29 -0
  55. package/src/utils/safe-io.ts +188 -0
  56. package/src/utils/string-safe.ts +298 -0
@@ -176,3 +176,122 @@ export {
176
176
  type TrendAnalysis,
177
177
  type LayerStatistics,
178
178
  } from "./statistics";
179
+
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+ // Config Guard - 설정 무결성 검증
182
+ // ═══════════════════════════════════════════════════════════════════════════
183
+
184
+ export {
185
+ guardConfig,
186
+ quickConfigGuard,
187
+ formatConfigGuardResult,
188
+ formatConfigGuardAsJSON,
189
+ calculateHealthScore,
190
+ type ConfigGuardResult,
191
+ type ConfigGuardError,
192
+ type ConfigGuardWarning,
193
+ type ConfigGuardOptions,
194
+ type UnifiedHealthResult,
195
+ } from "./config-guard";
196
+
197
+ // ═══════════════════════════════════════════════════════════════════════════
198
+ // Self-Healing Guard
199
+ // ═══════════════════════════════════════════════════════════════════════════
200
+
201
+ export {
202
+ // Main API
203
+ checkWithHealing,
204
+ generateHealing,
205
+ applyHealing,
206
+ healAll,
207
+ explainRule,
208
+ // Types
209
+ type HealingOption,
210
+ type HealingFixResult,
211
+ type HealingSuggestion,
212
+ type HealingContext,
213
+ type HealingItem,
214
+ type HealingResult,
215
+ type RuleExplanation,
216
+ } from "./healing";
217
+
218
+ // ═══════════════════════════════════════════════════════════════════════════
219
+ // Decision Memory - 아키텍처 결정 기억 시스템
220
+ // ═══════════════════════════════════════════════════════════════════════════
221
+
222
+ export {
223
+ // Core API
224
+ getAllDecisions,
225
+ getDecisionById,
226
+ searchDecisions,
227
+ saveDecision,
228
+ checkConsistency,
229
+ getNextDecisionId,
230
+ // Compact Architecture
231
+ generateCompactArchitecture,
232
+ updateCompactArchitecture,
233
+ getCompactArchitecture,
234
+ // Utilities
235
+ parseADRMarkdown,
236
+ formatADRAsMarkdown,
237
+ // Constants
238
+ DECISIONS_DIR,
239
+ ARCHITECTURE_FILE,
240
+ // Types
241
+ type DecisionStatus,
242
+ type ArchitectureDecision,
243
+ type DecisionSearchResult,
244
+ type ConsistencyCheckResult,
245
+ type CompactArchitecture,
246
+ } from "./decision-memory";
247
+
248
+ // ═══════════════════════════════════════════════════════════════════════════
249
+ // Semantic Slots - 의미론적 슬롯 검증
250
+ // ═══════════════════════════════════════════════════════════════════════════
251
+
252
+ export {
253
+ // Validation API
254
+ validateSlotConstraints,
255
+ validateSlots,
256
+ extractSlotMetadata,
257
+ // Analysis utilities
258
+ countCodeLines,
259
+ calculateCyclomaticComplexity,
260
+ extractImports as extractSlotImports,
261
+ extractFunctionCalls,
262
+ checkPattern,
263
+ // Default constraints
264
+ DEFAULT_SLOT_CONSTRAINTS,
265
+ API_SLOT_CONSTRAINTS,
266
+ READONLY_SLOT_CONSTRAINTS,
267
+ // Types
268
+ type SlotConstraints,
269
+ type SlotPattern,
270
+ type SlotMetadata,
271
+ type CustomRule,
272
+ type ConstraintViolation,
273
+ type SlotValidationResult,
274
+ } from "./semantic-slots";
275
+
276
+ // ═══════════════════════════════════════════════════════════════════════════
277
+ // Architecture Negotiation - AI-Framework 협상
278
+ // ═══════════════════════════════════════════════════════════════════════════
279
+
280
+ export {
281
+ // Core API
282
+ negotiate,
283
+ generateScaffold,
284
+ analyzeExistingStructure,
285
+ // Utilities
286
+ detectCategory,
287
+ // Types
288
+ type NegotiationRequest,
289
+ type NegotiationResponse,
290
+ type FeatureCategory,
291
+ type DirectoryProposal,
292
+ type FileProposal,
293
+ type FileTemplate,
294
+ type SlotProposal,
295
+ type RelatedDecision,
296
+ type ScaffoldResult,
297
+ } from "./negotiation";
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Architecture Negotiation Tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeAll, afterAll } from "bun:test";
6
+ import { mkdir, rm, mkdtemp, readFile, stat } from "fs/promises";
7
+ import { join } from "path";
8
+ import { tmpdir } from "os";
9
+ import {
10
+ negotiate,
11
+ generateScaffold,
12
+ analyzeExistingStructure,
13
+ detectCategory,
14
+ type NegotiationRequest,
15
+ } from "./negotiation";
16
+
17
+ // ═══════════════════════════════════════════════════════════════════════════
18
+ // Test Setup
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+
21
+ let TEST_DIR: string;
22
+
23
+ beforeAll(async () => {
24
+ TEST_DIR = await mkdtemp(join(tmpdir(), "test-negotiation-"));
25
+
26
+ // 기본 프로젝트 구조 생성
27
+ await mkdir(join(TEST_DIR, "spec", "decisions"), { recursive: true });
28
+ await mkdir(join(TEST_DIR, "server", "domain"), { recursive: true });
29
+ await mkdir(join(TEST_DIR, "shared"), { recursive: true });
30
+
31
+ // 샘플 ADR 생성
32
+ await Bun.write(
33
+ join(TEST_DIR, "spec", "decisions", "ADR-001-auth.md"),
34
+ `# Use JWT for Authentication
35
+
36
+ **ID:** ADR-001
37
+ **Status:** accepted
38
+ **Date:** 2024-01-15
39
+ **Tags:** auth, jwt, security
40
+
41
+ ## Context
42
+ Need authentication for API.
43
+
44
+ ## Decision
45
+ Use JWT with refresh tokens.
46
+
47
+ ## Consequences
48
+ - Token management required
49
+ `
50
+ );
51
+ });
52
+
53
+ afterAll(async () => {
54
+ await rm(TEST_DIR, { recursive: true, force: true });
55
+ });
56
+
57
+ // ═══════════════════════════════════════════════════════════════════════════
58
+ // Unit Tests - Category Detection
59
+ // ═══════════════════════════════════════════════════════════════════════════
60
+
61
+ describe("Architecture Negotiation - Category Detection", () => {
62
+ it("should detect auth category", () => {
63
+ expect(detectCategory("사용자 인증 기능 추가")).toBe("auth");
64
+ expect(detectCategory("Add login feature")).toBe("auth");
65
+ expect(detectCategory("JWT 토큰 구현")).toBe("auth");
66
+ });
67
+
68
+ it("should detect crud category", () => {
69
+ expect(detectCategory("사용자 목록 조회")).toBe("crud");
70
+ expect(detectCategory("Create user management")).toBe("crud");
71
+ expect(detectCategory("CRUD for products")).toBe("crud");
72
+ });
73
+
74
+ it("should detect api category", () => {
75
+ expect(detectCategory("API 엔드포인트 추가")).toBe("api");
76
+ expect(detectCategory("REST endpoint")).toBe("api");
77
+ });
78
+
79
+ it("should detect ui category", () => {
80
+ expect(detectCategory("버튼 컴포넌트 만들기")).toBe("ui");
81
+ expect(detectCategory("Add modal component")).toBe("ui");
82
+ });
83
+
84
+ it("should detect integration category", () => {
85
+ expect(detectCategory("Stripe 결제 연동")).toBe("integration");
86
+ expect(detectCategory("third-party 서비스 통합")).toBe("integration");
87
+ expect(detectCategory("webhook 처리")).toBe("integration");
88
+ });
89
+
90
+ it("should fallback to other", () => {
91
+ expect(detectCategory("something completely different")).toBe("other");
92
+ });
93
+ });
94
+
95
+ // ═══════════════════════════════════════════════════════════════════════════
96
+ // Unit Tests - Negotiate
97
+ // ═══════════════════════════════════════════════════════════════════════════
98
+
99
+ describe("Architecture Negotiation - negotiate()", () => {
100
+ it("should return approved plan for auth feature", async () => {
101
+ const request: NegotiationRequest = {
102
+ intent: "사용자 인증 기능 추가",
103
+ requirements: ["JWT 기반", "리프레시 토큰"],
104
+ constraints: ["기존 User 모델 활용"],
105
+ };
106
+
107
+ const result = await negotiate(request, TEST_DIR);
108
+
109
+ expect(result.approved).toBe(true);
110
+ expect(result.structure.length).toBeGreaterThan(0);
111
+ expect(result.slots.length).toBeGreaterThan(0);
112
+ expect(result.preset).toBe("mandu");
113
+ expect(result.nextSteps.length).toBeGreaterThan(0);
114
+ });
115
+
116
+ it("should include related decisions", async () => {
117
+ const request: NegotiationRequest = {
118
+ intent: "Add authentication",
119
+ category: "auth",
120
+ };
121
+
122
+ const result = await negotiate(request, TEST_DIR);
123
+
124
+ // ADR-001이 auth 태그를 가지고 있으므로 관련 결정으로 포함되어야 함
125
+ expect(result.relatedDecisions.length).toBeGreaterThanOrEqual(0);
126
+ });
127
+
128
+ it("should generate structure for CRUD feature", async () => {
129
+ const request: NegotiationRequest = {
130
+ intent: "사용자 관리 CRUD",
131
+ };
132
+
133
+ const result = await negotiate(request, TEST_DIR);
134
+
135
+ expect(result.approved).toBe(true);
136
+ expect(result.structure.some((s) => s.path.includes("domain"))).toBe(true);
137
+ expect(result.structure.some((s) => s.path.includes("api"))).toBe(true);
138
+ });
139
+
140
+ it("should generate structure for UI feature", async () => {
141
+ const request: NegotiationRequest = {
142
+ intent: "모달 컴포넌트 추가",
143
+ category: "ui",
144
+ };
145
+
146
+ const result = await negotiate(request, TEST_DIR);
147
+
148
+ expect(result.approved).toBe(true);
149
+ expect(result.structure.some((s) => s.path.includes("widgets"))).toBe(true);
150
+ });
151
+
152
+ it("should estimate file count", async () => {
153
+ const request: NegotiationRequest = {
154
+ intent: "Simple API endpoint",
155
+ category: "api",
156
+ };
157
+
158
+ const result = await negotiate(request, TEST_DIR);
159
+
160
+ expect(result.estimatedFiles).toBeGreaterThan(0);
161
+ });
162
+ });
163
+
164
+ // ═══════════════════════════════════════════════════════════════════════════
165
+ // Unit Tests - Scaffold Generation
166
+ // ═══════════════════════════════════════════════════════════════════════════
167
+
168
+ describe("Architecture Negotiation - generateScaffold()", () => {
169
+ it("should generate scaffold files (dry run)", async () => {
170
+ const request: NegotiationRequest = {
171
+ intent: "test feature",
172
+ category: "util",
173
+ };
174
+
175
+ const plan = await negotiate(request, TEST_DIR);
176
+ const result = await generateScaffold(plan.structure, TEST_DIR, { dryRun: true });
177
+
178
+ expect(result.success).toBe(true);
179
+ expect(result.createdDirs.length).toBeGreaterThan(0);
180
+ expect(result.createdFiles.length).toBeGreaterThan(0);
181
+ expect(result.errors.length).toBe(0);
182
+ });
183
+
184
+ it("should actually create files when not dry run", async () => {
185
+ const testSubDir = await mkdtemp(join(TEST_DIR, "scaffold-"));
186
+
187
+ const request: NegotiationRequest = {
188
+ intent: "payment feature",
189
+ category: "integration",
190
+ };
191
+
192
+ const plan = await negotiate(request, testSubDir);
193
+ const result = await generateScaffold(plan.structure, testSubDir);
194
+
195
+ expect(result.success).toBe(true);
196
+
197
+ // 실제 파일 확인
198
+ for (const file of result.createdFiles.slice(0, 2)) {
199
+ const content = await readFile(join(testSubDir, file), "utf-8");
200
+ expect(content.length).toBeGreaterThan(0);
201
+ }
202
+ });
203
+
204
+ it("should skip existing files without overwrite flag", async () => {
205
+ const testSubDir = await mkdtemp(join(TEST_DIR, "skip-"));
206
+
207
+ // 먼저 파일 생성
208
+ const request: NegotiationRequest = {
209
+ intent: "skip test",
210
+ category: "util",
211
+ };
212
+
213
+ const plan = await negotiate(request, testSubDir);
214
+ await generateScaffold(plan.structure, testSubDir);
215
+
216
+ // 다시 생성 시도
217
+ const result2 = await generateScaffold(plan.structure, testSubDir);
218
+
219
+ expect(result2.skippedFiles.length).toBeGreaterThan(0);
220
+ });
221
+ });
222
+
223
+ // ═══════════════════════════════════════════════════════════════════════════
224
+ // Unit Tests - Analyze Structure
225
+ // ═══════════════════════════════════════════════════════════════════════════
226
+
227
+ describe("Architecture Negotiation - analyzeExistingStructure()", () => {
228
+ it("should detect existing layers", async () => {
229
+ const result = await analyzeExistingStructure(TEST_DIR);
230
+
231
+ // TEST_DIR에 server/domain과 shared가 있음
232
+ expect(result.layers).toContain("server/domain");
233
+ expect(result.layers).toContain("shared");
234
+ });
235
+
236
+ it("should provide recommendations for missing layers", async () => {
237
+ const emptyDir = await mkdtemp(join(tmpdir(), "empty-struct-"));
238
+
239
+ const result = await analyzeExistingStructure(emptyDir);
240
+
241
+ expect(result.recommendations.length).toBeGreaterThan(0);
242
+
243
+ await rm(emptyDir, { recursive: true, force: true });
244
+ });
245
+ });
246
+
247
+ // ═══════════════════════════════════════════════════════════════════════════
248
+ // Integration Tests
249
+ // ═══════════════════════════════════════════════════════════════════════════
250
+
251
+ describe("Architecture Negotiation - Integration", () => {
252
+ it("should complete full negotiation flow", async () => {
253
+ const testSubDir = await mkdtemp(join(TEST_DIR, "full-flow-"));
254
+
255
+ // 1. 협상
256
+ const request: NegotiationRequest = {
257
+ intent: "결제 기능 추가",
258
+ requirements: ["Stripe 연동", "웹훅 처리"],
259
+ constraints: ["기존 주문 시스템 연동"],
260
+ };
261
+
262
+ const plan = await negotiate(request, testSubDir);
263
+
264
+ expect(plan.approved).toBe(true);
265
+ expect(plan.warnings.length).toBeGreaterThanOrEqual(0);
266
+ expect(plan.nextSteps.length).toBeGreaterThan(0);
267
+
268
+ // 2. Scaffold 생성
269
+ const scaffold = await generateScaffold(plan.structure, testSubDir);
270
+
271
+ expect(scaffold.success).toBe(true);
272
+ expect(scaffold.createdFiles.length).toBeGreaterThan(0);
273
+
274
+ // 3. 생성된 파일 내용 확인
275
+ const firstFile = scaffold.createdFiles[0];
276
+ if (firstFile) {
277
+ const content = await readFile(join(testSubDir, firstFile), "utf-8");
278
+ expect(content).toContain("/**");
279
+ expect(content.length).toBeGreaterThan(50);
280
+ }
281
+ });
282
+ });