@mandujs/core 0.53.0 → 0.53.2

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 (112) hide show
  1. package/package.json +38 -2
  2. package/src/a11y/run-audit.ts +6 -6
  3. package/src/brain/adapters/oauth-flow.ts +1 -1
  4. package/src/brain/architecture/analyzer.ts +0 -2
  5. package/src/brain/credentials.ts +2 -2
  6. package/src/bundler/__tests__/build-runner.ts +13 -1
  7. package/src/bundler/__tests__/extended-watch.test.ts +3 -2
  8. package/src/bundler/__tests__/fast-refresh.test.ts +10 -12
  9. package/src/bundler/build.test.ts +30 -33
  10. package/src/bundler/build.ts +56 -27
  11. package/src/bundler/dev.ts +44 -39
  12. package/src/bundler/prerender.ts +63 -0
  13. package/src/bundler/safe-build.test.ts +2 -1
  14. package/src/bundler/types.ts +13 -6
  15. package/src/change/history.ts +1 -1
  16. package/src/change/snapshot.ts +18 -9
  17. package/src/client/Link.tsx +9 -9
  18. package/src/client/globals.ts +3 -2
  19. package/src/client/hooks.ts +0 -4
  20. package/src/client/hydrate.ts +1 -1
  21. package/src/client/router.ts +12 -12
  22. package/src/client/rpc.ts +1 -1
  23. package/src/client/use-fetch.ts +6 -6
  24. package/src/client/use-sse.ts +5 -5
  25. package/src/config/mcp-status.ts +0 -1
  26. package/src/config/validate.ts +4 -4
  27. package/src/config/watcher.ts +1 -1
  28. package/src/content/collection.ts +8 -12
  29. package/src/content/content-layer.ts +5 -5
  30. package/src/content/data-store.ts +2 -2
  31. package/src/content/loader-context.ts +0 -1
  32. package/src/content/loaders/file.ts +1 -1
  33. package/src/content/loaders/glob.ts +4 -4
  34. package/src/content/loaders/types.ts +1 -1
  35. package/src/content/meta-store.ts +1 -1
  36. package/src/content/types.ts +1 -1
  37. package/src/contract/define.ts +11 -12
  38. package/src/contract/index.ts +3 -3
  39. package/src/contract/protection.ts +2 -2
  40. package/src/contract/types.ts +1 -1
  41. package/src/contract/validator.ts +0 -1
  42. package/src/db/__tests__/db.test.ts +4 -7
  43. package/src/db/index.ts +138 -51
  44. package/src/db/migrations/index.ts +3 -0
  45. package/src/db/migrations/lock.ts +78 -23
  46. package/src/db/migrations/runner.ts +118 -101
  47. package/src/deploy/cache.ts +1 -0
  48. package/src/design/extract.ts +2 -1
  49. package/src/design/patch.ts +0 -1
  50. package/src/devtools/ai/mcp-connector.ts +24 -24
  51. package/src/devtools/client/components/kitchen-root.tsx +1 -1
  52. package/src/devtools/client/components/panel/errors-panel.tsx +1 -1
  53. package/src/devtools/client/state-manager.ts +1 -1
  54. package/src/devtools/init.ts +2 -3
  55. package/src/devtools/server/source-context.ts +1 -1
  56. package/src/devtools/worker/redaction-worker.ts +8 -4
  57. package/src/devtools/worker/worker-manager.ts +4 -3
  58. package/src/error/classifier.ts +1 -1
  59. package/src/error/domains.ts +1 -1
  60. package/src/filling/sse.ts +5 -5
  61. package/src/generator/generate.ts +2 -2
  62. package/src/guard/analyzer.ts +1 -1
  63. package/src/guard/ast-analyzer.ts +7 -7
  64. package/src/guard/auto-correct.ts +2 -2
  65. package/src/guard/config-guard.ts +0 -2
  66. package/src/guard/contract-guard.ts +1 -1
  67. package/src/guard/decision-memory.ts +2 -2
  68. package/src/guard/design-inline-class.ts +2 -1
  69. package/src/guard/graph.ts +1 -1
  70. package/src/guard/healing.ts +36 -41
  71. package/src/guard/negotiation.ts +3 -3
  72. package/src/guard/reporter.ts +1 -4
  73. package/src/guard/semantic-slots.ts +2 -2
  74. package/src/guard/suggestions.ts +2 -2
  75. package/src/guard/watcher.ts +5 -4
  76. package/src/index.ts +5 -5
  77. package/src/intent/index.ts +1 -1
  78. package/src/kitchen/api/agent-devtools-api.ts +544 -0
  79. package/src/kitchen/api/contract-api.ts +1 -1
  80. package/src/kitchen/api/file-api.ts +3 -3
  81. package/src/kitchen/kitchen-handler.ts +63 -38
  82. package/src/kitchen/kitchen-ui.ts +346 -62
  83. package/src/lockfile/validate.ts +0 -1
  84. package/src/openapi/generator.ts +2 -1
  85. package/src/openapi/openapi.test.ts +43 -14
  86. package/src/perf/user-marks.ts +1 -1
  87. package/src/plugins/registry.ts +1 -2
  88. package/src/plugins/types.ts +1 -1
  89. package/src/resource/__tests__/generator.test.ts +32 -15
  90. package/src/resource/ddl/__tests__/emit.test.ts +24 -0
  91. package/src/resource/ddl/diff.ts +0 -1
  92. package/src/resource/ddl/emit.ts +12 -1
  93. package/src/resource/generator-repo.ts +40 -20
  94. package/src/resource/generator.ts +1 -1
  95. package/src/resource/generators/contract.ts +1 -1
  96. package/src/resource/schema.ts +2 -1
  97. package/src/router/fs-routes.ts +6 -6
  98. package/src/router/fs-scanner.ts +1 -1
  99. package/src/runtime/env.ts +1 -1
  100. package/src/runtime/image-handler.ts +2 -1
  101. package/src/runtime/middleware.ts +2 -1
  102. package/src/runtime/server.ts +67 -60
  103. package/src/runtime/ssr.ts +3 -3
  104. package/src/runtime/streaming-ssr.ts +25 -27
  105. package/src/scheduler/validate.ts +1 -1
  106. package/src/seo/integration/ssr.ts +2 -3
  107. package/src/seo/render/opengraph.ts +1 -1
  108. package/src/seo/resolve/index.ts +0 -4
  109. package/src/slot/corrector.ts +1 -1
  110. package/src/slot/validator.ts +4 -4
  111. package/src/watcher/reporter.ts +1 -1
  112. package/src/watcher/watcher.ts +19 -19
@@ -69,7 +69,7 @@ import type {
69
69
  PendingMigration,
70
70
  SqlProvider,
71
71
  } from "../../resource/ddl/types";
72
- import type { Db } from "../index";
72
+ import { withPinnedDbHandle, type Db } from "../index";
73
73
  import {
74
74
  DEFAULT_HISTORY_TABLE,
75
75
  SAFE_HISTORY_TABLE_RE,
@@ -128,6 +128,24 @@ export class MigrationTimeoutError extends Error {
128
128
  }
129
129
  }
130
130
 
131
+ function assertNoTamperedHistory(
132
+ history: HistoryRow[],
133
+ diskByVersion: Map<string, PendingMigration>,
134
+ ): void {
135
+ for (const row of history) {
136
+ if (row.success !== 1) continue;
137
+ const disk = diskByVersion.get(row.version);
138
+ if (!disk) continue; // orphan on the history side — surfaced via status(), not apply()
139
+ if (disk.checksum !== row.checksum) {
140
+ throw new MigrationTamperedError(
141
+ disk.filename,
142
+ row.checksum,
143
+ disk.checksum,
144
+ );
145
+ }
146
+ }
147
+ }
148
+
131
149
  // ─── Public API ─────────────────────────────────────────────────────────────
132
150
 
133
151
  /** Options for {@link createMigrationRunner}. */
@@ -246,34 +264,18 @@ export function createMigrationRunner(
246
264
  async function apply(
247
265
  opts: { dryRun?: boolean } = {},
248
266
  ): Promise<AppliedMigration[]> {
249
- await ensureReady();
250
-
251
- // Tamper check BEFORE acquiring the lock so the fast-fail path
252
- // doesn't hold the advisory lock longer than necessary.
253
- const history = await readAllHistory(db, historyTable);
254
267
  const diskFiles = await readMigrationsFromDisk(migrationsDir);
255
268
  const diskByVersion = new Map(diskFiles.map((f) => [f.version, f]));
256
- for (const row of history) {
257
- if (row.success !== 1) continue;
258
- const disk = diskByVersion.get(row.version);
259
- if (!disk) continue; // orphan on the history side — surfaced via status(), not apply()
260
- if (disk.checksum !== row.checksum) {
261
- throw new MigrationTamperedError(
262
- disk.filename,
263
- row.checksum,
264
- disk.checksum,
265
- );
266
- }
267
- }
268
269
 
269
- const appliedVersions = new Set(
270
- history.filter((h) => h.success === 1).map((h) => h.version),
271
- );
272
- const pending = diskFiles.filter((f) => !appliedVersions.has(f.version));
273
- if (pending.length === 0) return [];
274
-
275
- // Dry-run: report what we WOULD apply, no IO, no history.
276
270
  if (opts.dryRun === true) {
271
+ await ensureReady();
272
+ const history = await readAllHistory(db, historyTable);
273
+ assertNoTamperedHistory(history, diskByVersion);
274
+
275
+ const appliedVersions = new Set(
276
+ history.filter((h) => h.success === 1).map((h) => h.version),
277
+ );
278
+ const pending = diskFiles.filter((f) => !appliedVersions.has(f.version));
277
279
  return pending.map<AppliedMigration>((p) => ({
278
280
  version: p.version,
279
281
  filename: p.filename,
@@ -290,100 +292,115 @@ export function createMigrationRunner(
290
292
 
291
293
  const applied: AppliedMigration[] = [];
292
294
 
293
- heldLock = await acquireMigrationLock(db, lockStrategy);
294
- try {
295
- for (const migration of pending) {
296
- const start = Date.now();
297
-
298
- const statements = splitStatements(migration.sql);
299
- if (statements.length === 0) {
300
- // Empty migration still record a history row so we don't
301
- // re-run it. execution_ms = 0 reflects reality.
295
+ const runLockedApply = async (): Promise<AppliedMigration[]> => {
296
+ heldLock = await acquireMigrationLock(db, lockStrategy);
297
+ try {
298
+ await ensureReady();
299
+ const lockedHistory = await readAllHistory(db, historyTable);
300
+ assertNoTamperedHistory(lockedHistory, diskByVersion);
301
+ const lockedAppliedVersions = new Set(
302
+ lockedHistory.filter((h) => h.success === 1).map((h) => h.version),
303
+ );
304
+ const lockedPending = diskFiles.filter((f) => !lockedAppliedVersions.has(f.version));
305
+
306
+ for (const migration of lockedPending) {
307
+ const start = Date.now();
308
+
309
+ const statements = splitStatements(migration.sql);
310
+ if (statements.length === 0) {
311
+ // Empty migration — still record a history row so we don't
312
+ // re-run it. execution_ms = 0 reflects reality.
313
+ await insertHistory(db, historyTable, {
314
+ version: migration.version,
315
+ filename: migration.filename,
316
+ checksum: migration.checksum,
317
+ applied_at: new Date(),
318
+ execution_ms: 0,
319
+ success: 1,
320
+ installed_by: installedBy,
321
+ });
322
+ applied.push({
323
+ version: migration.version,
324
+ filename: migration.filename,
325
+ checksum: migration.checksum,
326
+ appliedAt: new Date(),
327
+ executionMs: 0,
328
+ success: true,
329
+ });
330
+ continue;
331
+ }
332
+
333
+ try {
334
+ await db.transaction(async (tx) => {
335
+ for (const stmt of statements) {
336
+ await execRaw(tx, stmt);
337
+ const elapsed = Date.now() - start;
338
+ if (elapsed > applyTimeoutMs) {
339
+ throw new MigrationTimeoutError(
340
+ migration.filename,
341
+ elapsed,
342
+ applyTimeoutMs,
343
+ );
344
+ }
345
+ }
346
+ });
347
+ } catch (err) {
348
+ if (err instanceof MigrationTimeoutError) throw err;
349
+ // Wrap with migration context so downstream callers know
350
+ // which file blew up. Preserve the original stack where
351
+ // possible via `cause`.
352
+ const msg = err instanceof Error ? err.message : String(err);
353
+ const wrapped = new Error(
354
+ `[@mandujs/core/db/migrations] Failed to apply ${migration.filename}: ${msg}`,
355
+ );
356
+ // Preserve the original as a `cause` chain for diagnostics.
357
+ (wrapped as { cause?: unknown }).cause = err;
358
+ throw wrapped;
359
+ }
360
+
361
+ const executionMs = Date.now() - start;
362
+ const appliedAt = new Date();
363
+
364
+ // History row is written AFTER the SQL transaction commits.
365
+ // If this INSERT itself fails, the migration has run but we
366
+ // have no record — the user will see it as pending again.
367
+ // Mitigation: the insert is a single tiny statement; in
368
+ // practice it either succeeds or the whole connection is
369
+ // dead (in which case subsequent apply() calls will also fail
370
+ // and the user will debug from the DB side).
302
371
  await insertHistory(db, historyTable, {
303
372
  version: migration.version,
304
373
  filename: migration.filename,
305
374
  checksum: migration.checksum,
306
- applied_at: new Date(),
307
- execution_ms: 0,
375
+ applied_at: appliedAt,
376
+ execution_ms: executionMs,
308
377
  success: 1,
309
378
  installed_by: installedBy,
310
379
  });
380
+
311
381
  applied.push({
312
382
  version: migration.version,
313
383
  filename: migration.filename,
314
384
  checksum: migration.checksum,
315
- appliedAt: new Date(),
316
- executionMs: 0,
385
+ appliedAt,
386
+ executionMs,
317
387
  success: true,
318
388
  });
319
- continue;
320
389
  }
321
-
322
- try {
323
- await db.transaction(async (tx) => {
324
- for (const stmt of statements) {
325
- await execRaw(tx, stmt);
326
- const elapsed = Date.now() - start;
327
- if (elapsed > applyTimeoutMs) {
328
- throw new MigrationTimeoutError(
329
- migration.filename,
330
- elapsed,
331
- applyTimeoutMs,
332
- );
333
- }
334
- }
335
- });
336
- } catch (err) {
337
- if (err instanceof MigrationTimeoutError) throw err;
338
- // Wrap with migration context so downstream callers know
339
- // which file blew up. Preserve the original stack where
340
- // possible via `cause`.
341
- const msg = err instanceof Error ? err.message : String(err);
342
- const wrapped = new Error(
343
- `[@mandujs/core/db/migrations] Failed to apply ${migration.filename}: ${msg}`,
344
- );
345
- // Preserve the original as a `cause` chain for diagnostics.
346
- (wrapped as { cause?: unknown }).cause = err;
347
- throw wrapped;
390
+ } finally {
391
+ if (heldLock) {
392
+ await heldLock.release();
393
+ heldLock = null;
348
394
  }
395
+ }
349
396
 
350
- const executionMs = Date.now() - start;
351
- const appliedAt = new Date();
352
-
353
- // History row is written AFTER the SQL transaction commits.
354
- // If this INSERT itself fails, the migration has run but we
355
- // have no record — the user will see it as pending again.
356
- // Mitigation: the insert is a single tiny statement; in
357
- // practice it either succeeds or the whole connection is
358
- // dead (in which case subsequent apply() calls will also fail
359
- // and the user will debug from the DB side).
360
- await insertHistory(db, historyTable, {
361
- version: migration.version,
362
- filename: migration.filename,
363
- checksum: migration.checksum,
364
- applied_at: appliedAt,
365
- execution_ms: executionMs,
366
- success: 1,
367
- installed_by: installedBy,
368
- });
397
+ return applied;
398
+ };
369
399
 
370
- applied.push({
371
- version: migration.version,
372
- filename: migration.filename,
373
- checksum: migration.checksum,
374
- appliedAt,
375
- executionMs,
376
- success: true,
377
- });
378
- }
379
- } finally {
380
- if (heldLock) {
381
- await heldLock.release();
382
- heldLock = null;
383
- }
400
+ if (lockStrategy === "mysql_get_lock") {
401
+ return await withPinnedDbHandle(db, runLockedApply);
384
402
  }
385
-
386
- return applied;
403
+ return await runLockedApply();
387
404
  }
388
405
 
389
406
  async function status(): Promise<MigrationStatus> {
@@ -107,6 +107,7 @@ export async function loadDeployIntentCache(
107
107
  } catch (err) {
108
108
  throw new Error(
109
109
  `Deploy intent cache is not valid JSON (${file}): ${(err as Error).message}`,
110
+ { cause: err },
110
111
  );
111
112
  }
112
113
  return DeployIntentCache.parse(parsed);
@@ -1,3 +1,4 @@
1
+ import type * as __ManduNodeFsTypes0 from "node:fs";
1
2
  /**
2
3
  * DESIGN.md token extractor — scan project source for tokens that
3
4
  * should be promoted into DESIGN.md (Issue #245 M4 §3.5 internal loop).
@@ -155,7 +156,7 @@ const SOURCE_RX = /\.(?:tsx?|jsx?|mdx?|css)$/;
155
156
  async function collectFiles(root: string): Promise<string[]> {
156
157
  const out: string[] = [];
157
158
  async function walk(dir: string): Promise<void> {
158
- let entries: import("node:fs").Dirent[];
159
+ let entries: __ManduNodeFsTypes0.Dirent[];
159
160
  try {
160
161
  entries = await fs.readdir(dir, { withFileTypes: true });
161
162
  } catch {
@@ -23,7 +23,6 @@
23
23
  * limitation or fall back to a hand-edit.
24
24
  */
25
25
 
26
- import { parseDesignMd } from "./parser";
27
26
 
28
27
  export type PatchableSection =
29
28
  | "color-palette"
@@ -165,26 +165,26 @@ export class MCPConnector {
165
165
  try {
166
166
  this.ws = new WebSocket(url);
167
167
 
168
- this.ws.onopen = () => {
169
- clearTimeout(timeout);
170
- this.setStatus('connected');
171
- this.reconnectAttempts = 0;
172
- resolve();
173
- };
174
-
175
- this.ws.onclose = () => {
176
- this.handleDisconnect();
177
- };
178
-
179
- this.ws.onerror = (event) => {
180
- clearTimeout(timeout);
181
- this.handleConnectionError(new Error('WebSocket error'));
182
- reject(new Error('WebSocket connection failed'));
183
- };
184
-
185
- this.ws.onmessage = (event) => {
186
- this.handleMessage(event.data);
187
- };
168
+ this.ws.addEventListener('open', () => {
169
+ clearTimeout(timeout);
170
+ this.setStatus('connected');
171
+ this.reconnectAttempts = 0;
172
+ resolve();
173
+ });
174
+
175
+ this.ws.addEventListener('close', () => {
176
+ this.handleDisconnect();
177
+ });
178
+
179
+ this.ws.addEventListener('error', () => {
180
+ clearTimeout(timeout);
181
+ this.handleConnectionError(new Error('WebSocket error'));
182
+ reject(new Error('WebSocket connection failed'));
183
+ });
184
+
185
+ this.ws.addEventListener('message', (event) => {
186
+ this.handleMessage(event.data);
187
+ });
188
188
  } catch (error) {
189
189
  clearTimeout(timeout);
190
190
  this.handleConnectionError(
@@ -210,10 +210,10 @@ export class MCPConnector {
210
210
  }
211
211
 
212
212
  // 대기 중인 요청 모두 reject
213
- for (const [id, pending] of this.pendingRequests) {
214
- clearTimeout(pending.timeout);
215
- pending.reject(new Error('Connection closed'));
216
- }
213
+ for (const [, pending] of this.pendingRequests) {
214
+ clearTimeout(pending.timeout);
215
+ pending.reject(new Error('Connection closed'));
216
+ }
217
217
  this.pendingRequests.clear();
218
218
 
219
219
  this.setStatus('disconnected');
@@ -7,7 +7,7 @@
7
7
 
8
8
  import React, { useEffect, useState, useCallback, useMemo } from 'react';
9
9
  import { createRoot, type Root } from 'react-dom/client';
10
- import type { NormalizedError, DevToolsConfig, IslandSnapshot, NetworkRequest, DevToolsGuardViolation } from '../../types';
10
+ import type { NormalizedError, DevToolsConfig } from '../../types';
11
11
  import { generateCSSVariables, testIds, zIndex } from '../../design-tokens';
12
12
  import { getStateManager, type KitchenState } from '../state-manager';
13
13
  import { getOrCreateHook } from '../../hook';
@@ -159,7 +159,7 @@ export interface ErrorsPanelProps {
159
159
  export function ErrorsPanel({
160
160
  errors,
161
161
  onErrorClick,
162
- onErrorIgnore,
162
+ onErrorIgnore: _onErrorIgnore,
163
163
  onClearAll,
164
164
  }: ErrorsPanelProps): React.ReactElement {
165
165
  const formatTime = useCallback((timestamp: number): string => {
@@ -257,7 +257,7 @@ export class KitchenStateManager {
257
257
  }
258
258
  }
259
259
 
260
- setIslandHydrated(id: string, time: number): void {
260
+ setIslandHydrated(id: string, _time: number): void {
261
261
  this.updateIsland(id, {
262
262
  status: 'hydrated',
263
263
  hydrateEndTime: Date.now(),
@@ -17,10 +17,9 @@ import {
17
17
  initializePersistence,
18
18
  destroyPersistence,
19
19
  getStateManager,
20
- getPersistenceManager,
21
20
  } from './client';
22
21
  import { initializeWorkerManager, destroyWorkerManager } from './worker';
23
- import { getContextBuilder, resetContextBuilder, getMCPConnector, destroyMCPConnector } from './ai';
22
+ import { getContextBuilder, resetContextBuilder, destroyMCPConnector } from './ai';
24
23
 
25
24
  // ============================================================================
26
25
  // Types
@@ -134,7 +133,7 @@ export function initManduKitchen(config: DevToolsConfig = {}): KitchenInstance {
134
133
  });
135
134
 
136
135
  // 6. v1.1: AI Context Builder 초기화
137
- const contextBuilder = getContextBuilder({ config });
136
+ const _contextBuilder = getContextBuilder({ config });
138
137
 
139
138
  // 7. UI 마운트
140
139
  mountKitchen(config);
@@ -424,7 +424,7 @@ export function createViteMiddleware(projectRoot: string) {
424
424
  res.setHeader('Cache-Control', 'no-store');
425
425
  res.statusCode = result.success ? 200 : 400;
426
426
  res.end(JSON.stringify(result));
427
- } catch (error) {
427
+ } catch {
428
428
  res.statusCode = 500;
429
429
  res.end(JSON.stringify({
430
430
  success: false,
@@ -6,7 +6,7 @@
6
6
  * Prevents main thread blocking during large payload processing
7
7
  */
8
8
 
9
- import type { RedactPattern, WorkerTask } from '../types';
9
+ import type { RedactPattern } from '../types';
10
10
 
11
11
  // ============================================================================
12
12
  // Worker Message Types
@@ -206,14 +206,18 @@ function handleMessage(request: WorkerRequest): WorkerResponse {
206
206
  // Worker 컨텍스트에서만 실행
207
207
  interface WorkerSelf {
208
208
  postMessage: (message: WorkerResponse) => void;
209
- onmessage: ((event: MessageEvent<WorkerRequest>) => void) | null;
209
+ addEventListener: (
210
+ type: 'message',
211
+ listener: (event: MessageEvent<WorkerRequest>) => void
212
+ ) => void;
210
213
  }
211
214
  const workerSelf = typeof self !== 'undefined' ? (self as unknown as WorkerSelf) : undefined;
212
215
  if (workerSelf && typeof workerSelf.postMessage === 'function') {
213
- workerSelf.onmessage = (event: MessageEvent<WorkerRequest>) => {
216
+ workerSelf.addEventListener('message', (event: MessageEvent<WorkerRequest>) => {
214
217
  const response = handleMessage(event.data);
218
+ // oxlint-disable-next-line require-post-message-target-origin -- Worker.postMessage has no targetOrigin parameter.
215
219
  workerSelf.postMessage(response);
216
- };
220
+ });
217
221
  }
218
222
 
219
223
  // ============================================================================
@@ -88,8 +88,8 @@ export class WorkerManager {
88
88
  }
89
89
 
90
90
  // 메시지 핸들러 설정
91
- this.worker.onmessage = this.handleWorkerMessage.bind(this);
92
- this.worker.onerror = this.handleWorkerError.bind(this);
91
+ this.worker.addEventListener('message', this.handleWorkerMessage.bind(this));
92
+ this.worker.addEventListener('error', this.handleWorkerError.bind(this));
93
93
 
94
94
  // Ping 테스트
95
95
  const pingResult = await this.sendRequest({ type: 'ping', data: {} });
@@ -117,7 +117,7 @@ export class WorkerManager {
117
117
  }
118
118
 
119
119
  // 대기 중인 요청 모두 reject
120
- for (const [id, pending] of this.pendingRequests) {
120
+ for (const [, pending] of this.pendingRequests) {
121
121
  clearTimeout(pending.timeout);
122
122
  pending.reject(new Error('Worker terminated'));
123
123
  }
@@ -323,6 +323,7 @@ export class WorkerManager {
323
323
 
324
324
  this.pendingRequests.set(id, { resolve, reject, timeout });
325
325
 
326
+ // oxlint-disable-next-line require-post-message-target-origin -- Worker.postMessage has no targetOrigin parameter.
326
327
  this.worker!.postMessage({ id, ...request });
327
328
  });
328
329
  }
@@ -54,7 +54,7 @@ export class ErrorClassifier {
54
54
  /**
55
55
  * ValidationError 처리
56
56
  */
57
- private createValidationError(error: { errors: unknown[] }): ManduError {
57
+ private createValidationError(_error: { errors: unknown[] }): ManduError {
58
58
  const slotFile = this.findSlotFile();
59
59
 
60
60
  return {
@@ -5,7 +5,7 @@
5
5
  * try-catch에서 instanceof 체크 가능.
6
6
  */
7
7
 
8
- import { ErrorCode, ERROR_MESSAGES, ERROR_SUMMARIES } from "./types";
8
+ import { ErrorCode, ERROR_SUMMARIES } from "./types";
9
9
  import type { ManduError, RouteContext, ErrorType } from "./types";
10
10
 
11
11
  /**
@@ -45,11 +45,11 @@ export class SSEConnection {
45
45
  controller.enqueue(this.encoder.encode(chunk));
46
46
  }
47
47
  this.pending = [];
48
- },
49
- cancel: () => {
50
- this.close();
51
- },
52
- });
48
+ },
49
+ cancel: () => {
50
+ void this.close();
51
+ },
52
+ });
53
53
 
54
54
  const headers = new Headers(DEFAULT_SSE_HEADERS);
55
55
  if (options.headers) {
@@ -1,4 +1,4 @@
1
- import type { RoutesManifest, RouteSpec } from "../spec/schema";
1
+ import type { RoutesManifest } from "../spec/schema";
2
2
  import { generateApiHandler, generatePageComponent, generateSlotLogic } from "./templates";
3
3
  import { generateContractTypeGlue, generateContractTemplate, generateContractTypesIndex } from "./contract-glue";
4
4
  import { createHash } from "crypto";
@@ -102,7 +102,7 @@ export interface GeneratedMap {
102
102
  async function ensureDir(dirPath: string): Promise<void> {
103
103
  try {
104
104
  await fs.mkdir(dirPath, { recursive: true });
105
- } catch (error) {
105
+ } catch {
106
106
  // ignore if exists
107
107
  }
108
108
  }
@@ -63,7 +63,7 @@ const DEFAULT_IMPORT_PATTERN = /^import\s+(\w+)/;
63
63
  */
64
64
  export function extractImports(content: string): ImportInfo[] {
65
65
  const imports: ImportInfo[] = [];
66
- const lines = content.split("\n");
66
+ const _lines = content.split("\n");
67
67
 
68
68
  // Static imports
69
69
  let match: RegExpExecArray | null;
@@ -382,14 +382,14 @@ export function extractImportsAST(content: string): ImportInfo[] {
382
382
 
383
383
  let namedImports: string[] | undefined;
384
384
  let defaultImport: string | undefined;
385
- let namespaceImport: string | undefined;
386
- let isTypeOnly = false;
385
+ let _namespaceImport: string | undefined;
386
+ let _isTypeOnly = false;
387
387
 
388
388
  // Check for type-only import
389
389
  if (is("keyword", "type")) {
390
390
  consume("keyword", "type");
391
391
  skip();
392
- isTypeOnly = true;
392
+ _isTypeOnly = true;
393
393
  }
394
394
 
395
395
  // Side-effect import: import '...'
@@ -427,7 +427,7 @@ export function extractImportsAST(content: string): ImportInfo[] {
427
427
  consume("keyword", "as");
428
428
  skip();
429
429
  if (is("identifier")) {
430
- namespaceImport = consume("identifier")!.value;
430
+ _namespaceImport = consume("identifier")!.value;
431
431
  skip();
432
432
  }
433
433
  }
@@ -777,7 +777,7 @@ export interface ModuleAnalysis {
777
777
  /** Public API 여부 (index 파일) */
778
778
  isPublicAPI: boolean;
779
779
  /** 순수 타입 모듈 여부 */
780
- isTypeOnly: boolean;
780
+ _isTypeOnly: boolean;
781
781
  }
782
782
 
783
783
  /**
@@ -795,12 +795,12 @@ export function analyzeModuleAST(content: string, filePath: string): ModuleAnaly
795
795
  // 타입만 있는 모듈인지 확인
796
796
  const hasRuntimeExport = exports.some((e) => e.type !== "type");
797
797
  const hasRuntimeImport = imports.some((i) => !i.statement.includes("import type"));
798
- const isTypeOnly = !hasRuntimeExport && !hasRuntimeImport && exports.length > 0;
798
+ const _isTypeOnly = !hasRuntimeExport && !hasRuntimeImport && exports.length > 0;
799
799
 
800
800
  return {
801
801
  imports,
802
802
  exports,
803
803
  isPublicAPI,
804
- isTypeOnly,
804
+ _isTypeOnly,
805
805
  };
806
806
  }
@@ -4,7 +4,7 @@ import { GUARD_RULES } from "./rules";
4
4
  import { runGuardCheck } from "./check";
5
5
  import { generateRoutes } from "../generator/generate";
6
6
  import { beginChange, commitChange, rollbackChange } from "../change";
7
- import path from "path";
7
+
8
8
 
9
9
  export interface AutoCorrectStep {
10
10
  ruleId: string;
@@ -51,7 +51,7 @@ export async function runAutoCorrect(
51
51
  message: `Auto-correct: ${violations.length} violations`,
52
52
  autoGenerated: true,
53
53
  });
54
- } catch (error) {
54
+ } catch {
55
55
  // 이미 활성 트랜잭션이 있는 경우 그대로 진행 (사용자가 수동으로 시작한 경우)
56
56
  // 다른 오류는 트랜잭션 없이 진행
57
57
  }
@@ -12,8 +12,6 @@ import {
12
12
  validateLockfile,
13
13
  validateWithPolicy,
14
14
  detectMode,
15
- type ManduLockfile,
16
- type LockfileValidationResult,
17
15
  type LockfileMode,
18
16
  } from "../lockfile";
19
17
  import type { ConfigDiff } from "../utils/differ";
@@ -79,7 +79,7 @@ export interface ContractViolation extends GuardViolation {
79
79
  */
80
80
  export async function checkMissingContract(
81
81
  manifest: RoutesManifest,
82
- rootDir: string
82
+ _rootDir: string
83
83
  ): Promise<ContractViolation[]> {
84
84
  const violations: ContractViolation[] = [];
85
85
 
@@ -25,8 +25,8 @@
25
25
  * ```
26
26
  */
27
27
 
28
- import { join, basename, extname } from "path";
29
- import { mkdir, readdir, readFile, writeFile, stat } from "fs/promises";
28
+ import { join, extname } from "path";
29
+ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
30
30
 
31
31
  // ═══════════════════════════════════════════════════════════════════════════
32
32
  // Types
@@ -1,3 +1,4 @@
1
+ import type * as __ManduNodeFsTypes0 from "node:fs";
1
2
  /**
2
3
  * Guard rule — `DESIGN_INLINE_CLASS` (Issue #245).
3
4
  *
@@ -92,7 +93,7 @@ async function pathExists(p: string): Promise<boolean> {
92
93
  }
93
94
 
94
95
  async function* walk(rootDir: string): AsyncIterable<string> {
95
- let entries: import("node:fs").Dirent[];
96
+ let entries: __ManduNodeFsTypes0.Dirent[];
96
97
  try {
97
98
  entries = await fs.readdir(rootDir, { withFileTypes: true });
98
99
  } catch {