@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,417 @@
1
+ /**
2
+ * Perforce (P4) utilities for code review
3
+ *
4
+ * Provides pending changelist diff support for Perforce workspaces.
5
+ * Mirrors the structure of git.ts for consistent VCS abstraction.
6
+ */
7
+
8
+ import {
9
+ type DiffResult,
10
+ type DiffType,
11
+ type GitCommandResult,
12
+ type GitContext,
13
+ parseP4DiffType,
14
+ validateFilePath,
15
+ } from "@plannotator/shared/review-core";
16
+
17
+ // --- P4 command runner ---
18
+
19
+ async function runP4(
20
+ args: string[],
21
+ options?: { cwd?: string },
22
+ ): Promise<GitCommandResult> {
23
+ try {
24
+ const proc = Bun.spawn(["p4", ...args], {
25
+ cwd: options?.cwd,
26
+ stdout: "pipe",
27
+ stderr: "pipe",
28
+ });
29
+
30
+ const [stdout, stderr, exitCode] = await Promise.all([
31
+ new Response(proc.stdout).text(),
32
+ new Response(proc.stderr).text(),
33
+ proc.exited,
34
+ ]);
35
+
36
+ return { stdout: stdout.replace(/\r\n/g, "\n"), stderr, exitCode };
37
+ } catch {
38
+ // p4 not installed or not in PATH — treat as command failure
39
+ return { stdout: "", stderr: "p4 not found", exitCode: 1 };
40
+ }
41
+ }
42
+
43
+ // --- Path helpers ---
44
+
45
+ function normalizePath(p: string): string {
46
+ return p.replace(/\\/g, "/");
47
+ }
48
+
49
+ function toRelativePath(absPath: string, normalizedRoot: string): string {
50
+ const normalized = normalizePath(absPath);
51
+ if (normalized.startsWith(normalizedRoot + "/")) {
52
+ return normalized.slice(normalizedRoot.length + 1);
53
+ }
54
+ if (normalized.startsWith(normalizedRoot)) {
55
+ return normalized.slice(normalizedRoot.length);
56
+ }
57
+ return normalized;
58
+ }
59
+
60
+ // --- P4 workspace detection (cached) ---
61
+
62
+ export interface P4WorkspaceInfo {
63
+ clientName: string;
64
+ clientRoot: string;
65
+ /** clientRoot with backslashes normalized to forward slashes */
66
+ normalizedRoot: string;
67
+ userName: string;
68
+ serverAddress: string;
69
+ }
70
+
71
+ const workspaceCache = new Map<string, { info: P4WorkspaceInfo | null; ts: number }>();
72
+ const CACHE_TTL_MS = 30_000;
73
+
74
+ export async function detectP4Workspace(
75
+ cwd?: string,
76
+ ): Promise<P4WorkspaceInfo | null> {
77
+ const key = cwd ?? process.cwd();
78
+ const cached = workspaceCache.get(key);
79
+ if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
80
+ return cached.info;
81
+ }
82
+
83
+ const result = await runP4(["info"], { cwd });
84
+ if (result.exitCode !== 0) {
85
+ workspaceCache.set(key, { info: null, ts: Date.now() });
86
+ return null;
87
+ }
88
+
89
+ const info: Record<string, string> = {};
90
+ for (const line of result.stdout.split("\n")) {
91
+ const colonIdx = line.indexOf(": ");
92
+ if (colonIdx !== -1) {
93
+ info[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 2).trim();
94
+ }
95
+ }
96
+
97
+ const clientName = info["Client name"];
98
+ const clientRoot = info["Client root"];
99
+ const userName = info["User name"];
100
+ const serverAddress = info["Server address"];
101
+
102
+ if (!clientName || !clientRoot) {
103
+ workspaceCache.set(key, { info: null, ts: Date.now() });
104
+ return null;
105
+ }
106
+
107
+ const wsInfo: P4WorkspaceInfo = {
108
+ clientName,
109
+ clientRoot,
110
+ normalizedRoot: normalizePath(clientRoot).replace(/\/$/, ""),
111
+ userName,
112
+ serverAddress,
113
+ };
114
+ workspaceCache.set(key, { info: wsInfo, ts: Date.now() });
115
+ return wsInfo;
116
+ }
117
+
118
+ // --- P4 context (GitContext compatible) ---
119
+
120
+ export async function getP4Context(cwd?: string): Promise<GitContext> {
121
+ const workspace = await detectP4Workspace(cwd);
122
+ if (!workspace) {
123
+ return {
124
+ currentBranch: "",
125
+ defaultBranch: "",
126
+ diffOptions: [],
127
+ worktrees: [],
128
+ availableBranches: { local: [], remote: [] },
129
+ cwd,
130
+ vcsType: "p4",
131
+ };
132
+ }
133
+
134
+ const diffOptions: GitContext["diffOptions"] = [];
135
+
136
+ // Check default changelist has files
137
+ const [defaultOpened, changesResult] = await Promise.all([
138
+ runP4(["opened", "-c", "default"], { cwd }),
139
+ runP4(["changes", "-s", "pending", "-u", workspace.userName, "-c", workspace.clientName], { cwd }),
140
+ ]);
141
+
142
+ if (defaultOpened.exitCode === 0 && defaultOpened.stdout.trim()) {
143
+ diffOptions.push({ id: "p4-default", label: "Default changelist" });
144
+ }
145
+
146
+ // Collect numbered changelists, then check which have files
147
+ if (changesResult.exitCode === 0) {
148
+ const candidates: { clNumber: string; desc: string }[] = [];
149
+ for (const line of changesResult.stdout.trim().split("\n")) {
150
+ if (!line) continue;
151
+ const match = line.match(/^Change (\d+) on .+? by .+? \*?'(.*)'\s*$/);
152
+ if (match) {
153
+ const desc = match[2].length > 40 ? match[2].slice(0, 40) + "..." : match[2];
154
+ candidates.push({ clNumber: match[1], desc });
155
+ }
156
+ }
157
+
158
+ // Check all changelists for opened files in parallel
159
+ const checks = await Promise.all(
160
+ candidates.map(({ clNumber }) => runP4(["opened", "-c", clNumber], { cwd })),
161
+ );
162
+
163
+ for (let i = 0; i < candidates.length; i++) {
164
+ if (checks[i].exitCode === 0 && checks[i].stdout.trim()) {
165
+ diffOptions.push({
166
+ id: `p4-changelist:${candidates[i].clNumber}`,
167
+ label: `CL ${candidates[i].clNumber}: ${candidates[i].desc}`,
168
+ });
169
+ }
170
+ }
171
+ }
172
+
173
+ return {
174
+ currentBranch: workspace.clientName,
175
+ defaultBranch: "",
176
+ diffOptions,
177
+ worktrees: [],
178
+ availableBranches: { local: [], remote: [] },
179
+ cwd: cwd ?? workspace.clientRoot,
180
+ vcsType: "p4",
181
+ };
182
+ }
183
+
184
+ // --- P4 diff ---
185
+
186
+ /**
187
+ * Convert P4 diff output to git-compatible unified diff format.
188
+ *
189
+ * P4 `diff -du` outputs:
190
+ * --- //depot/path/file.cpp\tdate
191
+ * +++ C:\Workspace\client\path\file.cpp\tdate
192
+ * @@ hunks @@
193
+ *
194
+ * Converted to:
195
+ * diff --git a/relative/file.cpp b/relative/file.cpp
196
+ * --- a/relative/file.cpp
197
+ * +++ b/relative/file.cpp
198
+ * @@ hunks @@
199
+ */
200
+ function convertP4DiffToGitFormat(
201
+ rawOutput: string,
202
+ normalizedRoot: string,
203
+ ): string {
204
+ const lines = rawOutput.split("\n");
205
+ const result: string[] = [];
206
+
207
+ for (let i = 0; i < lines.length; i++) {
208
+ const line = lines[i];
209
+
210
+ // Match ==== separator (some P4 versions use this)
211
+ const separatorMatch = line.match(/^==== .+#\d+ - (.+?) ====/);
212
+ if (separatorMatch) {
213
+ const relativePath = toRelativePath(separatorMatch[1], normalizedRoot);
214
+ result.push(`diff --git a/${relativePath} b/${relativePath}`);
215
+ continue;
216
+ }
217
+
218
+ // Match --- line: starts a new file diff
219
+ if (line.startsWith("--- ") && !line.startsWith("--- a/")) {
220
+ // Determine relative path from +++ line (local path, more reliable)
221
+ const nextLine = lines[i + 1];
222
+ let relativePath: string;
223
+ if (nextLine && nextLine.startsWith("+++ ")) {
224
+ const localPath = nextLine.slice(4).split("\t")[0];
225
+ relativePath = toRelativePath(localPath, normalizedRoot);
226
+ } else {
227
+ const rawPath = line.slice(4).split("\t")[0];
228
+ relativePath = toRelativePath(rawPath, normalizedRoot);
229
+ }
230
+
231
+ result.push(`diff --git a/${relativePath} b/${relativePath}`);
232
+ result.push(`--- a/${relativePath}`);
233
+ continue;
234
+ }
235
+
236
+ if (line.startsWith("+++ ") && !line.startsWith("+++ b/")) {
237
+ const localPath = line.slice(4).split("\t")[0];
238
+ const relativePath = toRelativePath(localPath, normalizedRoot);
239
+ result.push(`+++ b/${relativePath}`);
240
+ continue;
241
+ }
242
+
243
+ result.push(line);
244
+ }
245
+
246
+ return result.join("\n");
247
+ }
248
+
249
+ /**
250
+ * Get unified diff for new files (p4 add) that have no depot version yet.
251
+ */
252
+ async function getNewFileDiff(
253
+ localPath: string,
254
+ relativePath: string,
255
+ ): Promise<string> {
256
+ try {
257
+ const content = await Bun.file(localPath).text();
258
+ const lines = content.replace(/\r\n/g, "\n").split("\n");
259
+ const header = [
260
+ `diff --git a/${relativePath} b/${relativePath}`,
261
+ "new file mode 100644",
262
+ "--- /dev/null",
263
+ `+++ b/${relativePath}`,
264
+ `@@ -0,0 +1,${lines.length} @@`,
265
+ ];
266
+ return header.join("\n") + "\n" + lines.map((l) => `+${l}`).join("\n");
267
+ } catch {
268
+ return "";
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Batch-resolve depot paths to local paths via a single `p4 where` call.
274
+ * Returns a map from depot path to { localPath, relativePath }.
275
+ */
276
+ async function batchResolveDepotPaths(
277
+ depotPaths: string[],
278
+ normalizedRoot: string,
279
+ cwd?: string,
280
+ ): Promise<Map<string, { localPath: string; relativePath: string }>> {
281
+ const result = new Map<string, { localPath: string; relativePath: string }>();
282
+ if (depotPaths.length === 0) return result;
283
+
284
+ // Use -ztag for structured output — avoids fragile index-based parsing
285
+ // that breaks with stream depots, overlays, or unmapped path error lines
286
+ const whereResult = await runP4(["-ztag", "where", ...depotPaths], { cwd });
287
+ if (whereResult.exitCode !== 0) return result;
288
+
289
+ let currentDepot = "";
290
+ let currentPath = "";
291
+
292
+ for (const line of whereResult.stdout.split("\n")) {
293
+ const tagMatch = line.match(/^\.\.\. (\w+) (.+)/);
294
+ if (!tagMatch) {
295
+ if (currentDepot && currentPath) {
296
+ const localPath = normalizePath(currentPath);
297
+ const relativePath = toRelativePath(localPath, normalizedRoot);
298
+ result.set(currentDepot, { localPath, relativePath });
299
+ }
300
+ currentDepot = "";
301
+ currentPath = "";
302
+ continue;
303
+ }
304
+
305
+ const [, field, value] = tagMatch;
306
+ if (field === "depotFile") currentDepot = value;
307
+ if (field === "path") currentPath = value;
308
+ }
309
+
310
+ // Handle last record (no trailing blank line)
311
+ if (currentDepot && currentPath) {
312
+ const localPath = normalizePath(currentPath);
313
+ const relativePath = toRelativePath(localPath, normalizedRoot);
314
+ result.set(currentDepot, { localPath, relativePath });
315
+ }
316
+
317
+ return result;
318
+ }
319
+
320
+ export async function runP4Diff(
321
+ diffType: DiffType,
322
+ cwd?: string,
323
+ ): Promise<DiffResult> {
324
+ const workspace = await detectP4Workspace(cwd);
325
+ if (!workspace) {
326
+ return { patch: "", label: "P4 error", error: "Not in a Perforce workspace" };
327
+ }
328
+
329
+ const parsed = parseP4DiffType(diffType);
330
+ if (!parsed) {
331
+ return { patch: "", label: "Unknown diff type" };
332
+ }
333
+
334
+ try {
335
+ const label = parsed.changelist === "default"
336
+ ? "Default changelist"
337
+ : `Changelist ${parsed.changelist}`;
338
+
339
+ const openedArgs = parsed.changelist === "default"
340
+ ? ["opened", "-c", "default"]
341
+ : ["opened", "-c", parsed.changelist];
342
+ const openedResult = await runP4(openedArgs, { cwd });
343
+
344
+ if (openedResult.exitCode !== 0 || !openedResult.stdout.trim()) {
345
+ return { patch: "", label, error: openedResult.stderr || undefined };
346
+ }
347
+
348
+ const diffDepotPaths: string[] = [];
349
+ const addedDepotPaths: string[] = [];
350
+
351
+ for (const line of openedResult.stdout.trim().split("\n")) {
352
+ if (!line) continue;
353
+ // Skip binary files — type in parentheses, e.g. "(binary)", "(binary+l)"
354
+ const typeMatch = line.match(/\((\S+)\)\s*(by\s|$)/);
355
+ if (typeMatch && typeMatch[1].startsWith("binary")) continue;
356
+ const depotPath = line.split("#")[0];
357
+ if (!depotPath) continue;
358
+ if (line.includes(" - add ")) {
359
+ addedDepotPaths.push(depotPath);
360
+ } else {
361
+ diffDepotPaths.push(depotPath);
362
+ }
363
+ }
364
+
365
+ let patch = "";
366
+
367
+ if (diffDepotPaths.length > 0) {
368
+ const diffResult = await runP4(["diff", "-du", ...diffDepotPaths], { cwd });
369
+ if (diffResult.exitCode === 0 || diffResult.stdout.trim()) {
370
+ patch = convertP4DiffToGitFormat(diffResult.stdout, workspace.normalizedRoot);
371
+ }
372
+ }
373
+
374
+ // Handle newly added files (p4 add) — they don't appear in p4 diff
375
+ if (addedDepotPaths.length > 0) {
376
+ const resolved = await batchResolveDepotPaths(addedDepotPaths, workspace.normalizedRoot, cwd);
377
+ for (const depotPath of addedDepotPaths) {
378
+ const mapping = resolved.get(depotPath);
379
+ if (!mapping) continue;
380
+ const newFilePatch = await getNewFileDiff(mapping.localPath, mapping.relativePath);
381
+ if (newFilePatch) {
382
+ patch += (patch ? "\n" : "") + newFilePatch;
383
+ }
384
+ }
385
+ }
386
+
387
+ return { patch, label };
388
+ } catch (error) {
389
+ const message = error instanceof Error ? error.message : String(error);
390
+ return { patch: "", label: "P4 error", error: message };
391
+ }
392
+ }
393
+
394
+ // --- File content retrieval ---
395
+
396
+ export async function getP4FileContentsForDiff(
397
+ diffType: DiffType,
398
+ filePath: string,
399
+ cwd?: string,
400
+ ): Promise<{ oldContent: string | null; newContent: string | null }> {
401
+ const workspace = await detectP4Workspace(cwd);
402
+ if (!workspace) return { oldContent: null, newContent: null };
403
+
404
+ validateFilePath(filePath);
405
+
406
+ const fullLocalPath = `${workspace.normalizedRoot}/${filePath}`;
407
+
408
+ const [printResult, newContent] = await Promise.all([
409
+ runP4(["print", "-q", `${fullLocalPath}#have`], { cwd }),
410
+ Bun.file(fullLocalPath).text().catch(() => null),
411
+ ]);
412
+
413
+ return {
414
+ oldContent: printResult.exitCode === 0 ? printResult.stdout : null,
415
+ newContent,
416
+ };
417
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@plannotator/server",
3
+ "version": "0.20.0",
4
+ "private": true,
5
+ "description": "Shared server implementation for Plannotator plugins",
6
+ "main": "index.ts",
7
+ "types": "index.ts",
8
+ "exports": {
9
+ ".": "./index.ts",
10
+ "./review": "./review.ts",
11
+ "./annotate": "./annotate.ts",
12
+ "./remote": "./remote.ts",
13
+ "./browser": "./browser.ts",
14
+ "./goal-setup": "./goal-setup.ts",
15
+ "./storage": "./storage.ts",
16
+ "./git": "./git.ts",
17
+ "./p4": "./p4.ts",
18
+ "./vcs": "./vcs.ts",
19
+ "./repo": "./repo.ts",
20
+ "./review-workspace": "./review-workspace.ts",
21
+ "./share-url": "./share-url.ts",
22
+ "./sessions": "./sessions.ts",
23
+ "./project": "./project.ts",
24
+ "./pr": "./pr.ts"
25
+ },
26
+ "files": [
27
+ "*.ts"
28
+ ],
29
+ "dependencies": {
30
+ "@pierre/diffs": "^1.1.12",
31
+ "@plannotator/ai": "workspace:*",
32
+ "@plannotator/shared": "workspace:*"
33
+ },
34
+ "peerDependencies": {
35
+ "bun": ">=1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "glimpseui": "^0.8.0"
39
+ }
40
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Strip a cwd prefix from an absolute path to get a repo-relative path.
3
+ * Used by review agent transforms to convert absolute file paths from
4
+ * agent output into diff-compatible relative paths.
5
+ *
6
+ * Uses path.relative for cross-platform support (Windows backslashes)
7
+ * and normalizes to forward slashes for git diff path matching.
8
+ */
9
+ import { relative } from "node:path";
10
+
11
+ export function toRelativePath(absolutePath: string, cwd?: string): string {
12
+ if (!cwd) return absolutePath;
13
+ const rel = relative(cwd, absolutePath);
14
+ // Don't relativize if the result goes outside cwd (different drive, symlink escape)
15
+ if (rel.startsWith("..")) return absolutePath;
16
+ // Normalize to forward slashes for diff path matching
17
+ return rel.replace(/\\/g, "/");
18
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * PR/MR provider for Bun runtimes
3
+ *
4
+ * Thin wrapper around shared pr-provider.ts, same pattern as git.ts.
5
+ * Pre-binds a Bun-based runtime so consumers get a clean API.
6
+ */
7
+
8
+ import type {
9
+ PRRef,
10
+ PRMetadata,
11
+ PRContext,
12
+ PRRuntime,
13
+ PRReviewFileComment,
14
+ PRStackTree,
15
+ PRListItem,
16
+ } from "@plannotator/shared/pr-types";
17
+ import {
18
+ parsePRUrl as parsePRUrlCore,
19
+ prRefFromMetadata,
20
+ getPlatformLabel,
21
+ getMRLabel,
22
+ getMRNumberLabel,
23
+ getDisplayRepo,
24
+ getCliName,
25
+ getCliInstallUrl,
26
+ } from "@plannotator/shared/pr-types";
27
+ import {
28
+ checkAuth as checkAuthCore,
29
+ getUser as getUserCore,
30
+ fetchPR as fetchPRCore,
31
+ fetchPRContext as fetchPRContextCore,
32
+ fetchPRFileContent as fetchPRFileContentCore,
33
+ submitPRReview as submitPRReviewCore,
34
+ fetchPRViewedFiles as fetchPRViewedFilesCore,
35
+ markPRFilesViewed as markPRFilesViewedCore,
36
+ fetchPRStack as fetchPRStackCore,
37
+ fetchPRList as fetchPRListCore,
38
+ } from "@plannotator/shared/pr-provider";
39
+
40
+ export type { PRRef, PRMetadata, PRContext, PRReviewFileComment, PRStackTree, PRListItem } from "@plannotator/shared/pr-types";
41
+ export { prRefFromMetadata, isSameProject, getPlatformLabel, getMRLabel, getMRNumberLabel, getDisplayRepo, getCliName, getCliInstallUrl } from "@plannotator/shared/pr-types";
42
+ export type { GithubPRMetadata } from "@plannotator/shared/pr-types";
43
+
44
+ const runtime: PRRuntime = {
45
+ async runCommand(cmd, args) {
46
+ const proc = Bun.spawn([cmd, ...args], {
47
+ stdout: "pipe",
48
+ stderr: "pipe",
49
+ });
50
+
51
+ const [stdout, stderr, exitCode] = await Promise.all([
52
+ new Response(proc.stdout).text(),
53
+ new Response(proc.stderr).text(),
54
+ proc.exited,
55
+ ]);
56
+
57
+ return { stdout, stderr, exitCode };
58
+ },
59
+
60
+ async runCommandWithInput(cmd, args, input) {
61
+ const proc = Bun.spawn([cmd, ...args], {
62
+ stdout: "pipe",
63
+ stderr: "pipe",
64
+ stdin: "pipe",
65
+ });
66
+
67
+ proc.stdin.write(input);
68
+ proc.stdin.end();
69
+
70
+ const [stdout, stderr, exitCode] = await Promise.all([
71
+ new Response(proc.stdout).text(),
72
+ new Response(proc.stderr).text(),
73
+ proc.exited,
74
+ ]);
75
+
76
+ return { stdout, stderr, exitCode };
77
+ },
78
+ };
79
+
80
+ export const parsePRUrl = parsePRUrlCore;
81
+
82
+ export function checkPRAuth(ref: PRRef): Promise<void> {
83
+ return checkAuthCore(runtime, ref);
84
+ }
85
+
86
+ export function getPRUser(ref: PRRef): Promise<string | null> {
87
+ return getUserCore(runtime, ref);
88
+ }
89
+
90
+ export function fetchPR(
91
+ ref: PRRef,
92
+ ): Promise<{ metadata: PRMetadata; rawPatch: string }> {
93
+ return fetchPRCore(runtime, ref);
94
+ }
95
+
96
+ export function fetchPRContext(
97
+ ref: PRRef,
98
+ ): Promise<PRContext> {
99
+ return fetchPRContextCore(runtime, ref);
100
+ }
101
+
102
+ export function fetchPRFileContent(
103
+ ref: PRRef,
104
+ sha: string,
105
+ filePath: string,
106
+ ): Promise<string | null> {
107
+ return fetchPRFileContentCore(runtime, ref, sha, filePath);
108
+ }
109
+
110
+ export function submitPRReview(
111
+ ref: PRRef,
112
+ headSha: string,
113
+ action: "approve" | "comment",
114
+ body: string,
115
+ fileComments: PRReviewFileComment[],
116
+ ): Promise<void> {
117
+ return submitPRReviewCore(runtime, ref, headSha, action, body, fileComments);
118
+ }
119
+
120
+ export function fetchPRViewedFiles(
121
+ ref: PRRef,
122
+ ): Promise<Record<string, boolean>> {
123
+ return fetchPRViewedFilesCore(runtime, ref);
124
+ }
125
+
126
+ export function markPRFilesViewed(
127
+ ref: PRRef,
128
+ prNodeId: string,
129
+ filePaths: string[],
130
+ viewed: boolean,
131
+ ): Promise<void> {
132
+ return markPRFilesViewedCore(runtime, ref, prNodeId, filePaths, viewed);
133
+ }
134
+
135
+ export function fetchPRStack(
136
+ ref: PRRef,
137
+ metadata: PRMetadata,
138
+ ): Promise<PRStackTree | null> {
139
+ return fetchPRStackCore(runtime, ref, metadata);
140
+ }
141
+
142
+ export function fetchPRList(
143
+ ref: PRRef,
144
+ ): Promise<PRListItem[]> {
145
+ return fetchPRListCore(runtime, ref);
146
+ }