@mandujs/core 0.9.45 → 0.10.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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/brain/doctor/config-analyzer.ts +498 -0
  3. package/src/brain/doctor/index.ts +10 -0
  4. package/src/change/snapshot.ts +46 -1
  5. package/src/change/types.ts +13 -0
  6. package/src/config/index.ts +8 -2
  7. package/src/config/mcp-ref.ts +348 -0
  8. package/src/config/mcp-status.ts +348 -0
  9. package/src/config/metadata.test.ts +308 -0
  10. package/src/config/metadata.ts +293 -0
  11. package/src/config/symbols.ts +144 -0
  12. package/src/contract/index.ts +26 -25
  13. package/src/contract/protection.ts +364 -0
  14. package/src/error/domains.ts +265 -0
  15. package/src/error/index.ts +25 -13
  16. package/src/filling/filling.ts +88 -6
  17. package/src/guard/analyzer.ts +7 -2
  18. package/src/guard/config-guard.ts +281 -0
  19. package/src/guard/decision-memory.test.ts +293 -0
  20. package/src/guard/decision-memory.ts +532 -0
  21. package/src/guard/healing.test.ts +259 -0
  22. package/src/guard/healing.ts +874 -0
  23. package/src/guard/index.ts +119 -0
  24. package/src/guard/negotiation.test.ts +282 -0
  25. package/src/guard/negotiation.ts +975 -0
  26. package/src/guard/semantic-slots.test.ts +379 -0
  27. package/src/guard/semantic-slots.ts +796 -0
  28. package/src/index.ts +2 -0
  29. package/src/lockfile/generate.ts +259 -0
  30. package/src/lockfile/index.ts +186 -0
  31. package/src/lockfile/lockfile.test.ts +410 -0
  32. package/src/lockfile/types.ts +184 -0
  33. package/src/lockfile/validate.ts +308 -0
  34. package/src/runtime/security.ts +155 -0
  35. package/src/runtime/server.ts +320 -258
  36. package/src/utils/differ.test.ts +342 -0
  37. package/src/utils/differ.ts +482 -0
  38. package/src/utils/hasher.test.ts +326 -0
  39. package/src/utils/hasher.ts +319 -0
  40. package/src/utils/index.ts +29 -0
  41. package/src/utils/safe-io.ts +188 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.9.45",
3
+ "version": "0.10.0",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Brain Config Analyzer - Lockfile 불일치 원인 분석
3
+ *
4
+ * 설정 변경 사항을 분석하고 원인과 해결책을 제안
5
+ *
6
+ * @see docs/plans/09_lockfile_integration_plan.md
7
+ */
8
+
9
+ import type { ConfigDiff } from "../../utils/differ";
10
+ import type { LLMAdapter } from "../adapters/base";
11
+
12
+ /**
13
+ * 변경 항목 (내부용)
14
+ */
15
+ interface ChangeItem {
16
+ path: string;
17
+ value?: unknown;
18
+ oldValue?: unknown;
19
+ newValue?: unknown;
20
+ }
21
+
22
+ // ============================================
23
+ // 타입
24
+ // ============================================
25
+
26
+ export type ConfigIssueCategory =
27
+ | "security" // 민감정보 변경
28
+ | "mcp" // MCP 서버 설정 변경
29
+ | "server" // 서버 설정 변경
30
+ | "guard" // Guard 설정 변경
31
+ | "general"; // 일반 변경
32
+
33
+ export type ConfigIssueSeverity = "low" | "medium" | "high" | "critical";
34
+
35
+ export interface ConfigMismatchAnalysis {
36
+ /** 변경 카테고리 */
37
+ category: ConfigIssueCategory;
38
+ /** 심각도 */
39
+ severity: ConfigIssueSeverity;
40
+ /** 변경된 필드 경로 */
41
+ path: string;
42
+ /** 근본 원인 설명 */
43
+ rootCause: string;
44
+ /** 제안 사항 */
45
+ suggestions: string[];
46
+ /** 자동 수정 가능 여부 */
47
+ autoFixable: boolean;
48
+ }
49
+
50
+ export interface ConfigAnalysisReport {
51
+ /** 전체 분석 결과 */
52
+ analyses: ConfigMismatchAnalysis[];
53
+ /** 요약 */
54
+ summary: string;
55
+ /** 권장 조치 */
56
+ recommendedAction: "update-lockfile" | "revert-config" | "review-required";
57
+ /** 분석 시각 */
58
+ timestamp: string;
59
+ }
60
+
61
+ // ============================================
62
+ // 카테고리 판별
63
+ // ============================================
64
+
65
+ const SENSITIVE_PATTERNS = [
66
+ /apikey/i,
67
+ /secret/i,
68
+ /token/i,
69
+ /password/i,
70
+ /credential/i,
71
+ /auth/i,
72
+ /private/i,
73
+ ];
74
+
75
+ const MCP_PATTERNS = [
76
+ /^mcpServers/,
77
+ /^mcp\./,
78
+ ];
79
+
80
+ const SERVER_PATTERNS = [
81
+ /^server\./,
82
+ /^port$/,
83
+ /^host$/,
84
+ ];
85
+
86
+ const GUARD_PATTERNS = [
87
+ /^guard\./,
88
+ /^preset$/,
89
+ ];
90
+
91
+ /**
92
+ * 변경 경로에서 카테고리 판별
93
+ */
94
+ function categorizeChange(path: string): ConfigIssueCategory {
95
+ if (SENSITIVE_PATTERNS.some(p => p.test(path))) {
96
+ return "security";
97
+ }
98
+ if (MCP_PATTERNS.some(p => p.test(path))) {
99
+ return "mcp";
100
+ }
101
+ if (SERVER_PATTERNS.some(p => p.test(path))) {
102
+ return "server";
103
+ }
104
+ if (GUARD_PATTERNS.some(p => p.test(path))) {
105
+ return "guard";
106
+ }
107
+ return "general";
108
+ }
109
+
110
+ /**
111
+ * 카테고리별 기본 심각도
112
+ */
113
+ function getDefaultSeverity(category: ConfigIssueCategory): ConfigIssueSeverity {
114
+ switch (category) {
115
+ case "security":
116
+ return "critical";
117
+ case "mcp":
118
+ return "medium";
119
+ case "server":
120
+ return "medium";
121
+ case "guard":
122
+ return "low";
123
+ case "general":
124
+ return "low";
125
+ }
126
+ }
127
+
128
+ // ============================================
129
+ // 분석 함수
130
+ // ============================================
131
+
132
+ /**
133
+ * 단일 변경 분석
134
+ */
135
+ function analyzeChange(
136
+ change: ChangeItem,
137
+ changeType: "added" | "modified" | "removed"
138
+ ): ConfigMismatchAnalysis {
139
+ const category = categorizeChange(change.path);
140
+ const severity = getDefaultSeverity(category);
141
+
142
+ const suggestions = generateSuggestionsForPath(category, change.path, changeType);
143
+ const rootCause = generateRootCauseForPath(category, change.path, changeType);
144
+
145
+ return {
146
+ category,
147
+ severity,
148
+ path: change.path,
149
+ rootCause,
150
+ suggestions,
151
+ autoFixable: category === "general",
152
+ };
153
+ }
154
+
155
+ /**
156
+ * 근본 원인 생성
157
+ */
158
+ function generateRootCauseForPath(
159
+ category: ConfigIssueCategory,
160
+ path: string,
161
+ changeType: "added" | "modified" | "removed"
162
+ ): string {
163
+ const action = changeType === "added"
164
+ ? "추가되었습니다"
165
+ : changeType === "removed"
166
+ ? "삭제되었습니다"
167
+ : "변경되었습니다";
168
+
169
+ switch (category) {
170
+ case "security":
171
+ return `민감 정보 필드 '${path}'가 ${action}. 보안 검토가 필요합니다.`;
172
+
173
+ case "mcp":
174
+ return `MCP 서버 설정 '${path}'가 ${action}. AI 에이전트 통합에 영향을 줄 수 있습니다.`;
175
+
176
+ case "server":
177
+ return `서버 설정 '${path}'가 ${action}. 배포 환경에 영향을 줄 수 있습니다.`;
178
+
179
+ case "guard":
180
+ return `Guard 설정 '${path}'가 ${action}. 아키텍처 검증 규칙이 변경됩니다.`;
181
+
182
+ case "general":
183
+ default:
184
+ return `설정 '${path}'가 ${action}.`;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * 제안 사항 생성
190
+ */
191
+ function generateSuggestionsForPath(
192
+ category: ConfigIssueCategory,
193
+ path: string,
194
+ changeType: "added" | "modified" | "removed"
195
+ ): string[] {
196
+ const suggestions: string[] = [];
197
+
198
+ switch (category) {
199
+ case "security":
200
+ suggestions.push("민감 정보는 환경 변수를 통해 주입하는 것을 권장합니다.");
201
+ suggestions.push(".env 파일에 보관하고 .gitignore에 추가하세요.");
202
+ suggestions.push("의도한 변경이라면 보안 검토 후 'mandu lock'을 실행하세요.");
203
+ break;
204
+
205
+ case "mcp":
206
+ suggestions.push("MCP 서버 설정 변경 시 에이전트 연결을 확인하세요.");
207
+ if (changeType === "added") {
208
+ suggestions.push("새 MCP 서버가 정상적으로 연결되는지 확인하세요.");
209
+ } else if (changeType === "removed") {
210
+ suggestions.push("삭제된 MCP 서버를 사용하는 기능이 없는지 확인하세요.");
211
+ }
212
+ suggestions.push("의도한 변경이라면 'mandu lock'을 실행하세요.");
213
+ break;
214
+
215
+ case "server":
216
+ suggestions.push("서버 설정 변경 시 배포 환경과의 호환성을 확인하세요.");
217
+ if (path.includes("port")) {
218
+ suggestions.push("포트 변경 시 방화벽 규칙도 업데이트하세요.");
219
+ }
220
+ suggestions.push("의도한 변경이라면 'mandu lock'을 실행하세요.");
221
+ break;
222
+
223
+ case "guard":
224
+ suggestions.push("Guard 설정 변경 시 기존 코드가 새 규칙을 위반하지 않는지 확인하세요.");
225
+ suggestions.push("'mandu guard'를 실행하여 위반 사항을 확인하세요.");
226
+ suggestions.push("의도한 변경이라면 'mandu lock'을 실행하세요.");
227
+ break;
228
+
229
+ case "general":
230
+ default:
231
+ suggestions.push("의도한 변경이라면 'mandu lock'을 실행하세요.");
232
+ suggestions.push("의도하지 않은 변경이라면 설정을 원복하세요.");
233
+ break;
234
+ }
235
+
236
+ return suggestions;
237
+ }
238
+
239
+ // ============================================
240
+ // 메인 분석 함수
241
+ // ============================================
242
+
243
+ /**
244
+ * 설정 불일치 분석 (템플릿 기반)
245
+ *
246
+ * @param diff 설정 차이
247
+ * @returns 분석 보고서
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const diff = diffConfig(oldConfig, newConfig);
252
+ * const report = analyzeConfigMismatch(diff);
253
+ * console.log(report.summary);
254
+ * ```
255
+ */
256
+ export function analyzeConfigMismatch(diff: ConfigDiff): ConfigAnalysisReport {
257
+ const analyses: ConfigMismatchAnalysis[] = [];
258
+
259
+ // MCP 서버 변경 분석
260
+ for (const name of diff.mcpServers.added) {
261
+ analyses.push(analyzeChange({ path: `mcpServers.${name}` }, "added"));
262
+ }
263
+ for (const name of diff.mcpServers.removed) {
264
+ analyses.push(analyzeChange({ path: `mcpServers.${name}` }, "removed"));
265
+ }
266
+ for (const item of diff.mcpServers.modified) {
267
+ analyses.push(analyzeChange({ path: `mcpServers.${item.name}` }, "modified"));
268
+ }
269
+
270
+ // 프로젝트 설정 변경 분석
271
+ for (const path of diff.projectConfig.added) {
272
+ analyses.push(analyzeChange({ path }, "added"));
273
+ }
274
+ for (const path of diff.projectConfig.removed) {
275
+ analyses.push(analyzeChange({ path }, "removed"));
276
+ }
277
+ for (const item of diff.projectConfig.modified) {
278
+ analyses.push(analyzeChange({ path: item.key }, "modified"));
279
+ }
280
+
281
+ // 심각도별 정렬 (critical > high > medium > low)
282
+ const severityOrder: Record<ConfigIssueSeverity, number> = {
283
+ critical: 4,
284
+ high: 3,
285
+ medium: 2,
286
+ low: 1,
287
+ };
288
+ analyses.sort((a, b) => severityOrder[b.severity] - severityOrder[a.severity]);
289
+
290
+ // 요약 생성
291
+ const summary = generateSummary(analyses);
292
+
293
+ // 권장 조치 결정
294
+ const recommendedAction = determineRecommendedAction(analyses);
295
+
296
+ return {
297
+ analyses,
298
+ summary,
299
+ recommendedAction,
300
+ timestamp: new Date().toISOString(),
301
+ };
302
+ }
303
+
304
+ /**
305
+ * 요약 생성
306
+ */
307
+ function generateSummary(analyses: ConfigMismatchAnalysis[]): string {
308
+ if (analyses.length === 0) {
309
+ return "변경 사항이 없습니다.";
310
+ }
311
+
312
+ const bySeverity = {
313
+ critical: analyses.filter(a => a.severity === "critical").length,
314
+ high: analyses.filter(a => a.severity === "high").length,
315
+ medium: analyses.filter(a => a.severity === "medium").length,
316
+ low: analyses.filter(a => a.severity === "low").length,
317
+ };
318
+
319
+ const parts: string[] = [];
320
+ if (bySeverity.critical > 0) parts.push(`심각 ${bySeverity.critical}개`);
321
+ if (bySeverity.high > 0) parts.push(`높음 ${bySeverity.high}개`);
322
+ if (bySeverity.medium > 0) parts.push(`중간 ${bySeverity.medium}개`);
323
+ if (bySeverity.low > 0) parts.push(`낮음 ${bySeverity.low}개`);
324
+
325
+ return `총 ${analyses.length}개 변경 감지 (${parts.join(", ")})`;
326
+ }
327
+
328
+ /**
329
+ * 권장 조치 결정
330
+ */
331
+ function determineRecommendedAction(
332
+ analyses: ConfigMismatchAnalysis[]
333
+ ): "update-lockfile" | "revert-config" | "review-required" {
334
+ const hasCritical = analyses.some(a => a.severity === "critical");
335
+ const hasHigh = analyses.some(a => a.severity === "high");
336
+ const hasSecurity = analyses.some(a => a.category === "security");
337
+
338
+ if (hasCritical || hasSecurity) {
339
+ return "review-required";
340
+ }
341
+
342
+ if (hasHigh) {
343
+ return "review-required";
344
+ }
345
+
346
+ // 모두 자동 수정 가능하면 lockfile 업데이트
347
+ if (analyses.every(a => a.autoFixable)) {
348
+ return "update-lockfile";
349
+ }
350
+
351
+ return "review-required";
352
+ }
353
+
354
+ // ============================================
355
+ // 포맷팅
356
+ // ============================================
357
+
358
+ /**
359
+ * 분석 보고서를 콘솔 출력용 문자열로 변환
360
+ */
361
+ export function formatConfigAnalysisReport(report: ConfigAnalysisReport): string {
362
+ const lines: string[] = [];
363
+
364
+ lines.push("═══════════════════════════════════════");
365
+ lines.push("🩺 Config Mismatch Analysis");
366
+ lines.push("═══════════════════════════════════════");
367
+ lines.push("");
368
+ lines.push(`📊 ${report.summary}`);
369
+ lines.push("");
370
+
371
+ if (report.analyses.length > 0) {
372
+ lines.push("변경 사항:");
373
+ lines.push("───────────────────────────────────────");
374
+
375
+ for (const analysis of report.analyses) {
376
+ const icon = getSeverityIcon(analysis.severity);
377
+ lines.push(`${icon} [${analysis.category.toUpperCase()}] ${analysis.path}`);
378
+ lines.push(` ${analysis.rootCause}`);
379
+
380
+ if (analysis.suggestions.length > 0) {
381
+ lines.push(` 제안:`);
382
+ for (const suggestion of analysis.suggestions.slice(0, 2)) {
383
+ lines.push(` • ${suggestion}`);
384
+ }
385
+ }
386
+ lines.push("");
387
+ }
388
+ }
389
+
390
+ lines.push("───────────────────────────────────────");
391
+ lines.push(`권장 조치: ${formatRecommendedAction(report.recommendedAction)}`);
392
+
393
+ return lines.join("\n");
394
+ }
395
+
396
+ function getSeverityIcon(severity: ConfigIssueSeverity): string {
397
+ switch (severity) {
398
+ case "critical": return "🚨";
399
+ case "high": return "🔴";
400
+ case "medium": return "🟡";
401
+ case "low": return "🟢";
402
+ }
403
+ }
404
+
405
+ function formatRecommendedAction(
406
+ action: "update-lockfile" | "revert-config" | "review-required"
407
+ ): string {
408
+ switch (action) {
409
+ case "update-lockfile":
410
+ return "'mandu lock'을 실행하여 lockfile을 업데이트하세요.";
411
+ case "revert-config":
412
+ return "설정을 이전 상태로 원복하세요.";
413
+ case "review-required":
414
+ return "변경 사항을 검토한 후 적절한 조치를 취하세요.";
415
+ }
416
+ }
417
+
418
+ // ============================================
419
+ // LLM 기반 분석 (선택적)
420
+ // ============================================
421
+
422
+ /**
423
+ * LLM을 사용한 심층 분석
424
+ *
425
+ * @param diff 설정 차이
426
+ * @param adapter LLM 어댑터
427
+ * @returns 분석 보고서
428
+ */
429
+ export async function analyzeConfigMismatchWithLLM(
430
+ diff: ConfigDiff,
431
+ adapter: LLMAdapter
432
+ ): Promise<ConfigAnalysisReport> {
433
+ // 기본 템플릿 분석 수행
434
+ const baseReport = analyzeConfigMismatch(diff);
435
+
436
+ // LLM 사용 불가 시 기본 보고서 반환
437
+ const status = await adapter.checkStatus();
438
+ if (!status.available) {
439
+ return baseReport;
440
+ }
441
+
442
+ // LLM 프롬프트 생성
443
+ const prompt = buildConfigAnalysisPrompt(diff, baseReport);
444
+
445
+ try {
446
+ const messages = [{ role: "user" as const, content: prompt }];
447
+ const response = await adapter.complete(messages);
448
+
449
+ // LLM 응답 파싱 및 병합
450
+ const content = response.content ?? "";
451
+ return mergeWithLLMAnalysis(baseReport, content);
452
+ } catch {
453
+ // LLM 분석 실패 시 기본 보고서 반환
454
+ return baseReport;
455
+ }
456
+ }
457
+
458
+ function buildConfigAnalysisPrompt(diff: ConfigDiff, baseReport: ConfigAnalysisReport): string {
459
+ const mcpAdded = diff.mcpServers.added.map(n => `- mcpServers.${n}`).join("\n") || "없음";
460
+ const mcpRemoved = diff.mcpServers.removed.map(n => `- mcpServers.${n}`).join("\n") || "없음";
461
+ const mcpModified = diff.mcpServers.modified.map(m => `- mcpServers.${m.name}`).join("\n") || "없음";
462
+
463
+ const configAdded = diff.projectConfig.added.map(p => `- ${p}`).join("\n") || "없음";
464
+ const configRemoved = diff.projectConfig.removed.map(p => `- ${p}`).join("\n") || "없음";
465
+ const configModified = diff.projectConfig.modified.map(m => `- ${m.key}`).join("\n") || "없음";
466
+
467
+ return `다음 설정 변경 사항을 분석하고 추가 인사이트를 제공해주세요.
468
+
469
+ 변경 요약: ${baseReport.summary}
470
+
471
+ MCP 서버 변경:
472
+ - 추가: ${mcpAdded}
473
+ - 삭제: ${mcpRemoved}
474
+ - 수정: ${mcpModified}
475
+
476
+ 프로젝트 설정 변경:
477
+ - 추가: ${configAdded}
478
+ - 삭제: ${configRemoved}
479
+ - 수정: ${configModified}
480
+
481
+ 다음을 분석해주세요:
482
+ 1. 이 변경이 시스템에 미칠 수 있는 영향
483
+ 2. 잠재적인 문제점
484
+ 3. 추가 제안 사항
485
+
486
+ 응답은 간결하게 해주세요.`;
487
+ }
488
+
489
+ function mergeWithLLMAnalysis(
490
+ baseReport: ConfigAnalysisReport,
491
+ llmResponse: string
492
+ ): ConfigAnalysisReport {
493
+ // LLM 응답을 요약에 추가
494
+ return {
495
+ ...baseReport,
496
+ summary: `${baseReport.summary}\n\n🤖 AI 분석: ${llmResponse.slice(0, 200)}...`,
497
+ };
498
+ }
@@ -38,3 +38,13 @@ export {
38
38
  formatDoctorReport,
39
39
  type ReportFormat,
40
40
  } from "./reporter";
41
+
42
+ export {
43
+ analyzeConfigMismatch,
44
+ analyzeConfigMismatchWithLLM,
45
+ formatConfigAnalysisReport,
46
+ type ConfigMismatchAnalysis,
47
+ type ConfigAnalysisReport,
48
+ type ConfigIssueCategory,
49
+ type ConfigIssueSeverity,
50
+ } from "./config-analyzer";
@@ -1,7 +1,15 @@
1
1
  import path from "path";
2
- import type { Snapshot, RestoreResult } from "./types";
2
+ import type { Snapshot, RestoreResult, ConfigSnapshot } from "./types";
3
3
  import type { RoutesManifest } from "../spec/schema";
4
4
  import type { SpecLock } from "../spec/lock";
5
+ import {
6
+ readLockfile,
7
+ writeLockfile,
8
+ generateLockfile,
9
+ LOCKFILE_PATH,
10
+ readMcpConfig,
11
+ } from "../lockfile";
12
+ import { validateAndReport } from "../config";
5
13
 
6
14
  const SPEC_DIR = "spec";
7
15
  const MANIFEST_FILE = "routes.manifest.json";
@@ -84,6 +92,29 @@ export async function createSnapshot(rootDir: string): Promise<Snapshot> {
84
92
  // Slot 내용 수집
85
93
  const slotContents = await collectSlotContents(rootDir);
86
94
 
95
+ // Config 스냅샷 생성 (lockfile 포함)
96
+ let configSnapshot: ConfigSnapshot | undefined;
97
+ try {
98
+ const config = await validateAndReport(rootDir);
99
+ if (config) {
100
+ const existingLockfile = await readLockfile(rootDir);
101
+ let mcpConfig: Record<string, unknown> | null = null;
102
+ try {
103
+ mcpConfig = await readMcpConfig(rootDir);
104
+ } catch {
105
+ // ignore MCP config errors in snapshot
106
+ }
107
+ const lockfile =
108
+ existingLockfile ?? generateLockfile(config, { includeSnapshot: true }, mcpConfig);
109
+ configSnapshot = {
110
+ lockfile,
111
+ configHash: lockfile.configHash,
112
+ };
113
+ }
114
+ } catch {
115
+ // Config 로드 실패 시 스킵
116
+ }
117
+
87
118
  const id = generateSnapshotId();
88
119
 
89
120
  return {
@@ -92,6 +123,7 @@ export async function createSnapshot(rootDir: string): Promise<Snapshot> {
92
123
  manifest,
93
124
  lock,
94
125
  slotContents,
126
+ configSnapshot,
95
127
  };
96
128
  }
97
129
 
@@ -183,6 +215,19 @@ export async function restoreSnapshot(rootDir: string, snapshot: Snapshot): Prom
183
215
  }
184
216
  }
185
217
 
218
+ // 4. Config Lockfile 복원 (있는 경우)
219
+ if (snapshot.configSnapshot) {
220
+ try {
221
+ await writeLockfile(rootDir, snapshot.configSnapshot.lockfile);
222
+ restoredFiles.push(LOCKFILE_PATH);
223
+ } catch (error) {
224
+ failedFiles.push(LOCKFILE_PATH);
225
+ errors.push(
226
+ `Failed to restore config lockfile: ${error instanceof Error ? error.message : String(error)}`
227
+ );
228
+ }
229
+ }
230
+
186
231
  return {
187
232
  success: failedFiles.length === 0,
188
233
  restoredFiles,
@@ -1,5 +1,6 @@
1
1
  import type { RoutesManifest } from "../spec/schema";
2
2
  import type { SpecLock } from "../spec/lock";
3
+ import type { ManduLockfile } from "../lockfile";
3
4
 
4
5
  /**
5
6
  * 변경 기록 - 하나의 트랜잭션을 나타냄
@@ -33,6 +34,18 @@ export interface Snapshot {
33
34
  lock: SpecLock | null;
34
35
  /** Slot 파일 내용만 저장 (Generated 파일은 재생성 가능) */
35
36
  slotContents: Record<string, string>;
37
+ /** 설정 스냅샷 (Lockfile 포함) - ont-run 통합 */
38
+ configSnapshot?: ConfigSnapshot;
39
+ }
40
+
41
+ /**
42
+ * 설정 스냅샷 - mandu.config의 상태를 저장
43
+ */
44
+ export interface ConfigSnapshot {
45
+ /** Lockfile 데이터 */
46
+ lockfile: ManduLockfile;
47
+ /** 설정 해시 */
48
+ configHash: string;
36
49
  }
37
50
 
38
51
  /**
@@ -1,2 +1,8 @@
1
- export * from "./mandu";
2
- export * from "./validate";
1
+ export * from "./mandu";
2
+ export * from "./validate";
3
+
4
+ // Symbol 메타데이터 (Phase 3)
5
+ export * from "./symbols.js";
6
+ export * from "./metadata.js";
7
+ export * from "./mcp-ref.js";
8
+ export * from "./mcp-status.js";