@opengsd/gsd-pi 1.0.2-dev.50223bc → 1.0.2-dev.5961fbf

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 (251) hide show
  1. package/dist/resource-loader.d.ts +5 -0
  2. package/dist/resource-loader.js +24 -8
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/gsd/auto/loop.js +19 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  6. package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
  7. package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
  8. package/dist/web/standalone/.next/BUILD_ID +1 -1
  9. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  10. package/dist/web/standalone/.next/build-manifest.json +2 -2
  11. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  12. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  29. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  30. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  31. package/dist/web/standalone/.next/server/app/index.html +1 -1
  32. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  39. package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
  40. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  42. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  43. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  44. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  45. package/dist/web/standalone/package.json +0 -1
  46. package/dist/worktree-cli.d.ts +0 -2
  47. package/dist/worktree-cli.js +21 -9
  48. package/package.json +9 -4
  49. package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
  50. package/packages/cloud-mcp-gateway/package.json +5 -4
  51. package/packages/contracts/package.json +2 -2
  52. package/packages/daemon/bin/gsd-daemon.js +14 -0
  53. package/packages/daemon/bin/gsd-mcp-runtime.js +14 -0
  54. package/packages/daemon/bin/gsd-mcp.js +14 -0
  55. package/packages/daemon/dist/channel-manager.d.ts +53 -0
  56. package/packages/daemon/dist/channel-manager.d.ts.map +1 -0
  57. package/packages/daemon/dist/channel-manager.js +167 -0
  58. package/packages/daemon/dist/channel-manager.js.map +1 -0
  59. package/packages/daemon/dist/cli.d.ts +3 -0
  60. package/packages/daemon/dist/cli.d.ts.map +1 -0
  61. package/packages/daemon/dist/cli.js +94 -0
  62. package/packages/daemon/dist/cli.js.map +1 -0
  63. package/packages/daemon/dist/cloud-cli.d.ts +7 -0
  64. package/packages/daemon/dist/cloud-cli.d.ts.map +1 -0
  65. package/packages/daemon/dist/cloud-cli.js +96 -0
  66. package/packages/daemon/dist/cloud-cli.js.map +1 -0
  67. package/packages/daemon/dist/cloud-config.d.ts +18 -0
  68. package/packages/daemon/dist/cloud-config.d.ts.map +1 -0
  69. package/packages/daemon/dist/cloud-config.js +209 -0
  70. package/packages/daemon/dist/cloud-config.js.map +1 -0
  71. package/packages/daemon/dist/cloud-config.test.d.ts +2 -0
  72. package/packages/daemon/dist/cloud-config.test.d.ts.map +1 -0
  73. package/packages/daemon/dist/cloud-config.test.js +132 -0
  74. package/packages/daemon/dist/cloud-config.test.js.map +1 -0
  75. package/packages/daemon/dist/cloud-runtime.d.ts +26 -0
  76. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -0
  77. package/packages/daemon/dist/cloud-runtime.js +180 -0
  78. package/packages/daemon/dist/cloud-runtime.js.map +1 -0
  79. package/packages/daemon/dist/cloud-runtime.test.d.ts +2 -0
  80. package/packages/daemon/dist/cloud-runtime.test.d.ts.map +1 -0
  81. package/packages/daemon/dist/cloud-runtime.test.js +28 -0
  82. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -0
  83. package/packages/daemon/dist/cloud-token.d.ts +3 -0
  84. package/packages/daemon/dist/cloud-token.d.ts.map +1 -0
  85. package/packages/daemon/dist/cloud-token.js +37 -0
  86. package/packages/daemon/dist/cloud-token.js.map +1 -0
  87. package/packages/daemon/dist/commands.d.ts +25 -0
  88. package/packages/daemon/dist/commands.d.ts.map +1 -0
  89. package/packages/daemon/dist/commands.js +81 -0
  90. package/packages/daemon/dist/commands.js.map +1 -0
  91. package/packages/daemon/dist/config.d.ts +17 -0
  92. package/packages/daemon/dist/config.d.ts.map +1 -0
  93. package/packages/daemon/dist/config.js +146 -0
  94. package/packages/daemon/dist/config.js.map +1 -0
  95. package/packages/daemon/dist/daemon.d.ts +38 -0
  96. package/packages/daemon/dist/daemon.d.ts.map +1 -0
  97. package/packages/daemon/dist/daemon.js +194 -0
  98. package/packages/daemon/dist/daemon.js.map +1 -0
  99. package/packages/daemon/dist/daemon.test.d.ts +2 -0
  100. package/packages/daemon/dist/daemon.test.d.ts.map +1 -0
  101. package/packages/daemon/dist/daemon.test.js +692 -0
  102. package/packages/daemon/dist/daemon.test.js.map +1 -0
  103. package/packages/daemon/dist/discord-bot.d.ts +70 -0
  104. package/packages/daemon/dist/discord-bot.d.ts.map +1 -0
  105. package/packages/daemon/dist/discord-bot.js +433 -0
  106. package/packages/daemon/dist/discord-bot.js.map +1 -0
  107. package/packages/daemon/dist/discord-bot.test.d.ts +2 -0
  108. package/packages/daemon/dist/discord-bot.test.d.ts.map +1 -0
  109. package/packages/daemon/dist/discord-bot.test.js +667 -0
  110. package/packages/daemon/dist/discord-bot.test.js.map +1 -0
  111. package/packages/daemon/dist/event-bridge.d.ts +72 -0
  112. package/packages/daemon/dist/event-bridge.d.ts.map +1 -0
  113. package/packages/daemon/dist/event-bridge.js +366 -0
  114. package/packages/daemon/dist/event-bridge.js.map +1 -0
  115. package/packages/daemon/dist/event-bridge.test.d.ts +9 -0
  116. package/packages/daemon/dist/event-bridge.test.d.ts.map +1 -0
  117. package/packages/daemon/dist/event-bridge.test.js +528 -0
  118. package/packages/daemon/dist/event-bridge.test.js.map +1 -0
  119. package/packages/daemon/dist/event-formatter.d.ts +34 -0
  120. package/packages/daemon/dist/event-formatter.d.ts.map +1 -0
  121. package/packages/daemon/dist/event-formatter.js +355 -0
  122. package/packages/daemon/dist/event-formatter.js.map +1 -0
  123. package/packages/daemon/dist/event-formatter.test.d.ts +2 -0
  124. package/packages/daemon/dist/event-formatter.test.d.ts.map +1 -0
  125. package/packages/daemon/dist/event-formatter.test.js +333 -0
  126. package/packages/daemon/dist/event-formatter.test.js.map +1 -0
  127. package/packages/daemon/dist/index.d.ts +25 -0
  128. package/packages/daemon/dist/index.d.ts.map +1 -0
  129. package/packages/daemon/dist/index.js +17 -0
  130. package/packages/daemon/dist/index.js.map +1 -0
  131. package/packages/daemon/dist/launchd.d.ts +49 -0
  132. package/packages/daemon/dist/launchd.d.ts.map +1 -0
  133. package/packages/daemon/dist/launchd.js +188 -0
  134. package/packages/daemon/dist/launchd.js.map +1 -0
  135. package/packages/daemon/dist/launchd.test.d.ts +2 -0
  136. package/packages/daemon/dist/launchd.test.d.ts.map +1 -0
  137. package/packages/daemon/dist/launchd.test.js +296 -0
  138. package/packages/daemon/dist/launchd.test.js.map +1 -0
  139. package/packages/daemon/dist/local-tool-executor.d.ts +22 -0
  140. package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -0
  141. package/packages/daemon/dist/local-tool-executor.js +307 -0
  142. package/packages/daemon/dist/local-tool-executor.js.map +1 -0
  143. package/packages/daemon/dist/local-tool-executor.test.d.ts +2 -0
  144. package/packages/daemon/dist/local-tool-executor.test.d.ts.map +1 -0
  145. package/packages/daemon/dist/local-tool-executor.test.js +111 -0
  146. package/packages/daemon/dist/local-tool-executor.test.js.map +1 -0
  147. package/packages/daemon/dist/logger.d.ts +25 -0
  148. package/packages/daemon/dist/logger.d.ts.map +1 -0
  149. package/packages/daemon/dist/logger.js +72 -0
  150. package/packages/daemon/dist/logger.js.map +1 -0
  151. package/packages/daemon/dist/mcp-cli.d.ts +3 -0
  152. package/packages/daemon/dist/mcp-cli.d.ts.map +1 -0
  153. package/packages/daemon/dist/mcp-cli.js +8 -0
  154. package/packages/daemon/dist/mcp-cli.js.map +1 -0
  155. package/packages/daemon/dist/mcp-cli.test.d.ts +2 -0
  156. package/packages/daemon/dist/mcp-cli.test.d.ts.map +1 -0
  157. package/packages/daemon/dist/mcp-cli.test.js +13 -0
  158. package/packages/daemon/dist/mcp-cli.test.js.map +1 -0
  159. package/packages/daemon/dist/mcp-runtime-cli.d.ts +3 -0
  160. package/packages/daemon/dist/mcp-runtime-cli.d.ts.map +1 -0
  161. package/packages/daemon/dist/mcp-runtime-cli.js +8 -0
  162. package/packages/daemon/dist/mcp-runtime-cli.js.map +1 -0
  163. package/packages/daemon/dist/message-batcher.d.ts +78 -0
  164. package/packages/daemon/dist/message-batcher.d.ts.map +1 -0
  165. package/packages/daemon/dist/message-batcher.js +173 -0
  166. package/packages/daemon/dist/message-batcher.js.map +1 -0
  167. package/packages/daemon/dist/message-batcher.test.d.ts +2 -0
  168. package/packages/daemon/dist/message-batcher.test.d.ts.map +1 -0
  169. package/packages/daemon/dist/message-batcher.test.js +242 -0
  170. package/packages/daemon/dist/message-batcher.test.js.map +1 -0
  171. package/packages/daemon/dist/orchestrator.d.ts +98 -0
  172. package/packages/daemon/dist/orchestrator.d.ts.map +1 -0
  173. package/packages/daemon/dist/orchestrator.js +359 -0
  174. package/packages/daemon/dist/orchestrator.js.map +1 -0
  175. package/packages/daemon/dist/orchestrator.test.d.ts +8 -0
  176. package/packages/daemon/dist/orchestrator.test.d.ts.map +1 -0
  177. package/packages/daemon/dist/orchestrator.test.js +425 -0
  178. package/packages/daemon/dist/orchestrator.test.js.map +1 -0
  179. package/packages/daemon/dist/project-scanner.d.ts +18 -0
  180. package/packages/daemon/dist/project-scanner.d.ts.map +1 -0
  181. package/packages/daemon/dist/project-scanner.js +90 -0
  182. package/packages/daemon/dist/project-scanner.js.map +1 -0
  183. package/packages/daemon/dist/project-scanner.test.d.ts +5 -0
  184. package/packages/daemon/dist/project-scanner.test.d.ts.map +1 -0
  185. package/packages/daemon/dist/project-scanner.test.js +183 -0
  186. package/packages/daemon/dist/project-scanner.test.js.map +1 -0
  187. package/packages/daemon/dist/session-manager.d.ts +70 -0
  188. package/packages/daemon/dist/session-manager.d.ts.map +1 -0
  189. package/packages/daemon/dist/session-manager.js +358 -0
  190. package/packages/daemon/dist/session-manager.js.map +1 -0
  191. package/packages/daemon/dist/session-manager.test.d.ts +9 -0
  192. package/packages/daemon/dist/session-manager.test.d.ts.map +1 -0
  193. package/packages/daemon/dist/session-manager.test.js +616 -0
  194. package/packages/daemon/dist/session-manager.test.js.map +1 -0
  195. package/packages/daemon/dist/types.d.ts +133 -0
  196. package/packages/daemon/dist/types.d.ts.map +1 -0
  197. package/packages/daemon/dist/types.js +8 -0
  198. package/packages/daemon/dist/types.js.map +1 -0
  199. package/packages/daemon/dist/verbosity.d.ts +27 -0
  200. package/packages/daemon/dist/verbosity.d.ts.map +1 -0
  201. package/packages/daemon/dist/verbosity.js +86 -0
  202. package/packages/daemon/dist/verbosity.js.map +1 -0
  203. package/packages/daemon/dist/verbosity.test.d.ts +2 -0
  204. package/packages/daemon/dist/verbosity.test.d.ts.map +1 -0
  205. package/packages/daemon/dist/verbosity.test.js +136 -0
  206. package/packages/daemon/dist/verbosity.test.js.map +1 -0
  207. package/packages/daemon/package.json +9 -8
  208. package/packages/gsd-agent-core/package.json +6 -6
  209. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  210. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
  211. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  212. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  213. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  214. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  215. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
  216. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  217. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
  218. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  219. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  220. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
  221. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  222. package/packages/gsd-agent-modes/package.json +8 -8
  223. package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
  224. package/packages/mcp-server/package.json +6 -5
  225. package/packages/native/package.json +3 -3
  226. package/packages/pi-agent-core/package.json +4 -4
  227. package/packages/pi-ai/bin/pi-ai.js +14 -0
  228. package/packages/pi-ai/dist/models.generated.d.ts +0 -17
  229. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  230. package/packages/pi-ai/dist/models.generated.js +18 -35
  231. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  232. package/packages/pi-ai/package.json +5 -4
  233. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
  234. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  235. package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
  236. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  237. package/packages/pi-coding-agent/package.json +9 -9
  238. package/packages/pi-tui/package.json +2 -2
  239. package/packages/rpc-client/package.json +3 -3
  240. package/pkg/package.json +1 -1
  241. package/scripts/ensure-workspace-builds.cjs +4 -4
  242. package/scripts/install/deps.js +10 -0
  243. package/src/resources/extensions/gsd/auto/loop.ts +22 -0
  244. package/src/resources/extensions/gsd/auto/phases.ts +1 -1
  245. package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
  246. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
  247. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
  248. package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
  249. package/dist/tsconfig.extensions.tsbuildinfo +0 -1
  250. /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_buildManifest.js +0 -0
  251. /package/dist/web/standalone/.next/static/{JP7xjsa5zSaO76XhE-mFJ → spUYLkQXoHJyxYOMH9VQy}/_ssgManifest.js +0 -0
@@ -0,0 +1,307 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFileSync } from "node:child_process";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import { basename, join, resolve } from "node:path";
5
+ import { readCaptures, readHistory, readKnowledge, readProgress, readRoadmap, buildGraph, graphDiff, graphQuery, graphStatus, registerWorkflowTools, resolveGsdRoot, runDoctorLite, writeGraph, writeSnapshot, WORKFLOW_TOOL_NAMES, } from "@opengsd/mcp-server";
6
+ const WORKFLOW_TOOL_NAME_SET = new Set(WORKFLOW_TOOL_NAMES);
7
+ const QUERY_FIELDS = {
8
+ all: ["state", "project", "requirements", "milestones"],
9
+ state: ["state"],
10
+ status: ["state"],
11
+ project: ["project"],
12
+ requirements: ["requirements"],
13
+ milestones: ["milestones"],
14
+ };
15
+ export class LocalToolExecutor {
16
+ sessionManager;
17
+ scanProjects;
18
+ workflowHandlers = new Map();
19
+ constructor(sessionManager, scanProjects) {
20
+ this.sessionManager = sessionManager;
21
+ this.scanProjects = scanProjects;
22
+ registerWorkflowTools({
23
+ tool: (name, _description, _params, handler) => {
24
+ if (!WORKFLOW_TOOL_NAME_SET.has(name))
25
+ return;
26
+ this.workflowHandlers.set(name, handler);
27
+ },
28
+ });
29
+ }
30
+ async execute(toolName, rawArgs, projectAlias) {
31
+ const args = { ...rawArgs };
32
+ if (projectAlias) {
33
+ args.projectDir = await this.resolveProjectPath(projectAlias);
34
+ }
35
+ const workflowResult = this.executeWorkflowTool(toolName, args);
36
+ if (workflowResult)
37
+ return workflowResult;
38
+ switch (toolName) {
39
+ case "gsd_execute": {
40
+ const projectDir = await this.requiredProjectDir(args);
41
+ const sessionId = await this.sessionManager.startSession({
42
+ projectDir,
43
+ command: typeof args.command === "string" ? args.command : undefined,
44
+ model: typeof args.model === "string" ? args.model : undefined,
45
+ bare: typeof args.bare === "boolean" ? args.bare : undefined,
46
+ });
47
+ return { content: [{ type: "text", text: JSON.stringify({ sessionId, status: "started" }, null, 2) }] };
48
+ }
49
+ case "gsd_status": {
50
+ const session = this.sessionManager.getSession(String(args.sessionId ?? ""));
51
+ if (!session)
52
+ throw new Error(`Session not found: ${String(args.sessionId ?? "")}`);
53
+ const toolCallCount = session.events.filter((event) => {
54
+ const type = event.type;
55
+ return type === "tool_use" || type === "tool_execution_start";
56
+ }).length;
57
+ return { content: [{ type: "text", text: JSON.stringify({
58
+ status: session.status,
59
+ progress: {
60
+ eventCount: session.events.length,
61
+ toolCalls: toolCallCount,
62
+ },
63
+ recentEvents: session.events.slice(-10),
64
+ pendingBlocker: session.pendingBlocker
65
+ ? {
66
+ id: session.pendingBlocker.id,
67
+ method: session.pendingBlocker.method,
68
+ message: session.pendingBlocker.message,
69
+ }
70
+ : null,
71
+ cost: session.cost,
72
+ durationMs: Date.now() - session.startTime,
73
+ }, null, 2) }] };
74
+ }
75
+ case "gsd_result":
76
+ return { content: [{ type: "text", text: JSON.stringify(this.sessionManager.getResult(String(args.sessionId ?? "")), null, 2) }] };
77
+ case "gsd_cancel":
78
+ if (typeof args.sessionId === "string")
79
+ await this.sessionManager.cancelSession(args.sessionId);
80
+ else
81
+ await this.sessionManager.cancelSessionByDir(await this.requiredProjectDir(args));
82
+ return { content: [{ type: "text", text: JSON.stringify({ cancelled: true }, null, 2) }] };
83
+ case "gsd_resolve_blocker": {
84
+ const sessionId = String(args.sessionId ?? "");
85
+ const response = String(args.response ?? "");
86
+ await this.sessionManager.resolveBlocker(sessionId, response);
87
+ return { content: [{ type: "text", text: JSON.stringify({ resolved: true }, null, 2) }] };
88
+ }
89
+ case "gsd_query": {
90
+ const projectDir = await this.requiredProjectDir(args);
91
+ const query = typeof args.query === "string" ? args.query : undefined;
92
+ return { content: [{ type: "text", text: JSON.stringify(await readProjectState(projectDir, query), null, 2) }] };
93
+ }
94
+ case "gsd_progress":
95
+ return { content: [{ type: "text", text: JSON.stringify(await readProgress(await this.requiredProjectDir(args)), null, 2) }] };
96
+ case "gsd_roadmap":
97
+ return { content: [{ type: "text", text: JSON.stringify(await readRoadmap(await this.requiredProjectDir(args)), null, 2) }] };
98
+ case "gsd_history":
99
+ return { content: [{ type: "text", text: JSON.stringify(await readHistory(await this.requiredProjectDir(args)), null, 2) }] };
100
+ case "gsd_doctor":
101
+ return { content: [{ type: "text", text: JSON.stringify(await runDoctorLite(await this.requiredProjectDir(args)), null, 2) }] };
102
+ case "gsd_captures":
103
+ return { content: [{ type: "text", text: JSON.stringify(await readCaptures(await this.requiredProjectDir(args)), null, 2) }] };
104
+ case "gsd_knowledge":
105
+ return { content: [{ type: "text", text: JSON.stringify(await readKnowledge(await this.requiredProjectDir(args)), null, 2) }] };
106
+ case "gsd_graph":
107
+ return { content: [{ type: "text", text: JSON.stringify(await this.executeGraph(args), null, 2) }] };
108
+ default:
109
+ throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);
110
+ }
111
+ }
112
+ async advertisedProjects() {
113
+ const projects = await this.scanProjects();
114
+ return projects.map((project) => {
115
+ const remoteLabel = gitRemote(project.path);
116
+ return {
117
+ alias: project.name,
118
+ path: project.path,
119
+ repoIdentity: identityFor(project.path, remoteLabel),
120
+ ...(remoteLabel ? { remoteLabel } : {}),
121
+ markers: project.markers,
122
+ };
123
+ });
124
+ }
125
+ async requiredProjectDir(args) {
126
+ const value = args.projectDir;
127
+ if (typeof value === "string" && value.trim())
128
+ return this.resolveProjectPath(value);
129
+ throw new Error("projectDir or projectAlias is required");
130
+ }
131
+ async resolveProjectPath(aliasOrPath) {
132
+ const projects = await this.scanProjects();
133
+ const match = projects.find((project) => project.name === aliasOrPath || project.path === aliasOrPath);
134
+ if (!match)
135
+ throw new Error(`Project is not advertised by the Local GSD Runtime: ${aliasOrPath}`);
136
+ return match.path;
137
+ }
138
+ executeWorkflowTool(toolName, args) {
139
+ switch (toolName) {
140
+ case "gsd_decision_save": return this.invokeRegisteredWorkflowTool("gsd_decision_save", args);
141
+ case "gsd_save_decision": return this.invokeRegisteredWorkflowTool("gsd_save_decision", args);
142
+ case "gsd_requirement_update": return this.invokeRegisteredWorkflowTool("gsd_requirement_update", args);
143
+ case "gsd_update_requirement": return this.invokeRegisteredWorkflowTool("gsd_update_requirement", args);
144
+ case "gsd_requirement_save": return this.invokeRegisteredWorkflowTool("gsd_requirement_save", args);
145
+ case "gsd_save_requirement": return this.invokeRegisteredWorkflowTool("gsd_save_requirement", args);
146
+ case "gsd_milestone_generate_id": return this.invokeRegisteredWorkflowTool("gsd_milestone_generate_id", args);
147
+ case "gsd_generate_milestone_id": return this.invokeRegisteredWorkflowTool("gsd_generate_milestone_id", args);
148
+ case "gsd_plan_milestone": return this.invokeRegisteredWorkflowTool("gsd_plan_milestone", args);
149
+ case "gsd_plan_slice": return this.invokeRegisteredWorkflowTool("gsd_plan_slice", args);
150
+ case "gsd_plan_task": return this.invokeRegisteredWorkflowTool("gsd_plan_task", args);
151
+ case "gsd_task_plan": return this.invokeRegisteredWorkflowTool("gsd_task_plan", args);
152
+ case "gsd_replan_slice": return this.invokeRegisteredWorkflowTool("gsd_replan_slice", args);
153
+ case "gsd_slice_replan": return this.invokeRegisteredWorkflowTool("gsd_slice_replan", args);
154
+ case "gsd_slice_complete": return this.invokeRegisteredWorkflowTool("gsd_slice_complete", args);
155
+ case "gsd_complete_slice": return this.invokeRegisteredWorkflowTool("gsd_complete_slice", args);
156
+ case "gsd_skip_slice": return this.invokeRegisteredWorkflowTool("gsd_skip_slice", args);
157
+ case "gsd_complete_milestone": return this.invokeRegisteredWorkflowTool("gsd_complete_milestone", args);
158
+ case "gsd_milestone_complete": return this.invokeRegisteredWorkflowTool("gsd_milestone_complete", args);
159
+ case "gsd_validate_milestone": return this.invokeRegisteredWorkflowTool("gsd_validate_milestone", args);
160
+ case "gsd_milestone_validate": return this.invokeRegisteredWorkflowTool("gsd_milestone_validate", args);
161
+ case "gsd_reassess_roadmap": return this.invokeRegisteredWorkflowTool("gsd_reassess_roadmap", args);
162
+ case "gsd_roadmap_reassess": return this.invokeRegisteredWorkflowTool("gsd_roadmap_reassess", args);
163
+ case "gsd_save_gate_result": return this.invokeRegisteredWorkflowTool("gsd_save_gate_result", args);
164
+ case "gsd_summary_save": return this.invokeRegisteredWorkflowTool("gsd_summary_save", args);
165
+ case "gsd_task_complete": return this.invokeRegisteredWorkflowTool("gsd_task_complete", args);
166
+ case "gsd_complete_task": return this.invokeRegisteredWorkflowTool("gsd_complete_task", args);
167
+ case "gsd_task_reopen": return this.invokeRegisteredWorkflowTool("gsd_task_reopen", args);
168
+ case "gsd_reopen_task": return this.invokeRegisteredWorkflowTool("gsd_reopen_task", args);
169
+ case "gsd_slice_reopen": return this.invokeRegisteredWorkflowTool("gsd_slice_reopen", args);
170
+ case "gsd_reopen_slice": return this.invokeRegisteredWorkflowTool("gsd_reopen_slice", args);
171
+ case "gsd_milestone_reopen": return this.invokeRegisteredWorkflowTool("gsd_milestone_reopen", args);
172
+ case "gsd_reopen_milestone": return this.invokeRegisteredWorkflowTool("gsd_reopen_milestone", args);
173
+ case "gsd_milestone_status": return this.invokeRegisteredWorkflowTool("gsd_milestone_status", args);
174
+ case "gsd_journal_query": return this.invokeRegisteredWorkflowTool("gsd_journal_query", args);
175
+ case "gsd_exec": return this.invokeRegisteredWorkflowTool("gsd_exec", args);
176
+ case "gsd_exec_search": return this.invokeRegisteredWorkflowTool("gsd_exec_search", args);
177
+ case "gsd_resume": return this.invokeRegisteredWorkflowTool("gsd_resume", args);
178
+ case "gsd_capture_thought": return this.invokeRegisteredWorkflowTool("gsd_capture_thought", args);
179
+ case "gsd_memory_query": return this.invokeRegisteredWorkflowTool("gsd_memory_query", args);
180
+ case "gsd_memory_graph": return this.invokeRegisteredWorkflowTool("gsd_memory_graph", args);
181
+ default:
182
+ if (WORKFLOW_TOOL_NAME_SET.has(toolName)) {
183
+ throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);
184
+ }
185
+ return undefined;
186
+ }
187
+ }
188
+ invokeRegisteredWorkflowTool(toolName, args) {
189
+ const workflow = this.workflowHandlers.get(toolName);
190
+ if (!workflow)
191
+ throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);
192
+ return workflow(args);
193
+ }
194
+ async executeGraph(args) {
195
+ const projectDir = await this.requiredProjectDir(args);
196
+ const mode = args.mode;
197
+ switch (mode) {
198
+ case "build": {
199
+ const gsdRoot = resolveGsdRoot(projectDir);
200
+ if (args.snapshot === true) {
201
+ await writeSnapshot(gsdRoot).catch(() => { });
202
+ }
203
+ const graph = await buildGraph(projectDir);
204
+ await writeGraph(gsdRoot, graph);
205
+ return {
206
+ built: true,
207
+ nodeCount: graph.nodes.length,
208
+ edgeCount: graph.edges.length,
209
+ builtAt: graph.builtAt,
210
+ };
211
+ }
212
+ case "query":
213
+ return graphQuery(projectDir, typeof args.term === "string" ? args.term : "", typeof args.budget === "number" ? args.budget : undefined);
214
+ case "status":
215
+ return graphStatus(projectDir);
216
+ case "diff":
217
+ return graphDiff(projectDir);
218
+ default:
219
+ throw new Error("gsd_graph mode must be one of: build, query, status, diff");
220
+ }
221
+ }
222
+ }
223
+ async function readProjectState(projectDir, query) {
224
+ const resolvedProjectDir = resolve(projectDir);
225
+ const gsdDir = join(resolvedProjectDir, ".gsd");
226
+ const category = normalizeQuery(query);
227
+ const wanted = new Set(QUERY_FIELDS[category]);
228
+ const result = {
229
+ projectDir: resolvedProjectDir,
230
+ query: category,
231
+ };
232
+ if (wanted.has("state")) {
233
+ result.state = await readTextOrNull(join(gsdDir, "STATE.md"));
234
+ }
235
+ if (wanted.has("project")) {
236
+ result.project = await readTextOrNull(join(gsdDir, "PROJECT.md"));
237
+ }
238
+ if (wanted.has("requirements")) {
239
+ result.requirements = await readTextOrNull(join(gsdDir, "REQUIREMENTS.md"));
240
+ }
241
+ if (wanted.has("milestones")) {
242
+ result.milestones = await readMilestones(gsdDir);
243
+ }
244
+ return result;
245
+ }
246
+ function normalizeQuery(query) {
247
+ const key = (query ?? "all").trim().toLowerCase();
248
+ return Object.hasOwn(QUERY_FIELDS, key) ? key : "all";
249
+ }
250
+ async function readTextOrNull(path) {
251
+ try {
252
+ return await readFile(path, "utf8");
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }
258
+ async function readMilestones(gsdDir) {
259
+ try {
260
+ const entries = await readdir(join(gsdDir, "milestones"), { withFileTypes: true });
261
+ const milestones = [];
262
+ for (const entry of entries) {
263
+ if (!entry.isDirectory())
264
+ continue;
265
+ milestones.push({
266
+ id: entry.name,
267
+ hasRoadmap: await milestoneFileExists(gsdDir, entry.name, "ROADMAP"),
268
+ hasSummary: await milestoneFileExists(gsdDir, entry.name, "SUMMARY"),
269
+ });
270
+ }
271
+ return milestones;
272
+ }
273
+ catch {
274
+ return [];
275
+ }
276
+ }
277
+ async function milestoneFileExists(gsdDir, milestoneId, suffix) {
278
+ const milestoneDir = join(gsdDir, "milestones", milestoneId);
279
+ return fileExists(join(milestoneDir, `${milestoneId}-${suffix}.md`))
280
+ || fileExists(join(milestoneDir, `${basename(milestoneDir)}-${suffix}.md`))
281
+ || fileExists(join(milestoneDir, `${suffix}.md`));
282
+ }
283
+ async function fileExists(path) {
284
+ try {
285
+ await readFile(path);
286
+ return true;
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ }
292
+ function gitRemote(projectPath) {
293
+ try {
294
+ return execFileSync("git", ["remote", "get-url", "origin"], {
295
+ cwd: projectPath,
296
+ encoding: "utf8",
297
+ stdio: ["ignore", "pipe", "ignore"],
298
+ }).trim() || undefined;
299
+ }
300
+ catch {
301
+ return undefined;
302
+ }
303
+ }
304
+ function identityFor(projectPath, remote) {
305
+ return createHash("sha256").update(remote || `${basename(projectPath)}:${projectPath}`).digest("hex").slice(0, 12);
306
+ }
307
+ //# sourceMappingURL=local-tool-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-tool-executor.js","sourceRoot":"","sources":["../src/local-tool-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { basename, join, resolve } from \"node:path\";\nimport {\n readCaptures,\n readHistory,\n readKnowledge,\n readProgress,\n readRoadmap,\n buildGraph,\n graphDiff,\n graphQuery,\n graphStatus,\n registerWorkflowTools,\n resolveGsdRoot,\n runDoctorLite,\n writeGraph,\n writeSnapshot,\n WORKFLOW_TOOL_NAMES,\n} from \"@opengsd/mcp-server\";\nimport type { SessionManager } from \"./session-manager.js\";\nimport type { ProjectInfo } from \"./types.js\";\n\ntype ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;\nconst WORKFLOW_TOOL_NAME_SET = new Set<string>(WORKFLOW_TOOL_NAMES);\nconst QUERY_FIELDS = {\n all: [\"state\", \"project\", \"requirements\", \"milestones\"],\n state: [\"state\"],\n status: [\"state\"],\n project: [\"project\"],\n requirements: [\"requirements\"],\n milestones: [\"milestones\"],\n} as const;\n\ntype QueryCategory = keyof typeof QUERY_FIELDS;\ntype ProjectStateField = (typeof QUERY_FIELDS)[QueryCategory][number];\n\nexport class LocalToolExecutor {\n private readonly workflowHandlers = new Map<string, ToolHandler>();\n\n constructor(\n private readonly sessionManager: SessionManager,\n private readonly scanProjects: () => Promise<ProjectInfo[]>,\n ) {\n registerWorkflowTools({\n tool: (name: string, _description: string, _params: Record<string, unknown>, handler: ToolHandler) => {\n if (!WORKFLOW_TOOL_NAME_SET.has(name)) return;\n this.workflowHandlers.set(name, handler);\n },\n });\n }\n\n async execute(toolName: string, rawArgs: Record<string, unknown>, projectAlias?: string): Promise<unknown> {\n const args = { ...rawArgs };\n if (projectAlias) {\n args.projectDir = await this.resolveProjectPath(projectAlias);\n }\n\n const workflowResult = this.executeWorkflowTool(toolName, args);\n if (workflowResult) return workflowResult;\n\n switch (toolName) {\n case \"gsd_execute\": {\n const projectDir = await this.requiredProjectDir(args);\n const sessionId = await this.sessionManager.startSession({\n projectDir,\n command: typeof args.command === \"string\" ? args.command : undefined,\n model: typeof args.model === \"string\" ? args.model : undefined,\n bare: typeof args.bare === \"boolean\" ? args.bare : undefined,\n });\n return { content: [{ type: \"text\", text: JSON.stringify({ sessionId, status: \"started\" }, null, 2) }] };\n }\n case \"gsd_status\": {\n const session = this.sessionManager.getSession(String(args.sessionId ?? \"\"));\n if (!session) throw new Error(`Session not found: ${String(args.sessionId ?? \"\")}`);\n const toolCallCount = session.events.filter((event) => {\n const type = (event as Record<string, unknown>).type;\n return type === \"tool_use\" || type === \"tool_execution_start\";\n }).length;\n return { content: [{ type: \"text\", text: JSON.stringify({\n status: session.status,\n progress: {\n eventCount: session.events.length,\n toolCalls: toolCallCount,\n },\n recentEvents: session.events.slice(-10),\n pendingBlocker: session.pendingBlocker\n ? {\n id: session.pendingBlocker.id,\n method: session.pendingBlocker.method,\n message: session.pendingBlocker.message,\n }\n : null,\n cost: session.cost,\n durationMs: Date.now() - session.startTime,\n }, null, 2) }] };\n }\n case \"gsd_result\":\n return { content: [{ type: \"text\", text: JSON.stringify(this.sessionManager.getResult(String(args.sessionId ?? \"\")), null, 2) }] };\n case \"gsd_cancel\":\n if (typeof args.sessionId === \"string\") await this.sessionManager.cancelSession(args.sessionId);\n else await this.sessionManager.cancelSessionByDir(await this.requiredProjectDir(args));\n return { content: [{ type: \"text\", text: JSON.stringify({ cancelled: true }, null, 2) }] };\n case \"gsd_resolve_blocker\": {\n const sessionId = String(args.sessionId ?? \"\");\n const response = String(args.response ?? \"\");\n await this.sessionManager.resolveBlocker(sessionId, response);\n return { content: [{ type: \"text\", text: JSON.stringify({ resolved: true }, null, 2) }] };\n }\n case \"gsd_query\": {\n const projectDir = await this.requiredProjectDir(args);\n const query = typeof args.query === \"string\" ? args.query : undefined;\n return { content: [{ type: \"text\", text: JSON.stringify(await readProjectState(projectDir, query), null, 2) }] };\n }\n case \"gsd_progress\":\n return { content: [{ type: \"text\", text: JSON.stringify(await readProgress(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_roadmap\":\n return { content: [{ type: \"text\", text: JSON.stringify(await readRoadmap(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_history\":\n return { content: [{ type: \"text\", text: JSON.stringify(await readHistory(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_doctor\":\n return { content: [{ type: \"text\", text: JSON.stringify(await runDoctorLite(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_captures\":\n return { content: [{ type: \"text\", text: JSON.stringify(await readCaptures(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_knowledge\":\n return { content: [{ type: \"text\", text: JSON.stringify(await readKnowledge(await this.requiredProjectDir(args)), null, 2) }] };\n case \"gsd_graph\":\n return { content: [{ type: \"text\", text: JSON.stringify(await this.executeGraph(args), null, 2) }] };\n default:\n throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);\n }\n }\n\n async advertisedProjects(): Promise<Array<{\n alias: string;\n path: string;\n repoIdentity: string;\n remoteLabel?: string;\n markers: string[];\n }>> {\n const projects = await this.scanProjects();\n return projects.map((project) => {\n const remoteLabel = gitRemote(project.path);\n return {\n alias: project.name,\n path: project.path,\n repoIdentity: identityFor(project.path, remoteLabel),\n ...(remoteLabel ? { remoteLabel } : {}),\n markers: project.markers,\n };\n });\n }\n\n private async requiredProjectDir(args: Record<string, unknown>): Promise<string> {\n const value = args.projectDir;\n if (typeof value === \"string\" && value.trim()) return this.resolveProjectPath(value);\n throw new Error(\"projectDir or projectAlias is required\");\n }\n\n private async resolveProjectPath(aliasOrPath: string): Promise<string> {\n const projects = await this.scanProjects();\n const match = projects.find((project) => project.name === aliasOrPath || project.path === aliasOrPath);\n if (!match) throw new Error(`Project is not advertised by the Local GSD Runtime: ${aliasOrPath}`);\n return match.path;\n }\n\n private executeWorkflowTool(toolName: string, args: Record<string, unknown>): Promise<unknown> | undefined {\n switch (toolName) {\n case \"gsd_decision_save\": return this.invokeRegisteredWorkflowTool(\"gsd_decision_save\", args);\n case \"gsd_save_decision\": return this.invokeRegisteredWorkflowTool(\"gsd_save_decision\", args);\n case \"gsd_requirement_update\": return this.invokeRegisteredWorkflowTool(\"gsd_requirement_update\", args);\n case \"gsd_update_requirement\": return this.invokeRegisteredWorkflowTool(\"gsd_update_requirement\", args);\n case \"gsd_requirement_save\": return this.invokeRegisteredWorkflowTool(\"gsd_requirement_save\", args);\n case \"gsd_save_requirement\": return this.invokeRegisteredWorkflowTool(\"gsd_save_requirement\", args);\n case \"gsd_milestone_generate_id\": return this.invokeRegisteredWorkflowTool(\"gsd_milestone_generate_id\", args);\n case \"gsd_generate_milestone_id\": return this.invokeRegisteredWorkflowTool(\"gsd_generate_milestone_id\", args);\n case \"gsd_plan_milestone\": return this.invokeRegisteredWorkflowTool(\"gsd_plan_milestone\", args);\n case \"gsd_plan_slice\": return this.invokeRegisteredWorkflowTool(\"gsd_plan_slice\", args);\n case \"gsd_plan_task\": return this.invokeRegisteredWorkflowTool(\"gsd_plan_task\", args);\n case \"gsd_task_plan\": return this.invokeRegisteredWorkflowTool(\"gsd_task_plan\", args);\n case \"gsd_replan_slice\": return this.invokeRegisteredWorkflowTool(\"gsd_replan_slice\", args);\n case \"gsd_slice_replan\": return this.invokeRegisteredWorkflowTool(\"gsd_slice_replan\", args);\n case \"gsd_slice_complete\": return this.invokeRegisteredWorkflowTool(\"gsd_slice_complete\", args);\n case \"gsd_complete_slice\": return this.invokeRegisteredWorkflowTool(\"gsd_complete_slice\", args);\n case \"gsd_skip_slice\": return this.invokeRegisteredWorkflowTool(\"gsd_skip_slice\", args);\n case \"gsd_complete_milestone\": return this.invokeRegisteredWorkflowTool(\"gsd_complete_milestone\", args);\n case \"gsd_milestone_complete\": return this.invokeRegisteredWorkflowTool(\"gsd_milestone_complete\", args);\n case \"gsd_validate_milestone\": return this.invokeRegisteredWorkflowTool(\"gsd_validate_milestone\", args);\n case \"gsd_milestone_validate\": return this.invokeRegisteredWorkflowTool(\"gsd_milestone_validate\", args);\n case \"gsd_reassess_roadmap\": return this.invokeRegisteredWorkflowTool(\"gsd_reassess_roadmap\", args);\n case \"gsd_roadmap_reassess\": return this.invokeRegisteredWorkflowTool(\"gsd_roadmap_reassess\", args);\n case \"gsd_save_gate_result\": return this.invokeRegisteredWorkflowTool(\"gsd_save_gate_result\", args);\n case \"gsd_summary_save\": return this.invokeRegisteredWorkflowTool(\"gsd_summary_save\", args);\n case \"gsd_task_complete\": return this.invokeRegisteredWorkflowTool(\"gsd_task_complete\", args);\n case \"gsd_complete_task\": return this.invokeRegisteredWorkflowTool(\"gsd_complete_task\", args);\n case \"gsd_task_reopen\": return this.invokeRegisteredWorkflowTool(\"gsd_task_reopen\", args);\n case \"gsd_reopen_task\": return this.invokeRegisteredWorkflowTool(\"gsd_reopen_task\", args);\n case \"gsd_slice_reopen\": return this.invokeRegisteredWorkflowTool(\"gsd_slice_reopen\", args);\n case \"gsd_reopen_slice\": return this.invokeRegisteredWorkflowTool(\"gsd_reopen_slice\", args);\n case \"gsd_milestone_reopen\": return this.invokeRegisteredWorkflowTool(\"gsd_milestone_reopen\", args);\n case \"gsd_reopen_milestone\": return this.invokeRegisteredWorkflowTool(\"gsd_reopen_milestone\", args);\n case \"gsd_milestone_status\": return this.invokeRegisteredWorkflowTool(\"gsd_milestone_status\", args);\n case \"gsd_journal_query\": return this.invokeRegisteredWorkflowTool(\"gsd_journal_query\", args);\n case \"gsd_exec\": return this.invokeRegisteredWorkflowTool(\"gsd_exec\", args);\n case \"gsd_exec_search\": return this.invokeRegisteredWorkflowTool(\"gsd_exec_search\", args);\n case \"gsd_resume\": return this.invokeRegisteredWorkflowTool(\"gsd_resume\", args);\n case \"gsd_capture_thought\": return this.invokeRegisteredWorkflowTool(\"gsd_capture_thought\", args);\n case \"gsd_memory_query\": return this.invokeRegisteredWorkflowTool(\"gsd_memory_query\", args);\n case \"gsd_memory_graph\": return this.invokeRegisteredWorkflowTool(\"gsd_memory_graph\", args);\n default:\n if (WORKFLOW_TOOL_NAME_SET.has(toolName)) {\n throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);\n }\n return undefined;\n }\n }\n\n private invokeRegisteredWorkflowTool(toolName: string, args: Record<string, unknown>): Promise<unknown> {\n const workflow = this.workflowHandlers.get(toolName);\n if (!workflow) throw new Error(`Unsupported forwarded GSD MCP tool: ${toolName}`);\n return workflow(args);\n }\n\n private async executeGraph(args: Record<string, unknown>): Promise<unknown> {\n const projectDir = await this.requiredProjectDir(args);\n const mode = args.mode;\n switch (mode) {\n case \"build\": {\n const gsdRoot = resolveGsdRoot(projectDir);\n if (args.snapshot === true) {\n await writeSnapshot(gsdRoot).catch(() => { /* best-effort */ });\n }\n const graph = await buildGraph(projectDir);\n await writeGraph(gsdRoot, graph);\n return {\n built: true,\n nodeCount: graph.nodes.length,\n edgeCount: graph.edges.length,\n builtAt: graph.builtAt,\n };\n }\n case \"query\":\n return graphQuery(\n projectDir,\n typeof args.term === \"string\" ? args.term : \"\",\n typeof args.budget === \"number\" ? args.budget : undefined,\n );\n case \"status\":\n return graphStatus(projectDir);\n case \"diff\":\n return graphDiff(projectDir);\n default:\n throw new Error(\"gsd_graph mode must be one of: build, query, status, diff\");\n }\n }\n}\n\nasync function readProjectState(projectDir: string, query: string | undefined): Promise<Record<string, unknown>> {\n const resolvedProjectDir = resolve(projectDir);\n const gsdDir = join(resolvedProjectDir, \".gsd\");\n const category = normalizeQuery(query);\n const wanted = new Set<ProjectStateField>(QUERY_FIELDS[category]);\n\n const result: Record<string, unknown> = {\n projectDir: resolvedProjectDir,\n query: category,\n };\n\n if (wanted.has(\"state\")) {\n result.state = await readTextOrNull(join(gsdDir, \"STATE.md\"));\n }\n if (wanted.has(\"project\")) {\n result.project = await readTextOrNull(join(gsdDir, \"PROJECT.md\"));\n }\n if (wanted.has(\"requirements\")) {\n result.requirements = await readTextOrNull(join(gsdDir, \"REQUIREMENTS.md\"));\n }\n if (wanted.has(\"milestones\")) {\n result.milestones = await readMilestones(gsdDir);\n }\n\n return result;\n}\n\nfunction normalizeQuery(query: string | undefined): QueryCategory {\n const key = (query ?? \"all\").trim().toLowerCase();\n return Object.hasOwn(QUERY_FIELDS, key) ? key as QueryCategory : \"all\";\n}\n\nasync function readTextOrNull(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function readMilestones(gsdDir: string): Promise<Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }>> {\n try {\n const entries = await readdir(join(gsdDir, \"milestones\"), { withFileTypes: true });\n const milestones: Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n milestones.push({\n id: entry.name,\n hasRoadmap: await milestoneFileExists(gsdDir, entry.name, \"ROADMAP\"),\n hasSummary: await milestoneFileExists(gsdDir, entry.name, \"SUMMARY\"),\n });\n }\n return milestones;\n } catch {\n return [];\n }\n}\n\nasync function milestoneFileExists(gsdDir: string, milestoneId: string, suffix: string): Promise<boolean> {\n const milestoneDir = join(gsdDir, \"milestones\", milestoneId);\n return fileExists(join(milestoneDir, `${milestoneId}-${suffix}.md`))\n || fileExists(join(milestoneDir, `${basename(milestoneDir)}-${suffix}.md`))\n || fileExists(join(milestoneDir, `${suffix}.md`));\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await readFile(path);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction gitRemote(projectPath: string): string | undefined {\n try {\n return execFileSync(\"git\", [\"remote\", \"get-url\", \"origin\"], {\n cwd: projectPath,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction identityFor(projectPath: string, remote?: string): string {\n return createHash(\"sha256\").update(remote || `${basename(projectPath)}:${projectPath}`).digest(\"hex\").slice(0, 12);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=local-tool-executor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-tool-executor.test.d.ts","sourceRoot":"","sources":["../src/local-tool-executor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,111 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { LocalToolExecutor } from "./local-tool-executor.js";
7
+ test("local tool executor rejects unsupported user-controlled tool names", async () => {
8
+ const executor = new LocalToolExecutor({}, async () => []);
9
+ await assert.rejects(executor.execute("constructor", {}), /Unsupported forwarded GSD MCP tool: constructor/);
10
+ });
11
+ test("local tool executor rejects unadvertised project paths", async () => {
12
+ const executor = new LocalToolExecutor({}, async () => []);
13
+ await assert.rejects(executor.execute("gsd_progress", { projectDir: "/tmp/not-advertised" }), /Project is not advertised by the Local GSD Runtime: \/tmp\/not-advertised/);
14
+ });
15
+ test("local tool executor resolves project aliases from scanned projects", async () => {
16
+ const project = {
17
+ name: "allowed-project",
18
+ path: "/tmp/allowed-project",
19
+ markers: ["git"],
20
+ lastModified: Date.now(),
21
+ };
22
+ let startedProjectDir;
23
+ const executor = new LocalToolExecutor({
24
+ startSession: async ({ projectDir }) => {
25
+ startedProjectDir = projectDir;
26
+ return "session-1";
27
+ },
28
+ }, async () => [project]);
29
+ await executor.execute("gsd_execute", {
30
+ projectDir: "/tmp/not-advertised",
31
+ }, "allowed-project");
32
+ assert.equal(startedProjectDir, project.path);
33
+ });
34
+ test("local tool executor forwards cloud blocker resolution", async () => {
35
+ let resolved;
36
+ const executor = new LocalToolExecutor({
37
+ resolveBlocker: async (sessionId, response) => {
38
+ resolved = { sessionId, response };
39
+ },
40
+ }, async () => []);
41
+ const result = await executor.execute("gsd_resolve_blocker", {
42
+ sessionId: "session-1",
43
+ response: "continue",
44
+ });
45
+ assert.deepEqual(resolved, { sessionId: "session-1", response: "continue" });
46
+ assert.deepEqual(result, { content: [{ type: "text", text: JSON.stringify({ resolved: true }, null, 2) }] });
47
+ });
48
+ test("local tool executor returns status payload with progress counters", async () => {
49
+ const startedAt = Date.now() - 1234;
50
+ const executor = new LocalToolExecutor({
51
+ getSession: (sessionId) => sessionId === "session-1"
52
+ ? {
53
+ sessionId,
54
+ status: "running",
55
+ events: [
56
+ { type: "message" },
57
+ { type: "tool_use" },
58
+ { type: "tool_execution_start" },
59
+ ],
60
+ pendingBlocker: null,
61
+ cost: { totalCost: 0, tokens: { input: 0, output: 0 } },
62
+ startTime: startedAt,
63
+ }
64
+ : undefined,
65
+ }, async () => []);
66
+ const result = await executor.execute("gsd_status", { sessionId: "session-1" });
67
+ const text = result.content[0].text;
68
+ const status = JSON.parse(text);
69
+ assert.equal(status.status, "running");
70
+ assert.deepEqual(status.progress, { eventCount: 3, toolCalls: 2 });
71
+ });
72
+ test("local tool executor returns gsd_query project-state payload", async () => {
73
+ const projectDir = mkdtempSync(join(tmpdir(), "gsd-query-project-"));
74
+ const gsdDir = join(projectDir, ".gsd");
75
+ mkdirSync(join(gsdDir, "milestones", "M001"), { recursive: true });
76
+ writeFileSync(join(gsdDir, "STATE.md"), "state text");
77
+ writeFileSync(join(gsdDir, "PROJECT.md"), "project text");
78
+ writeFileSync(join(gsdDir, "REQUIREMENTS.md"), "requirements text");
79
+ writeFileSync(join(gsdDir, "milestones", "M001", "M001-ROADMAP.md"), "roadmap text");
80
+ const project = {
81
+ name: "query-project",
82
+ path: projectDir,
83
+ markers: ["git"],
84
+ lastModified: Date.now(),
85
+ };
86
+ const executor = new LocalToolExecutor({}, async () => [project]);
87
+ const result = await executor.execute("gsd_query", { projectDir });
88
+ const text = result.content[0].text;
89
+ const query = JSON.parse(text);
90
+ assert.equal(query.state, "state text");
91
+ assert.equal(query.project, "project text");
92
+ assert.equal(query.requirements, "requirements text");
93
+ assert.deepEqual(query.milestones, [{ id: "M001", hasRoadmap: true, hasSummary: false }]);
94
+ });
95
+ test("local tool executor forwards unified graph tool modes", async () => {
96
+ const projectDir = mkdtempSync(join(tmpdir(), "gsd-graph-project-"));
97
+ const project = {
98
+ name: "graph-project",
99
+ path: projectDir,
100
+ markers: ["git"],
101
+ lastModified: Date.now(),
102
+ };
103
+ const executor = new LocalToolExecutor({}, async () => [project]);
104
+ const result = await executor.execute("gsd_graph", {
105
+ projectDir,
106
+ mode: "status",
107
+ });
108
+ const text = result.content[0].text;
109
+ assert.equal(typeof JSON.parse(text), "object");
110
+ });
111
+ //# sourceMappingURL=local-tool-executor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-tool-executor.test.js","sourceRoot":"","sources":["../src/local-tool-executor.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { mkdirSync, mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { test } from \"node:test\";\nimport { LocalToolExecutor } from \"./local-tool-executor.js\";\nimport type { SessionManager } from \"./session-manager.js\";\nimport type { ProjectInfo } from \"./types.js\";\n\ntest(\"local tool executor rejects unsupported user-controlled tool names\", async () => {\n const executor = new LocalToolExecutor({} as SessionManager, async () => []);\n\n await assert.rejects(\n executor.execute(\"constructor\", {}),\n /Unsupported forwarded GSD MCP tool: constructor/,\n );\n});\n\ntest(\"local tool executor rejects unadvertised project paths\", async () => {\n const executor = new LocalToolExecutor({} as SessionManager, async () => []);\n\n await assert.rejects(\n executor.execute(\"gsd_progress\", { projectDir: \"/tmp/not-advertised\" }),\n /Project is not advertised by the Local GSD Runtime: \\/tmp\\/not-advertised/,\n );\n});\n\ntest(\"local tool executor resolves project aliases from scanned projects\", async () => {\n const project: ProjectInfo = {\n name: \"allowed-project\",\n path: \"/tmp/allowed-project\",\n markers: [\"git\"],\n lastModified: Date.now(),\n };\n let startedProjectDir: string | undefined;\n const executor = new LocalToolExecutor({\n startSession: async ({ projectDir }: { projectDir: string }) => {\n startedProjectDir = projectDir;\n return \"session-1\";\n },\n } as SessionManager, async () => [project]);\n\n await executor.execute(\"gsd_execute\", {\n projectDir: \"/tmp/not-advertised\",\n }, \"allowed-project\");\n\n assert.equal(startedProjectDir, project.path);\n});\n\ntest(\"local tool executor forwards cloud blocker resolution\", async () => {\n let resolved: { sessionId: string; response: string } | undefined;\n const executor = new LocalToolExecutor({\n resolveBlocker: async (sessionId: string, response: string) => {\n resolved = { sessionId, response };\n },\n } as SessionManager, async () => []);\n\n const result = await executor.execute(\"gsd_resolve_blocker\", {\n sessionId: \"session-1\",\n response: \"continue\",\n });\n\n assert.deepEqual(resolved, { sessionId: \"session-1\", response: \"continue\" });\n assert.deepEqual(result, { content: [{ type: \"text\", text: JSON.stringify({ resolved: true }, null, 2) }] });\n});\n\ntest(\"local tool executor returns status payload with progress counters\", async () => {\n const startedAt = Date.now() - 1234;\n const executor = new LocalToolExecutor({\n getSession: (sessionId: string) => sessionId === \"session-1\"\n ? {\n sessionId,\n status: \"running\",\n events: [\n { type: \"message\" },\n { type: \"tool_use\" },\n { type: \"tool_execution_start\" },\n ],\n pendingBlocker: null,\n cost: { totalCost: 0, tokens: { input: 0, output: 0 } },\n startTime: startedAt,\n }\n : undefined,\n } as unknown as SessionManager, async () => []);\n\n const result = await executor.execute(\"gsd_status\", { sessionId: \"session-1\" });\n const text = (result as { content: Array<{ text: string }> }).content[0]!.text;\n const status = JSON.parse(text) as { status: string; progress: { eventCount: number; toolCalls: number } };\n\n assert.equal(status.status, \"running\");\n assert.deepEqual(status.progress, { eventCount: 3, toolCalls: 2 });\n});\n\ntest(\"local tool executor returns gsd_query project-state payload\", async () => {\n const projectDir = mkdtempSync(join(tmpdir(), \"gsd-query-project-\"));\n const gsdDir = join(projectDir, \".gsd\");\n mkdirSync(join(gsdDir, \"milestones\", \"M001\"), { recursive: true });\n writeFileSync(join(gsdDir, \"STATE.md\"), \"state text\");\n writeFileSync(join(gsdDir, \"PROJECT.md\"), \"project text\");\n writeFileSync(join(gsdDir, \"REQUIREMENTS.md\"), \"requirements text\");\n writeFileSync(join(gsdDir, \"milestones\", \"M001\", \"M001-ROADMAP.md\"), \"roadmap text\");\n\n const project: ProjectInfo = {\n name: \"query-project\",\n path: projectDir,\n markers: [\"git\"],\n lastModified: Date.now(),\n };\n const executor = new LocalToolExecutor({} as SessionManager, async () => [project]);\n\n const result = await executor.execute(\"gsd_query\", { projectDir });\n const text = (result as { content: Array<{ text: string }> }).content[0]!.text;\n const query = JSON.parse(text) as {\n state: string;\n project: string;\n requirements: string;\n milestones: Array<{ id: string; hasRoadmap: boolean; hasSummary: boolean }>;\n };\n\n assert.equal(query.state, \"state text\");\n assert.equal(query.project, \"project text\");\n assert.equal(query.requirements, \"requirements text\");\n assert.deepEqual(query.milestones, [{ id: \"M001\", hasRoadmap: true, hasSummary: false }]);\n});\n\ntest(\"local tool executor forwards unified graph tool modes\", async () => {\n const projectDir = mkdtempSync(join(tmpdir(), \"gsd-graph-project-\"));\n const project: ProjectInfo = {\n name: \"graph-project\",\n path: projectDir,\n markers: [\"git\"],\n lastModified: Date.now(),\n };\n const executor = new LocalToolExecutor({} as SessionManager, async () => [project]);\n\n const result = await executor.execute(\"gsd_graph\", {\n projectDir,\n mode: \"status\",\n });\n\n const text = (result as { content: Array<{ text: string }> }).content[0]!.text;\n assert.equal(typeof JSON.parse(text), \"object\");\n});\n"]}
@@ -0,0 +1,25 @@
1
+ import type { LogLevel } from './types.js';
2
+ export interface LoggerOptions {
3
+ filePath: string;
4
+ level: LogLevel;
5
+ verbose?: boolean;
6
+ }
7
+ /**
8
+ * Structured JSON-lines file logger.
9
+ * Writes LogEntry objects one per line in append mode.
10
+ * The open write stream keeps the Node event loop alive (daemon keepalive).
11
+ */
12
+ export declare class Logger {
13
+ private readonly stream;
14
+ private readonly level;
15
+ private readonly verbose;
16
+ constructor(opts: LoggerOptions);
17
+ debug(msg: string, data?: Record<string, unknown>): void;
18
+ info(msg: string, data?: Record<string, unknown>): void;
19
+ warn(msg: string, data?: Record<string, unknown>): void;
20
+ error(msg: string, data?: Record<string, unknown>): void;
21
+ /** End the write stream. Resolves when the stream is fully flushed. */
22
+ close(): Promise<void>;
23
+ private write;
24
+ }
25
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,YAAY,CAAC;AASrD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEtB,IAAI,EAAE,aAAa;IAe/B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIvD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIvD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAIxD,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtB,OAAO,CAAC,KAAK;CAmBd"}
@@ -0,0 +1,72 @@
1
+ import { createWriteStream, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ const LEVEL_ORDER = {
4
+ debug: 0,
5
+ info: 1,
6
+ warn: 2,
7
+ error: 3,
8
+ };
9
+ /**
10
+ * Structured JSON-lines file logger.
11
+ * Writes LogEntry objects one per line in append mode.
12
+ * The open write stream keeps the Node event loop alive (daemon keepalive).
13
+ */
14
+ export class Logger {
15
+ stream;
16
+ level;
17
+ verbose;
18
+ constructor(opts) {
19
+ // Ensure parent directory exists
20
+ const dir = dirname(opts.filePath);
21
+ try {
22
+ mkdirSync(dir, { recursive: true });
23
+ }
24
+ catch (err) {
25
+ const msg = err instanceof Error ? err.message : String(err);
26
+ throw new Error(`Cannot create log directory ${dir}: ${msg}`);
27
+ }
28
+ this.stream = createWriteStream(opts.filePath, { flags: 'a' });
29
+ this.level = LEVEL_ORDER[opts.level] ?? LEVEL_ORDER.info;
30
+ this.verbose = opts.verbose ?? false;
31
+ }
32
+ debug(msg, data) {
33
+ this.write('debug', msg, data);
34
+ }
35
+ info(msg, data) {
36
+ this.write('info', msg, data);
37
+ }
38
+ warn(msg, data) {
39
+ this.write('warn', msg, data);
40
+ }
41
+ error(msg, data) {
42
+ this.write('error', msg, data);
43
+ }
44
+ /** End the write stream. Resolves when the stream is fully flushed. */
45
+ close() {
46
+ return new Promise((resolve, reject) => {
47
+ // Attach listeners BEFORE triggering end() so a synchronous error
48
+ // from end() or an immediate 'close' cannot slip past the listener.
49
+ this.stream.once('close', () => resolve());
50
+ this.stream.once('error', reject);
51
+ this.stream.end();
52
+ });
53
+ }
54
+ write(level, msg, data) {
55
+ if (LEVEL_ORDER[level] < this.level)
56
+ return;
57
+ const entry = {
58
+ ts: new Date().toISOString(),
59
+ level,
60
+ msg,
61
+ ...(data !== undefined ? { data } : {}),
62
+ };
63
+ const line = JSON.stringify(entry) + '\n';
64
+ this.stream.write(line);
65
+ if (this.verbose) {
66
+ const prefix = `[${entry.ts}] ${level.toUpperCase()}`;
67
+ const suffix = data ? ` ${JSON.stringify(data)}` : '';
68
+ process.stderr.write(`${prefix}: ${msg}${suffix}\n`);
69
+ }
70
+ }
71
+ }
72
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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","sourcesContent":["import { createWriteStream, mkdirSync, type WriteStream } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { LogLevel, LogEntry } from './types.js';\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport interface LoggerOptions {\n filePath: string;\n level: LogLevel;\n verbose?: boolean;\n}\n\n/**\n * Structured JSON-lines file logger.\n * Writes LogEntry objects one per line in append mode.\n * The open write stream keeps the Node event loop alive (daemon keepalive).\n */\nexport class Logger {\n private readonly stream: WriteStream;\n private readonly level: number;\n private readonly verbose: boolean;\n\n constructor(opts: LoggerOptions) {\n // Ensure parent directory exists\n const dir = dirname(opts.filePath);\n try {\n mkdirSync(dir, { recursive: true });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Cannot create log directory ${dir}: ${msg}`);\n }\n\n this.stream = createWriteStream(opts.filePath, { flags: 'a' });\n this.level = LEVEL_ORDER[opts.level] ?? LEVEL_ORDER.info;\n this.verbose = opts.verbose ?? false;\n }\n\n debug(msg: string, data?: Record<string, unknown>): void {\n this.write('debug', msg, data);\n }\n\n info(msg: string, data?: Record<string, unknown>): void {\n this.write('info', msg, data);\n }\n\n warn(msg: string, data?: Record<string, unknown>): void {\n this.write('warn', msg, data);\n }\n\n error(msg: string, data?: Record<string, unknown>): void {\n this.write('error', msg, data);\n }\n\n /** End the write stream. Resolves when the stream is fully flushed. */\n close(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // Attach listeners BEFORE triggering end() so a synchronous error\n // from end() or an immediate 'close' cannot slip past the listener.\n this.stream.once('close', () => resolve());\n this.stream.once('error', reject);\n this.stream.end();\n });\n }\n\n private write(level: LogLevel, msg: string, data?: Record<string, unknown>): void {\n if (LEVEL_ORDER[level] < this.level) return;\n\n const entry: LogEntry = {\n ts: new Date().toISOString(),\n level,\n msg,\n ...(data !== undefined ? { data } : {}),\n };\n\n const line = JSON.stringify(entry) + '\\n';\n this.stream.write(line);\n\n if (this.verbose) {\n const prefix = `[${entry.ts}] ${level.toUpperCase()}`;\n const suffix = data ? ` ${JSON.stringify(data)}` : '';\n process.stderr.write(`${prefix}: ${msg}${suffix}\\n`);\n }\n }\n}\n"]}