@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.
- package/README.md +79 -10
- package/package.json +1 -1
- package/src/brain/doctor/config-analyzer.ts +498 -0
- package/src/brain/doctor/index.ts +10 -0
- package/src/change/snapshot.ts +46 -1
- package/src/change/types.ts +13 -0
- package/src/config/index.ts +9 -2
- package/src/config/mcp-ref.ts +348 -0
- package/src/config/mcp-status.ts +348 -0
- package/src/config/metadata.test.ts +308 -0
- package/src/config/metadata.ts +293 -0
- package/src/config/symbols.ts +144 -0
- package/src/config/validate.ts +122 -65
- package/src/config/watcher.ts +311 -0
- package/src/contract/index.ts +26 -25
- package/src/contract/protection.ts +364 -0
- package/src/error/domains.ts +265 -0
- package/src/error/index.ts +25 -13
- package/src/errors/extractor.ts +409 -0
- package/src/errors/index.ts +19 -0
- package/src/filling/context.ts +29 -1
- package/src/filling/deps.ts +238 -0
- package/src/filling/filling.ts +94 -8
- package/src/filling/index.ts +18 -0
- package/src/guard/analyzer.ts +7 -2
- package/src/guard/config-guard.ts +281 -0
- package/src/guard/decision-memory.test.ts +293 -0
- package/src/guard/decision-memory.ts +532 -0
- package/src/guard/healing.test.ts +259 -0
- package/src/guard/healing.ts +874 -0
- package/src/guard/index.ts +119 -0
- package/src/guard/negotiation.test.ts +282 -0
- package/src/guard/negotiation.ts +975 -0
- package/src/guard/semantic-slots.test.ts +379 -0
- package/src/guard/semantic-slots.ts +796 -0
- package/src/index.ts +4 -1
- package/src/lockfile/generate.ts +259 -0
- package/src/lockfile/index.ts +186 -0
- package/src/lockfile/lockfile.test.ts +410 -0
- package/src/lockfile/types.ts +184 -0
- package/src/lockfile/validate.ts +308 -0
- package/src/logging/index.ts +22 -0
- package/src/logging/transports.ts +365 -0
- package/src/plugins/index.ts +38 -0
- package/src/plugins/registry.ts +377 -0
- package/src/plugins/types.ts +363 -0
- package/src/runtime/security.ts +155 -0
- package/src/runtime/server.ts +318 -256
- package/src/runtime/session-key.ts +328 -0
- package/src/utils/differ.test.ts +342 -0
- package/src/utils/differ.ts +482 -0
- package/src/utils/hasher.test.ts +326 -0
- package/src/utils/hasher.ts +319 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/safe-io.ts +188 -0
- package/src/utils/string-safe.ts +298 -0
package/src/guard/index.ts
CHANGED
|
@@ -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
|
+
});
|