@ilya-lesikov/pi-pi 0.1.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 (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,520 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { delimiter, join } from "node:path";
6
+ import { getPlannotatorDataDir } from "./data-dir";
7
+ import type {
8
+ SemanticDiffAvailability,
9
+ SemanticDiffBinaryChange,
10
+ SemanticDiffChange,
11
+ SemanticDiffResponse,
12
+ SemanticDiffSummary,
13
+ } from "./semantic-diff-types";
14
+
15
+ export const PLANNOTATOR_SEM_VERSION = "v0.8.0";
16
+
17
+ const SEM_TIMEOUT_MS = 20_000;
18
+ const SEM_VERSION_TIMEOUT_MS = 3_000;
19
+ export interface CommandResult {
20
+ stdout: string;
21
+ stderr: string;
22
+ exitCode: number;
23
+ error?: string;
24
+ timedOut?: boolean;
25
+ }
26
+
27
+ export interface SemanticDiffRuntime {
28
+ runCommand: (
29
+ command: string,
30
+ args: string[],
31
+ options?: { cwd?: string; input?: string; timeoutMs?: number },
32
+ ) => Promise<CommandResult>;
33
+ fileExists: (path: string) => boolean;
34
+ env: Record<string, string | undefined>;
35
+ cwd: string;
36
+ dataDir: string;
37
+ pathDelimiter: string;
38
+ platform: NodeJS.Platform;
39
+ }
40
+
41
+ interface SemCandidate {
42
+ command: string;
43
+ source: string;
44
+ explicit: boolean;
45
+ }
46
+
47
+ export interface ResolvedSem {
48
+ command: string;
49
+ source: string;
50
+ version: string;
51
+ }
52
+
53
+ type SemResolveFailure = Exclude<SemanticDiffResponse, { status: "ok" }>;
54
+
55
+ function defaultRunCommand(
56
+ command: string,
57
+ args: string[],
58
+ options: { cwd?: string; input?: string; timeoutMs?: number } = {},
59
+ ): Promise<CommandResult> {
60
+ return new Promise((resolveResult) => {
61
+ let settled = false;
62
+ let timer: ReturnType<typeof setTimeout> | undefined;
63
+ let proc: ReturnType<typeof spawn>;
64
+
65
+ try {
66
+ proc = spawn(command, args, {
67
+ cwd: options.cwd,
68
+ stdio: ["pipe", "pipe", "pipe"],
69
+ });
70
+ } catch (error) {
71
+ resolveResult({
72
+ stdout: "",
73
+ stderr: "",
74
+ exitCode: 1,
75
+ error: error instanceof Error ? error.message : String(error),
76
+ });
77
+ return;
78
+ }
79
+
80
+ const stdoutChunks: Buffer[] = [];
81
+ const stderrChunks: Buffer[] = [];
82
+ let stdinError: string | undefined;
83
+
84
+ const finish = (result: CommandResult) => {
85
+ if (settled) return;
86
+ settled = true;
87
+ if (timer) clearTimeout(timer);
88
+ resolveResult(result);
89
+ };
90
+
91
+ if (options.timeoutMs) {
92
+ timer = setTimeout(() => {
93
+ try {
94
+ proc.kill();
95
+ } catch {
96
+ // Ignore kill failures; process close/error will settle if needed.
97
+ }
98
+ finish({
99
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
100
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
101
+ exitCode: 1,
102
+ error: `command timed out after ${options.timeoutMs}ms`,
103
+ timedOut: true,
104
+ });
105
+ }, options.timeoutMs);
106
+ }
107
+
108
+ proc.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk));
109
+ proc.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
110
+ proc.on("error", (error) => {
111
+ finish({
112
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
113
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
114
+ exitCode: 1,
115
+ error: error.message,
116
+ });
117
+ });
118
+ proc.on("close", (code) => {
119
+ finish({
120
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
121
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
122
+ exitCode: code ?? 1,
123
+ ...(stdinError && { error: stdinError }),
124
+ });
125
+ });
126
+
127
+ proc.stdin?.on("error", (error) => {
128
+ stdinError = error.message;
129
+ });
130
+
131
+ try {
132
+ if (options.input !== undefined) {
133
+ proc.stdin?.write(options.input);
134
+ }
135
+ proc.stdin?.end();
136
+ } catch (error) {
137
+ stdinError = error instanceof Error ? error.message : String(error);
138
+ }
139
+ });
140
+ }
141
+
142
+ export function createDefaultSemanticDiffRuntime(): SemanticDiffRuntime {
143
+ return {
144
+ runCommand: defaultRunCommand,
145
+ fileExists: existsSync,
146
+ env: process.env,
147
+ cwd: process.cwd(),
148
+ dataDir: getPlannotatorDataDir(),
149
+ pathDelimiter: delimiter,
150
+ platform: process.platform,
151
+ };
152
+ }
153
+
154
+ function semBinaryName(platform: NodeJS.Platform): string {
155
+ return platform === "win32" ? "sem.exe" : "sem";
156
+ }
157
+
158
+ export function getManagedSemBinaryPath(
159
+ dataDir = getPlannotatorDataDir(),
160
+ platform: NodeJS.Platform = process.platform,
161
+ ): string {
162
+ return join(dataDir, "vendor", "sem", PLANNOTATOR_SEM_VERSION, semBinaryName(platform));
163
+ }
164
+
165
+ export function getSemanticDiffScratchCwd(dataDir = getPlannotatorDataDir()): string {
166
+ const primary = join(dataDir, "semantic-diff", "patch-only");
167
+ try {
168
+ mkdirSync(primary, { recursive: true });
169
+ return primary;
170
+ } catch {
171
+ const fallback = join(tmpdir(), "plannotator-semantic-diff");
172
+ try {
173
+ mkdirSync(fallback, { recursive: true });
174
+ return fallback;
175
+ } catch {
176
+ return tmpdir();
177
+ }
178
+ }
179
+ }
180
+
181
+ function isPathLike(value: string): boolean {
182
+ return value.includes("/") || value.includes("\\") || value.startsWith(".");
183
+ }
184
+
185
+ function pathCandidates(runtime: SemanticDiffRuntime): SemCandidate[] {
186
+ if (runtime.platform === "win32") {
187
+ const pathext = (runtime.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
188
+ .split(";")
189
+ .map((ext) => ext.trim())
190
+ .filter(Boolean);
191
+ for (const dir of (runtime.env.PATH || "").split(runtime.pathDelimiter)) {
192
+ for (const ext of pathext) {
193
+ const candidate = join(dir, `sem${ext.toLowerCase()}`);
194
+ if (runtime.fileExists(candidate)) {
195
+ return [{ command: candidate, source: "path", explicit: false }];
196
+ }
197
+ }
198
+ }
199
+ return [];
200
+ }
201
+
202
+ return [{ command: "sem", source: "path", explicit: false }];
203
+ }
204
+
205
+ function semCandidates(runtime: SemanticDiffRuntime): SemCandidate[] {
206
+ const candidates: SemCandidate[] = [];
207
+ const explicit = runtime.env.PLANNOTATOR_SEM_PATH?.trim();
208
+
209
+ if (explicit) {
210
+ candidates.push({ command: explicit, source: "env", explicit: true });
211
+ return candidates;
212
+ }
213
+
214
+ const managed = getManagedSemBinaryPath(runtime.dataDir, runtime.platform);
215
+ if (runtime.fileExists(managed)) {
216
+ candidates.push({ command: managed, source: "managed", explicit: false });
217
+ }
218
+
219
+ candidates.push(...pathCandidates(runtime));
220
+ return candidates;
221
+ }
222
+
223
+ export function parseSemVersion(stdout: string): string | null {
224
+ const match = stdout.trim().match(/^sem\s+([0-9]+(?:\.[0-9]+){1,3}(?:[-+][^\s]+)?)/);
225
+ return match?.[1] ?? null;
226
+ }
227
+
228
+ async function resolveSem(runtime: SemanticDiffRuntime): Promise<ResolvedSem | SemResolveFailure> {
229
+ for (const candidate of semCandidates(runtime)) {
230
+ if (candidate.explicit && isPathLike(candidate.command) && !runtime.fileExists(candidate.command)) {
231
+ return {
232
+ status: "unavailable",
233
+ reason: "sem-path-missing",
234
+ message: `PLANNOTATOR_SEM_PATH points to a missing file: ${candidate.command}`,
235
+ };
236
+ }
237
+
238
+ const versionResult = await runtime.runCommand(candidate.command, ["--version"], {
239
+ timeoutMs: SEM_VERSION_TIMEOUT_MS,
240
+ });
241
+ const version = parseSemVersion(versionResult.stdout);
242
+ if (versionResult.exitCode === 0 && version) {
243
+ return { command: candidate.command, source: candidate.source, version };
244
+ }
245
+
246
+ if (candidate.explicit) {
247
+ return {
248
+ status: "unavailable",
249
+ reason: "invalid-sem-binary",
250
+ message: `PLANNOTATOR_SEM_PATH did not resolve to the Ataraxy sem CLI.`,
251
+ };
252
+ }
253
+ }
254
+
255
+ return {
256
+ status: "unavailable",
257
+ reason: "sem-not-found",
258
+ message: "Semantic diff is unavailable because the Ataraxy sem CLI was not found.",
259
+ };
260
+ }
261
+
262
+ export async function getSemanticDiffAvailability(
263
+ runtime: SemanticDiffRuntime = createDefaultSemanticDiffRuntime(),
264
+ ): Promise<SemanticDiffAvailability> {
265
+ const resolved = await resolveSem(runtime);
266
+ if ("command" in resolved) {
267
+ return {
268
+ available: true,
269
+ semVersion: resolved.version,
270
+ semSource: resolved.source,
271
+ };
272
+ }
273
+
274
+ return {
275
+ available: false,
276
+ reason: resolved.reason,
277
+ message: resolved.message,
278
+ };
279
+ }
280
+
281
+ function valueAsNumber(value: unknown): number | null {
282
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
283
+ }
284
+
285
+ function valueAsString(value: unknown): string | null {
286
+ return typeof value === "string" ? value : null;
287
+ }
288
+
289
+ function valueAsBoolean(value: unknown): boolean | null {
290
+ return typeof value === "boolean" ? value : null;
291
+ }
292
+
293
+ function summaryFromJson(value: unknown): SemanticDiffSummary {
294
+ const summary = value && typeof value === "object" ? value as Record<string, unknown> : {};
295
+ return {
296
+ fileCount: valueAsNumber(summary.fileCount) ?? 0,
297
+ added: valueAsNumber(summary.added) ?? 0,
298
+ modified: valueAsNumber(summary.modified) ?? 0,
299
+ deleted: valueAsNumber(summary.deleted) ?? 0,
300
+ moved: valueAsNumber(summary.moved) ?? 0,
301
+ renamed: valueAsNumber(summary.renamed) ?? 0,
302
+ reordered: valueAsNumber(summary.reordered) ?? 0,
303
+ binary: valueAsNumber(summary.binary) ?? 0,
304
+ orphan: valueAsNumber(summary.orphan) ?? 0,
305
+ total: valueAsNumber(summary.total) ?? 0,
306
+ };
307
+ }
308
+
309
+ function changeFromJson(value: unknown): SemanticDiffChange | null {
310
+ if (!value || typeof value !== "object") return null;
311
+ const change = value as Record<string, unknown>;
312
+ const changeType = valueAsString(change.changeType);
313
+ const entityType = valueAsString(change.entityType);
314
+ const entityName = valueAsString(change.entityName);
315
+ const filePath = valueAsString(change.filePath);
316
+ if (!changeType || !entityType || !entityName || !filePath) return null;
317
+
318
+ return {
319
+ entityId: valueAsString(change.entityId),
320
+ changeType,
321
+ entityType,
322
+ entityName,
323
+ oldEntityName: valueAsString(change.oldEntityName),
324
+ filePath,
325
+ oldFilePath: valueAsString(change.oldFilePath),
326
+ startLine: valueAsNumber(change.startLine),
327
+ endLine: valueAsNumber(change.endLine),
328
+ oldStartLine: valueAsNumber(change.oldStartLine),
329
+ oldEndLine: valueAsNumber(change.oldEndLine),
330
+ structuralChange: valueAsBoolean(change.structuralChange),
331
+ };
332
+ }
333
+
334
+ function binaryChangeFromJson(value: unknown): SemanticDiffBinaryChange | null {
335
+ if (!value || typeof value !== "object") return null;
336
+ const change = value as Record<string, unknown>;
337
+ const filePath = valueAsString(change.filePath);
338
+ if (!filePath) return null;
339
+ return {
340
+ changeType: "binary",
341
+ filePath,
342
+ oldFilePath: valueAsString(change.oldFilePath),
343
+ fileStatus: valueAsString(change.fileStatus),
344
+ };
345
+ }
346
+
347
+ export function parseSemanticDiffJson(stdout: string, sem: ResolvedSem): SemanticDiffResponse {
348
+ let parsed: unknown;
349
+ try {
350
+ parsed = JSON.parse(stdout);
351
+ } catch {
352
+ return {
353
+ status: "error",
354
+ reason: "invalid-json",
355
+ message: "sem returned invalid JSON.",
356
+ semVersion: sem.version,
357
+ semSource: sem.source,
358
+ };
359
+ }
360
+
361
+ if (!parsed || typeof parsed !== "object") {
362
+ return {
363
+ status: "error",
364
+ reason: "invalid-json-shape",
365
+ message: "sem returned an unexpected JSON payload.",
366
+ semVersion: sem.version,
367
+ semSource: sem.source,
368
+ };
369
+ }
370
+
371
+ const payload = parsed as Record<string, unknown>;
372
+ const changes = Array.isArray(payload.changes)
373
+ ? payload.changes.map(changeFromJson).filter((change): change is SemanticDiffChange => !!change)
374
+ : [];
375
+ const binaryChanges = Array.isArray(payload.binaryChanges)
376
+ ? payload.binaryChanges.map(binaryChangeFromJson).filter((change): change is SemanticDiffBinaryChange => !!change)
377
+ : [];
378
+
379
+ return {
380
+ status: "ok",
381
+ summary: summaryFromJson(payload.summary),
382
+ changes,
383
+ binaryChanges,
384
+ semVersion: sem.version,
385
+ semSource: sem.source,
386
+ };
387
+ }
388
+
389
+ export function normalizeSemanticDiffFileExts(fileExts: string[] | undefined): string[] {
390
+ return Array.from(new Set((fileExts ?? [])
391
+ .map((ext) => ext.trim())
392
+ .filter(Boolean)
393
+ .map((ext) => ext.startsWith(".") ? ext : `.${ext}`)));
394
+ }
395
+
396
+ export function semanticDiffFileExtsFromSearchParams(params: URLSearchParams): string[] {
397
+ const requested = [
398
+ ...params.getAll("fileExt"),
399
+ ...params.getAll("fileExts").flatMap((value) => value.split(",")),
400
+ ];
401
+ return normalizeSemanticDiffFileExts(requested);
402
+ }
403
+
404
+ export function semanticDiffCacheKey(input: {
405
+ rawPatch: string;
406
+ cwd?: string;
407
+ fileExts?: string[];
408
+ }): string {
409
+ const hash = createHash("sha256");
410
+ hash.update(input.rawPatch);
411
+ hash.update("\0");
412
+ hash.update(input.cwd ?? "");
413
+ hash.update("\0");
414
+ hash.update(normalizeSemanticDiffFileExts(input.fileExts).join("\0"));
415
+ return hash.digest("hex");
416
+ }
417
+
418
+ export class SemanticDiffResponseCache {
419
+ private readonly cache = new Map<string, SemanticDiffResponse>();
420
+ private rawPatch: string | null = null;
421
+
422
+ constructor(private readonly maxEntries = 8) {}
423
+
424
+ get(cacheKey: string, rawPatch: string): SemanticDiffResponse | undefined {
425
+ this.syncPatch(rawPatch);
426
+ return this.cache.get(cacheKey);
427
+ }
428
+
429
+ set(cacheKey: string, rawPatch: string, response: SemanticDiffResponse): void {
430
+ this.syncPatch(rawPatch);
431
+
432
+ if (!this.cache.has(cacheKey) && this.cache.size >= this.maxEntries) {
433
+ const oldestKey = this.cache.keys().next().value;
434
+ if (typeof oldestKey === "string") {
435
+ this.cache.delete(oldestKey);
436
+ }
437
+ }
438
+
439
+ this.cache.set(cacheKey, response);
440
+ }
441
+
442
+ private syncPatch(rawPatch: string): void {
443
+ if (this.rawPatch === rawPatch) return;
444
+ this.cache.clear();
445
+ this.rawPatch = rawPatch;
446
+ }
447
+ }
448
+
449
+ export async function runSemanticDiff(
450
+ options: {
451
+ rawPatch: string;
452
+ cwd?: string;
453
+ fileExts?: string[];
454
+ timeoutMs?: number;
455
+ },
456
+ runtime: SemanticDiffRuntime = createDefaultSemanticDiffRuntime(),
457
+ ): Promise<SemanticDiffResponse> {
458
+ if (!options.rawPatch.trim()) {
459
+ return {
460
+ status: "ok",
461
+ summary: {
462
+ fileCount: 0,
463
+ added: 0,
464
+ modified: 0,
465
+ deleted: 0,
466
+ moved: 0,
467
+ renamed: 0,
468
+ reordered: 0,
469
+ binary: 0,
470
+ orphan: 0,
471
+ total: 0,
472
+ },
473
+ changes: [],
474
+ binaryChanges: [],
475
+ semVersion: "not-run",
476
+ semSource: "empty-patch",
477
+ };
478
+ }
479
+
480
+ const cwd = options.cwd || runtime.cwd || getSemanticDiffScratchCwd(runtime.dataDir);
481
+ const effectiveRuntime = cwd === runtime.cwd ? runtime : { ...runtime, cwd };
482
+ const resolved = await resolveSem(effectiveRuntime);
483
+ if (!("command" in resolved)) return resolved;
484
+
485
+ const fileExts = normalizeSemanticDiffFileExts(options.fileExts);
486
+ const args = ["diff", "--patch", "--format", "json"];
487
+ if (fileExts.length > 0) {
488
+ args.push("--file-exts", ...fileExts);
489
+ }
490
+
491
+ const result = await effectiveRuntime.runCommand(resolved.command, args, {
492
+ cwd,
493
+ input: options.rawPatch,
494
+ timeoutMs: options.timeoutMs ?? SEM_TIMEOUT_MS,
495
+ });
496
+
497
+ if (result.timedOut) {
498
+ return {
499
+ status: "error",
500
+ reason: "sem-timeout",
501
+ message: result.error ?? "sem timed out while analyzing the diff.",
502
+ semVersion: resolved.version,
503
+ semSource: resolved.source,
504
+ };
505
+ }
506
+
507
+ if (result.exitCode !== 0) {
508
+ return {
509
+ status: "error",
510
+ reason: "sem-exit",
511
+ message: result.stderr.trim() || result.error || `sem exited with code ${result.exitCode}.`,
512
+ exitCode: result.exitCode,
513
+ stderr: result.stderr.trim() || undefined,
514
+ semVersion: resolved.version,
515
+ semSource: resolved.source,
516
+ };
517
+ }
518
+
519
+ return parseSemanticDiffJson(result.stdout, resolved);
520
+ }