@obov/mext 0.4.0 → 0.6.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 (164) hide show
  1. package/dist/approval.d.ts +33 -0
  2. package/dist/approval.js +36 -0
  3. package/dist/approval.js.map +1 -0
  4. package/dist/audit.d.ts +123 -0
  5. package/dist/audit.js +221 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/build-cli.d.ts +1 -0
  8. package/dist/build-cli.js +2 -1
  9. package/dist/build-cli.js.map +1 -1
  10. package/dist/build.d.ts +2 -0
  11. package/dist/build.js +30 -3
  12. package/dist/build.js.map +1 -1
  13. package/dist/caller.d.ts +53 -0
  14. package/dist/caller.js +51 -0
  15. package/dist/caller.js.map +1 -0
  16. package/dist/cli/build.js +6 -3
  17. package/dist/cli/build.js.map +1 -1
  18. package/dist/cli/dev-audit.d.ts +25 -0
  19. package/dist/cli/dev-audit.js +84 -0
  20. package/dist/cli/dev-audit.js.map +1 -0
  21. package/dist/cli/dev-lock.d.ts +3 -1
  22. package/dist/cli/dev-lock.js +12 -5
  23. package/dist/cli/dev-lock.js.map +1 -1
  24. package/dist/cli/dev-server.d.ts +9 -0
  25. package/dist/cli/dev-server.js +24 -1
  26. package/dist/cli/dev-server.js.map +1 -1
  27. package/dist/cli/dev-tap.d.ts +20 -0
  28. package/dist/cli/dev-tap.js +70 -0
  29. package/dist/cli/dev-tap.js.map +1 -0
  30. package/dist/cli/dev.js +16 -4
  31. package/dist/cli/dev.js.map +1 -1
  32. package/dist/cli/help.js +2 -0
  33. package/dist/cli/help.js.map +1 -1
  34. package/dist/cli/inspect.d.ts +7 -0
  35. package/dist/cli/inspect.js +699 -0
  36. package/dist/cli/inspect.js.map +1 -0
  37. package/dist/cli/main.js +8 -0
  38. package/dist/cli/main.js.map +1 -1
  39. package/dist/cli/output.d.ts +1 -1
  40. package/dist/cli/output.js +3 -2
  41. package/dist/cli/output.js.map +1 -1
  42. package/dist/cli/run.js +28 -4
  43. package/dist/cli/run.js.map +1 -1
  44. package/dist/cli/types.d.ts +9 -0
  45. package/dist/cli/types.js +77 -0
  46. package/dist/cli/types.js.map +1 -0
  47. package/dist/client/index.d.ts +10 -0
  48. package/dist/client/index.js +7 -0
  49. package/dist/client/index.js.map +1 -0
  50. package/dist/client/manifest.js +6 -2
  51. package/dist/client/manifest.js.map +1 -1
  52. package/dist/client/runner.js +37 -3
  53. package/dist/client/runner.js.map +1 -1
  54. package/dist/client/typed.d.ts +54 -0
  55. package/dist/client/typed.js +490 -0
  56. package/dist/client/typed.js.map +1 -0
  57. package/dist/client/types.d.ts +196 -0
  58. package/dist/client/types.js +2 -0
  59. package/dist/client/types.js.map +1 -0
  60. package/dist/command.d.ts +61 -5
  61. package/dist/command.js +42 -1
  62. package/dist/command.js.map +1 -1
  63. package/dist/config.d.ts +42 -10
  64. package/dist/config.js +32 -7
  65. package/dist/config.js.map +1 -1
  66. package/dist/errors.d.ts +21 -3
  67. package/dist/errors.js +13 -0
  68. package/dist/errors.js.map +1 -1
  69. package/dist/expose.d.ts +9 -0
  70. package/dist/expose.js +52 -0
  71. package/dist/expose.js.map +1 -0
  72. package/dist/index.d.ts +15 -6
  73. package/dist/index.js +7 -2
  74. package/dist/index.js.map +1 -1
  75. package/dist/inspect/agent.d.ts +104 -0
  76. package/dist/inspect/agent.js +125 -0
  77. package/dist/inspect/agent.js.map +1 -0
  78. package/dist/inspect/audit.d.ts +46 -0
  79. package/dist/inspect/audit.js +133 -0
  80. package/dist/inspect/audit.js.map +1 -0
  81. package/dist/inspect/compare.d.ts +12 -0
  82. package/dist/inspect/compare.js +31 -0
  83. package/dist/inspect/compare.js.map +1 -0
  84. package/dist/inspect/index.d.ts +17 -0
  85. package/dist/inspect/index.js +12 -0
  86. package/dist/inspect/index.js.map +1 -0
  87. package/dist/inspect/inspector.d.ts +168 -0
  88. package/dist/inspect/inspector.js +392 -0
  89. package/dist/inspect/inspector.js.map +1 -0
  90. package/dist/inspect/profiles.d.ts +95 -0
  91. package/dist/inspect/profiles.js +235 -0
  92. package/dist/inspect/profiles.js.map +1 -0
  93. package/dist/inspect/scenario.d.ts +126 -0
  94. package/dist/inspect/scenario.js +555 -0
  95. package/dist/inspect/scenario.js.map +1 -0
  96. package/dist/inspect/ui-server.d.ts +31 -0
  97. package/dist/inspect/ui-server.js +378 -0
  98. package/dist/inspect/ui-server.js.map +1 -0
  99. package/dist/inspect/ui.html +917 -0
  100. package/dist/instrumentation.d.ts +13 -2
  101. package/dist/instrumentation.js +11 -1
  102. package/dist/instrumentation.js.map +1 -1
  103. package/dist/invoke.d.ts +32 -5
  104. package/dist/invoke.js +150 -16
  105. package/dist/invoke.js.map +1 -1
  106. package/dist/manifest.d.ts +13 -2
  107. package/dist/manifest.js +5 -1
  108. package/dist/manifest.js.map +1 -1
  109. package/dist/mcp/constants.d.ts +5 -0
  110. package/dist/mcp/constants.js +5 -0
  111. package/dist/mcp/constants.js.map +1 -1
  112. package/dist/mcp/dispatch.d.ts +1 -1
  113. package/dist/mcp/dispatch.js +14 -3
  114. package/dist/mcp/dispatch.js.map +1 -1
  115. package/dist/mcp/help.d.ts +2 -0
  116. package/dist/mcp/help.js +6 -1
  117. package/dist/mcp/help.js.map +1 -1
  118. package/dist/mcp/http.d.ts +16 -2
  119. package/dist/mcp/http.js +76 -7
  120. package/dist/mcp/http.js.map +1 -1
  121. package/dist/mcp/index.d.ts +1 -1
  122. package/dist/mcp/index.js +1 -1
  123. package/dist/mcp/index.js.map +1 -1
  124. package/dist/mcp/install.d.ts +1 -1
  125. package/dist/mcp/install.js +3 -2
  126. package/dist/mcp/install.js.map +1 -1
  127. package/dist/mcp/server.d.ts +12 -1
  128. package/dist/mcp/server.js +63 -17
  129. package/dist/mcp/server.js.map +1 -1
  130. package/dist/mcp/stdio.d.ts +4 -0
  131. package/dist/mcp/stdio.js +28 -0
  132. package/dist/mcp/stdio.js.map +1 -1
  133. package/dist/mcp/tasks.d.ts +3 -1
  134. package/dist/mcp/tasks.js +6 -3
  135. package/dist/mcp/tasks.js.map +1 -1
  136. package/dist/principal.d.ts +22 -0
  137. package/dist/principal.js +29 -0
  138. package/dist/principal.js.map +1 -1
  139. package/dist/registry.d.ts +5 -0
  140. package/dist/registry.js +21 -0
  141. package/dist/registry.js.map +1 -1
  142. package/dist/remote/client.d.ts +2 -0
  143. package/dist/remote/client.js +3 -2
  144. package/dist/remote/client.js.map +1 -1
  145. package/dist/remote/oauth.d.ts +1 -1
  146. package/dist/remote/oauth.js +1 -1
  147. package/dist/remote/oauth.js.map +1 -1
  148. package/dist/runtime.d.ts +10 -0
  149. package/dist/runtime.js +11 -0
  150. package/dist/runtime.js.map +1 -0
  151. package/dist/start.js +6 -2
  152. package/dist/start.js.map +1 -1
  153. package/dist/suggest.js +1 -1
  154. package/dist/suggest.js.map +1 -1
  155. package/dist/typegen.d.ts +27 -0
  156. package/dist/typegen.js +62 -0
  157. package/dist/typegen.js.map +1 -0
  158. package/dist/version-constant.d.ts +5 -0
  159. package/dist/version-constant.js +6 -0
  160. package/dist/version-constant.js.map +1 -0
  161. package/dist/version.d.ts +1 -2
  162. package/dist/version.js +1 -2
  163. package/dist/version.js.map +1 -1
  164. package/package.json +14 -3
@@ -1,5 +1,7 @@
1
1
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ /** 잠금 파일 권한: 소유자만 (auditToken 이 들어 있다) */
4
+ const LOCK_MODE = 0o600;
3
5
  /** <cwd>/.mext/dev/lock */
4
6
  export function devLockPath(cwd) {
5
7
  return join(resolve(cwd), ".mext", "dev", "lock");
@@ -13,13 +15,13 @@ export function readDevLock(cwd) {
13
15
  * 잠금 획득 (O_EXCL 생성). 이미 살아 있는 다른 프로세스의 잠금 = { running }.
14
16
  * 죽은 pid · 깨진 파일 = 오래된 잠금으로 보고 교체
15
17
  */
16
- export function acquireDevLock(cwd, url = null) {
18
+ export function acquireDevLock(cwd, url = null, auditToken) {
17
19
  const path = devLockPath(cwd);
18
20
  mkdirSync(dirname(path), { recursive: true });
19
- const lock = { pid: process.pid, url, startedAt: new Date().toISOString() };
21
+ const lock = { pid: process.pid, url, startedAt: new Date().toISOString(), ...(auditToken !== undefined ? { auditToken } : {}) };
20
22
  for (let attempt = 0;; attempt++) {
21
23
  try {
22
- writeFileSync(path, `${JSON.stringify(lock)}\n`, { flag: "wx" });
24
+ writeFileSync(path, `${JSON.stringify(lock)}\n`, { flag: "wx", mode: LOCK_MODE });
23
25
  return { acquired: lock };
24
26
  }
25
27
  catch (error) {
@@ -36,7 +38,7 @@ export function acquireDevLock(cwd, url = null) {
36
38
  export function updateDevLock(cwd, lock) {
37
39
  const path = devLockPath(cwd);
38
40
  const tmp = `${path}.${process.pid}.tmp`;
39
- writeFileSync(tmp, `${JSON.stringify(lock)}\n`);
41
+ writeFileSync(tmp, `${JSON.stringify(lock)}\n`, { mode: LOCK_MODE });
40
42
  renameSync(tmp, path);
41
43
  }
42
44
  /** 이 프로세스의 잠금이면 삭제 (동기. process exit 처리기에서도 호출) */
@@ -50,7 +52,12 @@ function parseLock(path) {
50
52
  const value = JSON.parse(readFileSync(path, "utf8"));
51
53
  if (typeof value.pid !== "number")
52
54
  return undefined;
53
- return { pid: value.pid, url: typeof value.url === "string" ? value.url : null, startedAt: String(value.startedAt ?? "") };
55
+ return {
56
+ pid: value.pid,
57
+ url: typeof value.url === "string" ? value.url : null,
58
+ startedAt: String(value.startedAt ?? ""),
59
+ ...(typeof value.auditToken === "string" && value.auditToken !== "" ? { auditToken: value.auditToken } : {}),
60
+ };
54
61
  }
55
62
  catch {
56
63
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"dev-lock.js","sourceRoot":"","sources":["../../src/cli/dev-lock.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\n\n/** .mext/dev/lock 내용. url = listen 전에는 null */\nexport interface DevLock {\n pid: number\n url: string | null\n startedAt: string\n}\n\n/** <cwd>/.mext/dev/lock */\nexport function devLockPath(cwd: string): string {\n return join(resolve(cwd), \".mext\", \"dev\", \"lock\")\n}\n\n/** 살아 있는 mext dev 의 잠금. 없음 · 죽은 pid · 읽을 수 없음 = undefined */\nexport function readDevLock(cwd: string): DevLock | undefined {\n const lock = parseLock(devLockPath(cwd))\n return lock && pidAlive(lock.pid) ? lock : undefined\n}\n\n/**\n * 잠금 획득 (O_EXCL 생성). 이미 살아 있는 다른 프로세스의 잠금 = { running }.\n * 죽은 pid · 깨진 파일 = 오래된 잠금으로 보고 교체\n */\nexport function acquireDevLock(cwd: string, url: string | null = null): { acquired: DevLock } | { running: DevLock } {\n const path = devLockPath(cwd)\n mkdirSync(dirname(path), { recursive: true })\n const lock: DevLock = { pid: process.pid, url, startedAt: new Date().toISOString() }\n for (let attempt = 0; ; attempt++) {\n try {\n writeFileSync(path, `${JSON.stringify(lock)}\\n`, { flag: \"wx\" })\n return { acquired: lock }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\" || attempt >= 3) throw error\n }\n const existing = parseLock(path)\n if (existing && existing.pid !== process.pid && pidAlive(existing.pid)) return { running: existing }\n rmSync(path, { force: true })\n }\n}\n\n/** 획득한 잠금의 url 갱신 (임시 파일 rename) */\nexport function updateDevLock(cwd: string, lock: DevLock): void {\n const path = devLockPath(cwd)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, `${JSON.stringify(lock)}\\n`)\n renameSync(tmp, path)\n}\n\n/** 이 프로세스의 잠금이면 삭제 (동기. process exit 처리기에서도 호출) */\nexport function releaseDevLock(cwd: string): void {\n const path = devLockPath(cwd)\n if (parseLock(path)?.pid === process.pid) rmSync(path, { force: true })\n}\n\nfunction parseLock(path: string): DevLock | undefined {\n try {\n const value = JSON.parse(readFileSync(path, \"utf8\")) as Partial<DevLock>\n if (typeof value.pid !== \"number\") return undefined\n return { pid: value.pid, url: typeof value.url === \"string\" ? value.url : null, startedAt: String(value.startedAt ?? \"\") }\n } catch {\n return undefined\n }\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\"\n }\n}\n"]}
1
+ {"version":3,"file":"dev-lock.js","sourceRoot":"","sources":["../../src/cli/dev-lock.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\n\n/** .mext/dev/lock 내용. url = listen 전에는 null */\nexport interface DevLock {\n pid: number\n url: string | null\n startedAt: string\n /** 감사 tap endpoint 의 토큰 (spec §10). 이전 버전의 잠금 = 없음. 로그 · 오류 메시지에 출력하지 않는다 */\n auditToken?: string\n}\n\n/** 잠금 파일 권한: 소유자만 (auditToken 이 들어 있다) */\nconst LOCK_MODE = 0o600\n\n/** <cwd>/.mext/dev/lock */\nexport function devLockPath(cwd: string): string {\n return join(resolve(cwd), \".mext\", \"dev\", \"lock\")\n}\n\n/** 살아 있는 mext dev 의 잠금. 없음 · 죽은 pid · 읽을 수 없음 = undefined */\nexport function readDevLock(cwd: string): DevLock | undefined {\n const lock = parseLock(devLockPath(cwd))\n return lock && pidAlive(lock.pid) ? lock : undefined\n}\n\n/**\n * 잠금 획득 (O_EXCL 생성). 이미 살아 있는 다른 프로세스의 잠금 = { running }.\n * 죽은 pid · 깨진 파일 = 오래된 잠금으로 보고 교체\n */\nexport function acquireDevLock(cwd: string, url: string | null = null, auditToken?: string): { acquired: DevLock } | { running: DevLock } {\n const path = devLockPath(cwd)\n mkdirSync(dirname(path), { recursive: true })\n const lock: DevLock = { pid: process.pid, url, startedAt: new Date().toISOString(), ...(auditToken !== undefined ? { auditToken } : {}) }\n for (let attempt = 0; ; attempt++) {\n try {\n writeFileSync(path, `${JSON.stringify(lock)}\\n`, { flag: \"wx\", mode: LOCK_MODE })\n return { acquired: lock }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\" || attempt >= 3) throw error\n }\n const existing = parseLock(path)\n if (existing && existing.pid !== process.pid && pidAlive(existing.pid)) return { running: existing }\n rmSync(path, { force: true })\n }\n}\n\n/** 획득한 잠금의 url 갱신 (임시 파일 rename) */\nexport function updateDevLock(cwd: string, lock: DevLock): void {\n const path = devLockPath(cwd)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, `${JSON.stringify(lock)}\\n`, { mode: LOCK_MODE })\n renameSync(tmp, path)\n}\n\n/** 이 프로세스의 잠금이면 삭제 (동기. process exit 처리기에서도 호출) */\nexport function releaseDevLock(cwd: string): void {\n const path = devLockPath(cwd)\n if (parseLock(path)?.pid === process.pid) rmSync(path, { force: true })\n}\n\nfunction parseLock(path: string): DevLock | undefined {\n try {\n const value = JSON.parse(readFileSync(path, \"utf8\")) as Partial<DevLock>\n if (typeof value.pid !== \"number\") return undefined\n return {\n pid: value.pid,\n url: typeof value.url === \"string\" ? value.url : null,\n startedAt: String(value.startedAt ?? \"\"),\n ...(typeof value.auditToken === \"string\" && value.auditToken !== \"\" ? { auditToken: value.auditToken } : {}),\n }\n } catch {\n return undefined\n }\n}\n\nfunction pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\"\n }\n}\n"]}
@@ -1,5 +1,14 @@
1
+ import type { AuditEvent } from "../audit.ts";
1
2
  /** 자식 → 부모 */
2
3
  export type DevChildMessage = {
4
+ type: "error";
5
+ message: string;
6
+ }
7
+ /** 감사 tap (spec §10): 호출 1건의 감사 이벤트 */
8
+ | {
9
+ type: "audit";
10
+ event: AuditEvent;
11
+ } | {
3
12
  type: "config";
4
13
  commandsDir: string;
5
14
  routesDir: string;
@@ -4,6 +4,7 @@
4
4
  * 요청은 감독 프로세스 (부모) 가 받아 이 포트로 중계한다. 부모 IPC 가 끊기면 종료.
5
5
  */
6
6
  import { createServer } from "node:http";
7
+ import { relative } from "node:path";
7
8
  import { loadConfig, mcpDeclarations, resolveCommandsDir, resolvePromptsDir, resolveResourcesDir, resolveRoutesDir } from "../config.js";
8
9
  import { loadProjectInstrumentation } from "../instrumentation.js";
9
10
  import { createMcpHttpHandler } from "../mcp/http.js";
@@ -11,6 +12,8 @@ import { loadPromptSet, withPrompts } from "../prompt.js";
11
12
  import { loadResourceSet, withResources } from "../resource.js";
12
13
  import { loadRoutes } from "../route.js";
13
14
  import { loadRegistry } from "../scan.js";
15
+ import { resolveTypesPath, writeTypes } from "../typegen.js";
16
+ import { tapAudit } from "./dev-tap.js";
14
17
  const send = (message) => new Promise((resolve) => {
15
18
  if (!process.connected)
16
19
  return resolve();
@@ -41,8 +44,14 @@ async function main() {
41
44
  });
42
45
  const registry = withPrompts(withResources(await loadRegistry(commandsDir), await loadResourceSet(resourcesDir)), await loadPromptSet(promptsDir));
43
46
  const routes = await loadRoutes(routesDir);
47
+ generateTypes(cwd, config.types, commandsDir);
48
+ // 감사 tap: 모든 호출의 이벤트를 감독 프로세스로 (전달을 기다리지 않는다). 프로젝트 audit 설정의 동작은 그대로
49
+ const tapped = tapAudit(registry, config.audit, (event) => {
50
+ if (process.connected)
51
+ process.send({ type: "audit", event }, undefined, {}, () => { });
52
+ });
44
53
  const listener = createMcpHttpHandler({
45
- registry,
54
+ registry: tapped.registry,
46
55
  instrumentation,
47
56
  ...(config.app ? { app: config.app } : {}),
48
57
  ...(config.auth ? { auth: config.auth } : {}),
@@ -50,6 +59,7 @@ async function main() {
50
59
  ...(config.oauth ? { oauth: config.oauth } : {}),
51
60
  ...(routes.length > 0 ? { routes } : {}),
52
61
  ...mcpDeclarations(config),
62
+ audit: tapped.audit,
53
63
  server: config.server?.allowedOrigins ? { allowedOrigins: config.server.allowedOrigins } : {},
54
64
  onerror: (error) => process.stderr.write(`mcp: ${error.message}\n`),
55
65
  });
@@ -77,6 +87,19 @@ main().catch(async (error) => {
77
87
  await send({ type: "error", message: describeLoadError(error) });
78
88
  process.exit(1);
79
89
  });
90
+ /** 명령어 타입 생성 (config.types, specs/client.md §4). 실패 = stderr 1줄 (서버는 계속) */
91
+ function generateTypes(cwd, types, commandsDir) {
92
+ const out = resolveTypesPath(cwd, types);
93
+ if (out === undefined)
94
+ return;
95
+ try {
96
+ if (writeTypes(commandsDir, out).changed)
97
+ process.stderr.write(`mext dev: wrote ${relative(cwd, out) || out}\n`);
98
+ }
99
+ catch (error) {
100
+ process.stderr.write(`mext dev: cannot write command types (${error instanceof Error ? error.message : String(error)})\n`);
101
+ }
102
+ }
80
103
  /** SyntaxError 는 stack 첫 줄들에 파일 위치가 있다 (ESM 파싱 오류). 그 외는 message (MextBuildError = issue 목록) */
81
104
  function describeLoadError(error) {
82
105
  if (error instanceof SyntaxError && error.stack) {
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.js","sourceRoot":"","sources":["../../src/cli/dev-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * mext dev 의 서버 자식 프로세스 엔트리 (cli/dev.ts 가 fork). 직접 실행하지 않는다.\n * cwd 의 mext.config.ts · commands/ 를 로드해 127.0.0.1 임의 포트에서 MCP HTTP 처리기를 띄우고 IPC 로 보고한다.\n * 요청은 감독 프로세스 (부모) 가 받아 이 포트로 중계한다. 부모 IPC 가 끊기면 종료.\n */\nimport { createServer } from \"node:http\"\nimport type { AddressInfo } from \"node:net\"\nimport { loadConfig, mcpDeclarations, resolveCommandsDir, resolvePromptsDir, resolveResourcesDir, resolveRoutesDir } from \"../config.ts\"\nimport { loadProjectInstrumentation } from \"../instrumentation.ts\"\nimport { createMcpHttpHandler } from \"../mcp/http.ts\"\nimport { loadPromptSet, withPrompts } from \"../prompt.ts\"\nimport { loadResourceSet, withResources } from \"../resource.ts\"\nimport { loadRoutes } from \"../route.ts\"\nimport { loadRegistry } from \"../scan.ts\"\n\n/** 자식 → 부모 */\nexport type DevChildMessage =\n | {\n type: \"config\"\n commandsDir: string\n routesDir: string\n /** 감시 대상 (§10). resources/ · prompts/ */\n resourcesDir: string\n promptsDir: string\n server: { host?: string; port?: number }\n }\n | { type: \"ready\"; port: number; commands: number }\n | { type: \"error\"; message: string }\n\n/** 부모 → 자식. retire = 새 자식으로 교체됨 (listen 스트림을 닫고 진행 중 요청은 끝까지), shutdown = 종료 */\nexport type DevParentMessage = { type: \"shutdown\" } | { type: \"retire\" }\n\nconst send = (message: DevChildMessage): Promise<void> =>\n new Promise((resolve) => {\n if (!process.connected) return resolve()\n process.send!(message, undefined, {}, () => resolve())\n })\n\n// 터미널 Ctrl-C 는 프로세스 그룹 전체에 간다. 종료는 부모가 정한다 (shutdown · IPC 끊김)\nprocess.on(\"SIGINT\", () => {})\nprocess.on(\"disconnect\", () => process.exit(0))\n\nasync function main(): Promise<void> {\n const cwd = process.cwd()\n // register() = 명령어 · config import 전\n const instrumentation = await loadProjectInstrumentation(cwd)\n const config = await loadConfig(cwd)\n const commandsDir = resolveCommandsDir(cwd, config)\n const routesDir = resolveRoutesDir(cwd, config)\n const resourcesDir = resolveResourcesDir(cwd, config)\n const promptsDir = resolvePromptsDir(cwd, config)\n await send({\n type: \"config\",\n commandsDir,\n routesDir,\n resourcesDir,\n promptsDir,\n server: {\n ...(config.server?.host !== undefined ? { host: config.server.host } : {}),\n ...(config.server?.port !== undefined ? { port: config.server.port } : {}),\n },\n })\n const registry = withPrompts(\n withResources(await loadRegistry(commandsDir), await loadResourceSet(resourcesDir)),\n await loadPromptSet(promptsDir),\n )\n const routes = await loadRoutes(routesDir)\n const listener = createMcpHttpHandler({\n registry,\n instrumentation,\n ...(config.app ? { app: config.app } : {}),\n ...(config.auth ? { auth: config.auth } : {}),\n ...(config.tools ? { tools: config.tools } : {}),\n ...(config.oauth ? { oauth: config.oauth } : {}),\n ...(routes.length > 0 ? { routes } : {}),\n ...mcpDeclarations(config),\n server: config.server?.allowedOrigins ? { allowedOrigins: config.server.allowedOrigins } : {},\n onerror: (error) => process.stderr.write(`mcp: ${error.message}\\n`),\n })\n const server = createServer(listener)\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(0, \"127.0.0.1\", () => {\n server.off(\"error\", reject)\n resolve()\n })\n })\n process.on(\"message\", (message: DevParentMessage) => {\n if (message?.type === \"retire\") void listener.closeSubscriptions()\n if (message?.type !== \"shutdown\") return\n void listener.close().finally(() => {\n server.closeAllConnections()\n server.close(() => process.exit(0))\n })\n })\n await send({ type: \"ready\", port: (server.address() as AddressInfo).port, commands: registry.commands.size })\n}\n\nmain().catch(async (error: unknown) => {\n await send({ type: \"error\", message: describeLoadError(error) })\n process.exit(1)\n})\n\n/** SyntaxError 는 stack 첫 줄들에 파일 위치가 있다 (ESM 파싱 오류). 그 외는 message (MextBuildError = issue 목록) */\nfunction describeLoadError(error: unknown): string {\n if (error instanceof SyntaxError && error.stack) {\n const at = error.stack.indexOf(\"\\n at \")\n return (at < 0 ? error.stack : error.stack.slice(0, at)).trim()\n }\n return error instanceof Error ? error.message : String(error)\n}\n"]}
1
+ {"version":3,"file":"dev-server.js","sourceRoot":"","sources":["../../src/cli/dev-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * mext dev 의 서버 자식 프로세스 엔트리 (cli/dev.ts 가 fork). 직접 실행하지 않는다.\n * cwd 의 mext.config.ts · commands/ 를 로드해 127.0.0.1 임의 포트에서 MCP HTTP 처리기를 띄우고 IPC 로 보고한다.\n * 요청은 감독 프로세스 (부모) 가 받아 이 포트로 중계한다. 부모 IPC 가 끊기면 종료.\n */\nimport { createServer } from \"node:http\"\nimport type { AddressInfo } from \"node:net\"\nimport { relative } from \"node:path\"\nimport type { AuditEvent } from \"../audit.ts\"\nimport { loadConfig, mcpDeclarations, resolveCommandsDir, resolvePromptsDir, resolveResourcesDir, resolveRoutesDir } from \"../config.ts\"\nimport { loadProjectInstrumentation } from \"../instrumentation.ts\"\nimport { createMcpHttpHandler } from \"../mcp/http.ts\"\nimport { loadPromptSet, withPrompts } from \"../prompt.ts\"\nimport { loadResourceSet, withResources } from \"../resource.ts\"\nimport { loadRoutes } from \"../route.ts\"\nimport { loadRegistry } from \"../scan.ts\"\nimport { resolveTypesPath, writeTypes } from \"../typegen.ts\"\nimport { tapAudit } from \"./dev-tap.ts\"\n\n/** 자식 → 부모 */\nexport type DevChildMessage =\n | { type: \"error\"; message: string }\n /** 감사 tap (spec §10): 호출 1건의 감사 이벤트 */\n | { type: \"audit\"; event: AuditEvent }\n| {\n type: \"config\"\n commandsDir: string\n routesDir: string\n /** 감시 대상 (§10). resources/ · prompts/ */\n resourcesDir: string\n promptsDir: string\n server: { host?: string; port?: number }\n }\n | { type: \"ready\"; port: number; commands: number }\n | { type: \"error\"; message: string }\n\n/** 부모 → 자식. retire = 새 자식으로 교체됨 (listen 스트림을 닫고 진행 중 요청은 끝까지), shutdown = 종료 */\nexport type DevParentMessage = { type: \"shutdown\" } | { type: \"retire\" }\n\nconst send = (message: DevChildMessage): Promise<void> =>\n new Promise((resolve) => {\n if (!process.connected) return resolve()\n process.send!(message, undefined, {}, () => resolve())\n })\n\n// 터미널 Ctrl-C 는 프로세스 그룹 전체에 간다. 종료는 부모가 정한다 (shutdown · IPC 끊김)\nprocess.on(\"SIGINT\", () => {})\nprocess.on(\"disconnect\", () => process.exit(0))\n\nasync function main(): Promise<void> {\n const cwd = process.cwd()\n // register() = 명령어 · config import 전\n const instrumentation = await loadProjectInstrumentation(cwd)\n const config = await loadConfig(cwd)\n const commandsDir = resolveCommandsDir(cwd, config)\n const routesDir = resolveRoutesDir(cwd, config)\n const resourcesDir = resolveResourcesDir(cwd, config)\n const promptsDir = resolvePromptsDir(cwd, config)\n await send({\n type: \"config\",\n commandsDir,\n routesDir,\n resourcesDir,\n promptsDir,\n server: {\n ...(config.server?.host !== undefined ? { host: config.server.host } : {}),\n ...(config.server?.port !== undefined ? { port: config.server.port } : {}),\n },\n })\n const registry = withPrompts(\n withResources(await loadRegistry(commandsDir), await loadResourceSet(resourcesDir)),\n await loadPromptSet(promptsDir),\n )\n const routes = await loadRoutes(routesDir)\n generateTypes(cwd, config.types, commandsDir)\n // 감사 tap: 모든 호출의 이벤트를 감독 프로세스로 (전달을 기다리지 않는다). 프로젝트 audit 설정의 동작은 그대로\n const tapped = tapAudit(registry, config.audit, (event) => {\n if (process.connected) process.send!({ type: \"audit\", event } satisfies DevChildMessage, undefined, {}, () => {})\n })\n const listener = createMcpHttpHandler({\n registry: tapped.registry,\n instrumentation,\n ...(config.app ? { app: config.app } : {}),\n ...(config.auth ? { auth: config.auth } : {}),\n ...(config.tools ? { tools: config.tools } : {}),\n ...(config.oauth ? { oauth: config.oauth } : {}),\n ...(routes.length > 0 ? { routes } : {}),\n ...mcpDeclarations(config),\n audit: tapped.audit,\n server: config.server?.allowedOrigins ? { allowedOrigins: config.server.allowedOrigins } : {},\n onerror: (error) => process.stderr.write(`mcp: ${error.message}\\n`),\n })\n const server = createServer(listener)\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject)\n server.listen(0, \"127.0.0.1\", () => {\n server.off(\"error\", reject)\n resolve()\n })\n })\n process.on(\"message\", (message: DevParentMessage) => {\n if (message?.type === \"retire\") void listener.closeSubscriptions()\n if (message?.type !== \"shutdown\") return\n void listener.close().finally(() => {\n server.closeAllConnections()\n server.close(() => process.exit(0))\n })\n })\n await send({ type: \"ready\", port: (server.address() as AddressInfo).port, commands: registry.commands.size })\n}\n\nmain().catch(async (error: unknown) => {\n await send({ type: \"error\", message: describeLoadError(error) })\n process.exit(1)\n})\n\n/** 명령어 타입 생성 (config.types, specs/client.md §4). 실패 = stderr 1줄 (서버는 계속) */\nfunction generateTypes(cwd: string, types: boolean | string | undefined, commandsDir: string): void {\n const out = resolveTypesPath(cwd, types)\n if (out === undefined) return\n try {\n if (writeTypes(commandsDir, out).changed) process.stderr.write(`mext dev: wrote ${relative(cwd, out) || out}\\n`)\n } catch (error) {\n process.stderr.write(`mext dev: cannot write command types (${error instanceof Error ? error.message : String(error)})\\n`)\n }\n}\n\n/** SyntaxError 는 stack 첫 줄들에 파일 위치가 있다 (ESM 파싱 오류). 그 외는 message (MextBuildError = issue 목록) */\nfunction describeLoadError(error: unknown): string {\n if (error instanceof SyntaxError && error.stack) {\n const at = error.stack.indexOf(\"\\n at \")\n return (at < 0 ? error.stack : error.stack.slice(0, at)).trim()\n }\n return error instanceof Error ? error.message : String(error)\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { AuditConfig, AuditEvent } from "../audit.ts";
2
+ import type { Registry } from "../registry.ts";
3
+ /**
4
+ * mext dev 의 감사 tap, 서버 자식 쪽 (spec §10 감사 tap). cli/dev-server.ts 만 import 한다:
5
+ * mext start · handler.js · 로컬 bin · 고객 CLI · mext dev --stdio 에는 이 코드 경로가 없다.
6
+ *
7
+ * 프로젝트 audit 설정을 감싸 모든 호출의 이벤트를 emit 으로 넘긴다 (readOnly · 숨긴 명령어 호출 시도 포함).
8
+ * 프로젝트가 관찰하는 동작은 tap 이 없을 때와 같다: 프로젝트 sink 는 자기 설정 (includeReadOnly · includeHidden) 이
9
+ * 기록했을 이벤트만 같은 인자로 받고, onerror 는 그 이벤트에서 난 예외만 받는다
10
+ */
11
+ export interface TappedAudit<R extends Registry> {
12
+ registry: R;
13
+ audit: AuditConfig;
14
+ }
15
+ /**
16
+ * emit = 이벤트 사본 (JSON 값) 전달. 예외는 삼키고 반환값을 기다리지 않는다.
17
+ * registry = 프로젝트가 기록하지 않는 호출 (설정 없음 · readOnly 인데 includeReadOnly 아님) 의 명령어만
18
+ * audit 함수 (target · summary · input) 를 예외를 삼키는 래퍼로 바꾼 사본: 그 예외가 프로젝트 onerror 로 가지 않게
19
+ */
20
+ export declare function tapAudit<R extends Registry>(registry: R, project: AuditConfig | undefined, emit: (event: AuditEvent) => void): TappedAudit<R>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * emit = 이벤트 사본 (JSON 값) 전달. 예외는 삼키고 반환값을 기다리지 않는다.
3
+ * registry = 프로젝트가 기록하지 않는 호출 (설정 없음 · readOnly 인데 includeReadOnly 아님) 의 명령어만
4
+ * audit 함수 (target · summary · input) 를 예외를 삼키는 래퍼로 바꾼 사본: 그 예외가 프로젝트 onerror 로 가지 않게
5
+ */
6
+ export function tapAudit(registry, project, emit) {
7
+ /** 프로젝트 설정이 이 이벤트를 기록했을지 (audit.ts startAudit · auditHidden 과 같은 조건) */
8
+ const recorded = (event) => {
9
+ if (!project)
10
+ return false;
11
+ if (event.outcome === "hidden")
12
+ return project.includeHidden === true;
13
+ return !event.readOnly || project.includeReadOnly === true;
14
+ };
15
+ const audit = {
16
+ includeReadOnly: true,
17
+ includeHidden: true,
18
+ ...(project?.input !== undefined ? { input: project.input } : {}),
19
+ ...(project?.onerror ? { onerror: project.onerror } : {}),
20
+ sink(event) {
21
+ try {
22
+ // 프로젝트 sink 가 이벤트를 바꾸기 전의 값. 직렬화할 수 없는 입력 (순환 · bigint) = 입력 없이
23
+ emit(copy(event));
24
+ }
25
+ catch {
26
+ // tap 의 예외는 응답 · 프로젝트 sink 에 영향을 주지 않는다
27
+ }
28
+ // 프로젝트 sink 의 반환 (Promise) · 예외는 그대로: 응답 전 await · onerror 보고 (§11.1)
29
+ return recorded(event) ? project.sink(event) : undefined;
30
+ },
31
+ };
32
+ const quiet = (entry) => !project || (entry.command.readOnly && project.includeReadOnly !== true);
33
+ const commands = new Map();
34
+ for (const [id, entry] of registry.commands) {
35
+ commands.set(id, entry.command.audit && quiet(entry) ? { ...entry, command: quietAudit(entry.command) } : entry);
36
+ }
37
+ return { registry: { ...registry, commands }, audit };
38
+ }
39
+ function copy(event) {
40
+ try {
41
+ return JSON.parse(JSON.stringify(event));
42
+ }
43
+ catch {
44
+ const { input: _input, target: _target, ...rest } = event;
45
+ return JSON.parse(JSON.stringify(rest));
46
+ }
47
+ }
48
+ /** audit 함수의 예외 = 그 필드 없음 (audit.ts guarded 가 보고 없이 계속했을 때와 같은 이벤트) */
49
+ function quietAudit(command) {
50
+ const a = command.audit;
51
+ const audit = {
52
+ ...a,
53
+ ...(typeof a.target === "function" ? { target: quiet(a.target) } : {}),
54
+ ...(typeof a.summary === "function" ? { summary: quiet(a.summary) } : {}),
55
+ // 가림 함수 예외 = 입력 없이 (가리지 않은 값으로 대체하지 않는다)
56
+ ...(typeof a.input === "function" ? { input: quiet(a.input) } : {}),
57
+ };
58
+ return { ...command, audit };
59
+ }
60
+ function quiet(fn) {
61
+ return (...args) => {
62
+ try {
63
+ return fn(...args);
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ };
69
+ }
70
+ //# sourceMappingURL=dev-tap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-tap.js","sourceRoot":"","sources":["../../src/cli/dev-tap.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuditConfig, AuditEvent } from \"../audit.ts\"\nimport type { Command, CommandAudit } from \"../command.ts\"\nimport type { RegisteredCommand, Registry } from \"../registry.ts\"\n\n/**\n * mext dev 의 감사 tap, 서버 자식 쪽 (spec §10 감사 tap). cli/dev-server.ts 만 import 한다:\n * mext start · handler.js · 로컬 bin · 고객 CLI · mext dev --stdio 에는 이 코드 경로가 없다.\n *\n * 프로젝트 audit 설정을 감싸 모든 호출의 이벤트를 emit 으로 넘긴다 (readOnly · 숨긴 명령어 호출 시도 포함).\n * 프로젝트가 관찰하는 동작은 tap 이 없을 때와 같다: 프로젝트 sink 는 자기 설정 (includeReadOnly · includeHidden) 이\n * 기록했을 이벤트만 같은 인자로 받고, onerror 는 그 이벤트에서 난 예외만 받는다\n */\n\nexport interface TappedAudit<R extends Registry> {\n registry: R\n audit: AuditConfig\n}\n\n/**\n * emit = 이벤트 사본 (JSON 값) 전달. 예외는 삼키고 반환값을 기다리지 않는다.\n * registry = 프로젝트가 기록하지 않는 호출 (설정 없음 · readOnly 인데 includeReadOnly 아님) 의 명령어만\n * audit 함수 (target · summary · input) 를 예외를 삼키는 래퍼로 바꾼 사본: 그 예외가 프로젝트 onerror 로 가지 않게\n */\nexport function tapAudit<R extends Registry>(registry: R, project: AuditConfig | undefined, emit: (event: AuditEvent) => void): TappedAudit<R> {\n /** 프로젝트 설정이 이 이벤트를 기록했을지 (audit.ts startAudit · auditHidden 과 같은 조건) */\n const recorded = (event: AuditEvent): boolean => {\n if (!project) return false\n if (event.outcome === \"hidden\") return project.includeHidden === true\n return !event.readOnly || project.includeReadOnly === true\n }\n const audit: AuditConfig = {\n includeReadOnly: true,\n includeHidden: true,\n ...(project?.input !== undefined ? { input: project.input } : {}),\n ...(project?.onerror ? { onerror: project.onerror } : {}),\n sink(event) {\n try {\n // 프로젝트 sink 가 이벤트를 바꾸기 전의 값. 직렬화할 수 없는 입력 (순환 · bigint) = 입력 없이\n emit(copy(event))\n } catch {\n // tap 의 예외는 응답 · 프로젝트 sink 에 영향을 주지 않는다\n }\n // 프로젝트 sink 의 반환 (Promise) · 예외는 그대로: 응답 전 await · onerror 보고 (§11.1)\n return recorded(event) ? project!.sink(event) : undefined\n },\n }\n const quiet = (entry: RegisteredCommand): boolean => !project || (entry.command.readOnly && project.includeReadOnly !== true)\n const commands = new Map<string, RegisteredCommand>()\n for (const [id, entry] of registry.commands) {\n commands.set(id, entry.command.audit && quiet(entry) ? { ...entry, command: quietAudit(entry.command) } : entry)\n }\n return { registry: { ...registry, commands }, audit }\n}\n\nfunction copy(event: AuditEvent): AuditEvent {\n try {\n return JSON.parse(JSON.stringify(event)) as AuditEvent\n } catch {\n const { input: _input, target: _target, ...rest } = event\n return JSON.parse(JSON.stringify(rest)) as AuditEvent\n }\n}\n\n/** audit 함수의 예외 = 그 필드 없음 (audit.ts guarded 가 보고 없이 계속했을 때와 같은 이벤트) */\nfunction quietAudit(command: Command): Command {\n const a = command.audit!\n const audit: CommandAudit = {\n ...a,\n ...(typeof a.target === \"function\" ? { target: quiet(a.target) } : {}),\n ...(typeof a.summary === \"function\" ? { summary: quiet(a.summary) } : {}),\n // 가림 함수 예외 = 입력 없이 (가리지 않은 값으로 대체하지 않는다)\n ...(typeof a.input === \"function\" ? { input: quiet(a.input) } : {}),\n }\n return { ...command, audit }\n}\n\nfunction quiet<A extends unknown[], T>(fn: (...args: A) => T): (...args: A) => T | undefined {\n return (...args) => {\n try {\n return fn(...args)\n } catch {\n return undefined\n }\n }\n}\n"]}
package/dist/cli/dev.js CHANGED
@@ -11,6 +11,7 @@ import { redirectStdout, serveMcpStdio } from "../mcp/stdio.js";
11
11
  import { loadPromptSet, withPrompts } from "../prompt.js";
12
12
  import { loadResourceSet, withResources } from "../resource.js";
13
13
  import { loadRegistry } from "../scan.js";
14
+ import { createDevAuditHub, newAuditToken } from "./dev-audit.js";
14
15
  import { acquireDevLock, releaseDevLock, updateDevLock } from "./dev-lock.js";
15
16
  import { table } from "./help.js";
16
17
  import { EXIT, exitCodeOf, formatError } from "./output.js";
@@ -85,7 +86,7 @@ async function stdio(io) {
85
86
  const instrumentation = await loadProjectInstrumentation(io.cwd);
86
87
  const config = await loadConfig(io.cwd);
87
88
  const registry = withPrompts(withResources(await loadRegistry(resolveCommandsDir(io.cwd, config)), await loadResourceSet(resolveResourcesDir(io.cwd, config))), await loadPromptSet(resolvePromptsDir(io.cwd, config)));
88
- const principal = await resolveLocalPrincipal(config);
89
+ const principal = await resolveLocalPrincipal(config, "stdio");
89
90
  const server = serveMcpStdio({
90
91
  registry,
91
92
  instrumentation,
@@ -135,9 +136,11 @@ function writerStream(writer) {
135
136
  async function supervise(args, io) {
136
137
  const cwd = resolve(io.cwd);
137
138
  const log = (line) => void io.stderr.write(`${DEV_PROG}: ${line}\n`);
139
+ // 감사 tap (spec §10): 실행마다 새 토큰. 잠금 파일에만 적는다 (출력하지 않는다)
140
+ const auditToken = newAuditToken();
138
141
  let lock;
139
142
  try {
140
- const got = acquireDevLock(cwd);
143
+ const got = acquireDevLock(cwd, null, auditToken);
141
144
  if ("running" in got) {
142
145
  const where = got.running.url ? `at ${got.running.url}` : "(starting)";
143
146
  io.stderr.write(`error: ${DEV_PROG} is already running ${where} (PID ${got.running.pid}).\n` +
@@ -152,7 +155,7 @@ async function supervise(args, io) {
152
155
  }
153
156
  const onExit = () => releaseDevLock(cwd);
154
157
  process.on("exit", onExit);
155
- const supervisor = new DevSupervisor(cwd, log);
158
+ const supervisor = new DevSupervisor(cwd, log, createDevAuditHub(auditToken));
156
159
  try {
157
160
  const first = await supervisor.start();
158
161
  const host = args.host ?? first?.host ?? DEFAULT_HOST;
@@ -206,9 +209,12 @@ class DevSupervisor {
206
209
  debounce;
207
210
  changed = new Set();
208
211
  firstConfig;
209
- constructor(cwd, log) {
212
+ /** 감사 tap: 자식이 넘긴 이벤트 보관 · endpoint */
213
+ audit;
214
+ constructor(cwd, log, audit) {
210
215
  this.cwd = cwd;
211
216
  this.log = log;
217
+ this.audit = audit;
212
218
  this.commandsDir = resolve(cwd, "commands");
213
219
  this.optionalDirs = ["routes", "resources", "prompts"].map((name) => resolve(cwd, name));
214
220
  }
@@ -259,6 +265,8 @@ class DevSupervisor {
259
265
  this.onConfig(child, message);
260
266
  else if (message.type === "ready")
261
267
  this.onReady(child, message.port, message.commands);
268
+ else if (message.type === "audit")
269
+ this.audit.push(message.event);
262
270
  else if (message.type === "error") {
263
271
  failed = true;
264
272
  this.onLoadFailed(child, message.message);
@@ -367,6 +375,9 @@ class DevSupervisor {
367
375
  }
368
376
  /* -------------------------------------------------------------- 요청 중계 */
369
377
  dispatch(req, res) {
378
+ // 감사 tap endpoint = 감독 프로세스가 직접 응답 (자식으로 중계하지 않는다. 재시작 중에도 응답)
379
+ if (this.audit.handle(req, res))
380
+ return;
370
381
  if (this.pending) {
371
382
  this.queue.push(() => this.dispatch(req, res));
372
383
  return;
@@ -509,6 +520,7 @@ class DevSupervisor {
509
520
  clearTimeout(this.debounce);
510
521
  for (const w of this.watchers.values())
511
522
  w.close();
523
+ this.audit.close();
512
524
  this.watchers.clear();
513
525
  const children = [this.current, this.pending, ...this.retired].filter((c) => c !== undefined);
514
526
  const exited = children.map((c) => new Promise((done) => {